From 420b91db29485df39fd6e724e782c449158811cb Mon Sep 17 00:00:00 2001 From: James Cook Date: Tue, 2 Jan 2007 08:33:20 +0000 Subject: Print done when done. --- indra/lib/python/indra/__init__.py | 6 + indra/linux_crash_logger/linux_crash_logger.cpp | 553 ++ indra/llaudio/llaudiodecodemgr.cpp | 616 ++ indra/llaudio/llaudiodecodemgr.h | 47 + indra/llcharacter/llanimationstates.cpp | 319 + indra/llcharacter/llanimationstates.h | 227 + indra/llcharacter/llbvhloader.cpp | 1489 +++ indra/llcharacter/llbvhloader.h | 282 + indra/llcharacter/llcharacter.cpp | 472 + indra/llcharacter/llcharacter.h | 252 + indra/llcharacter/lleditingmotion.cpp | 234 + indra/llcharacter/lleditingmotion.h | 115 + indra/llcharacter/llgesture.cpp | 356 + indra/llcharacter/llgesture.h | 96 + indra/llcharacter/llhandmotion.cpp | 202 + indra/llcharacter/llhandmotion.h | 119 + indra/llcharacter/llheadrotmotion.cpp | 505 + indra/llcharacter/llheadrotmotion.h | 194 + indra/llcharacter/lljoint.cpp | 504 + indra/llcharacter/lljoint.h | 163 + indra/llcharacter/lljointsolverrp3.cpp | 366 + indra/llcharacter/lljointsolverrp3.h | 158 + indra/llcharacter/lljointstate.h | 106 + indra/llcharacter/llkeyframefallmotion.cpp | 123 + indra/llcharacter/llkeyframefallmotion.h | 60 + indra/llcharacter/llkeyframemotion.cpp | 2112 +++++ indra/llcharacter/llkeyframemotion.h | 437 + indra/llcharacter/llkeyframemotionparam.cpp | 442 + indra/llcharacter/llkeyframemotionparam.h | 138 + indra/llcharacter/llkeyframestandmotion.cpp | 320 + indra/llcharacter/llkeyframestandmotion.h | 98 + indra/llcharacter/llkeyframewalkmotion.cpp | 379 + indra/llcharacter/llkeyframewalkmotion.h | 158 + indra/llcharacter/llmotion.cpp | 131 + indra/llcharacter/llmotion.h | 237 + indra/llcharacter/llmotioncontroller.cpp | 927 ++ indra/llcharacter/llmotioncontroller.h | 207 + indra/llcharacter/llmultigesture.cpp | 478 + indra/llcharacter/llmultigesture.h | 213 + indra/llcharacter/llpose.cpp | 544 ++ indra/llcharacter/llpose.h | 120 + indra/llcharacter/llstatemachine.cpp | 378 + indra/llcharacter/llstatemachine.h | 130 + indra/llcharacter/lltargetingmotion.cpp | 151 + indra/llcharacter/lltargetingmotion.h | 98 + indra/llcharacter/llvisualparam.cpp | 266 + indra/llcharacter/llvisualparam.h | 131 + indra/llcommon/bitpack.cpp | 148 + indra/llcommon/bitpack.h | 190 + indra/llcommon/ctype_workaround.h | 36 + indra/llcommon/doublelinkedlist.h | 1379 +++ indra/llcommon/imageids.h | 79 + indra/llcommon/indra_constants.h | 338 + indra/llcommon/linden_common.h | 53 + indra/llcommon/linked_lists.h | 919 ++ indra/llcommon/llagentconstants.h | 141 + indra/llcommon/llapp.cpp | 616 ++ indra/llcommon/llapp.h | 259 + indra/llcommon/llapr.cpp | 201 + indra/llcommon/llapr.h | 129 + indra/llcommon/llassettype.cpp | 219 + indra/llcommon/llassettype.h | 149 + indra/llcommon/llassoclist.h | 278 + indra/llcommon/llavatarconstants.h | 23 + indra/llcommon/llboost.h | 25 + indra/llcommon/llchat.h | 69 + indra/llcommon/llclickaction.h | 20 + indra/llcommon/llcommon.cpp | 43 + indra/llcommon/llcommon.h | 28 + indra/llcommon/llcriticaldamp.cpp | 72 + indra/llcommon/llcriticaldamp.h | 35 + indra/llcommon/lldarray.h | 192 + indra/llcommon/lldarrayptr.h | 18 + indra/llcommon/lldate.cpp | 174 + indra/llcommon/lldate.h | 102 + indra/llcommon/lldefs.h | 185 + indra/llcommon/lldepthstack.h | 81 + indra/llcommon/lldlinked.h | 77 + indra/llcommon/lldqueueptr.h | 334 + indra/llcommon/llendianswizzle.h | 76 + indra/llcommon/llenum.h | 60 + indra/llcommon/llerror.cpp | 40 + indra/llcommon/llerror.h | 218 + indra/llcommon/llerrorthread.cpp | 189 + indra/llcommon/llerrorthread.h | 28 + indra/llcommon/llevent.cpp | 285 + indra/llcommon/llevent.h | 178 + indra/llcommon/lleventemitter.h | 84 + indra/llcommon/llfasttimer.h | 187 + indra/llcommon/llfile.cpp | 251 + indra/llcommon/llfile.h | 151 + indra/llcommon/llfixedbuffer.cpp | 71 + indra/llcommon/llfixedbuffer.h | 44 + indra/llcommon/llframetimer.cpp | 69 + indra/llcommon/llframetimer.h | 122 + indra/llcommon/llhash.h | 45 + indra/llcommon/llindexedqueue.h | 137 + indra/llcommon/lllinkedqueue.h | 291 + indra/llcommon/lllivefile.cpp | 74 + indra/llcommon/lllivefile.h | 34 + indra/llcommon/lllocalidhashmap.h | 877 ++ indra/llcommon/lllslconstants.h | 139 + indra/llcommon/llmap.h | 231 + indra/llcommon/llmemory.cpp | 293 + indra/llcommon/llmemory.h | 300 + indra/llcommon/llmemorystream.cpp | 52 + indra/llcommon/llmemorystream.h | 63 + indra/llcommon/llmemtype.h | 135 + indra/llcommon/llmortician.cpp | 51 + indra/llcommon/llmortician.h | 33 + indra/llcommon/llnametable.h | 87 + indra/llcommon/llpreprocessor.h | 99 + indra/llcommon/llpriqueuemap.h | 127 + indra/llcommon/llprocessor.cpp | 2125 +++++ indra/llcommon/llprocessor.h | 158 + indra/llcommon/llptrskiplist.h | 704 ++ indra/llcommon/llptrskipmap.h | 1219 +++ indra/llcommon/llqueuedthread.cpp | 491 + indra/llcommon/llqueuedthread.h | 202 + indra/llcommon/llrun.cpp | 156 + indra/llcommon/llrun.h | 144 + indra/llcommon/llsd.cpp | 731 ++ indra/llcommon/llsd.h | 366 + indra/llcommon/llsdserialize.cpp | 1621 ++++ indra/llcommon/llsdserialize.h | 592 ++ indra/llcommon/llsdserialize_xml.cpp | 706 ++ indra/llcommon/llsdserialize_xml.h | 17 + indra/llcommon/llsdutil.cpp | 177 + indra/llcommon/llsdutil.h | 50 + indra/llcommon/llsecondlifeurls.cpp | 63 + indra/llcommon/llsecondlifeurls.h | 64 + indra/llcommon/llsimplehash.h | 137 + indra/llcommon/llskiplist.h | 502 + indra/llcommon/llskipmap.h | 1004 ++ indra/llcommon/llstack.h | 30 + indra/llcommon/llstat.cpp | 803 ++ indra/llcommon/llstat.h | 182 + indra/llcommon/llstatenums.h | 40 + indra/llcommon/llstl.h | 452 + indra/llcommon/llstreamtools.cpp | 551 ++ indra/llcommon/llstreamtools.h | 98 + indra/llcommon/llstrider.h | 38 + indra/llcommon/llstring.cpp | 835 ++ indra/llcommon/llstring.h | 1281 +++ indra/llcommon/llstringtable.cpp | 323 + indra/llcommon/llstringtable.h | 208 + indra/llcommon/llsys.cpp | 534 ++ indra/llcommon/llsys.h | 91 + indra/llcommon/llthread.cpp | 330 + indra/llcommon/llthread.h | 164 + indra/llcommon/lltimer.cpp | 517 + indra/llcommon/lltimer.h | 142 + indra/llcommon/lluri.cpp | 579 ++ indra/llcommon/lluri.h | 72 + indra/llcommon/lluuidhashmap.h | 557 ++ indra/llcommon/llworkerthread.cpp | 284 + indra/llcommon/llworkerthread.h | 168 + indra/llcommon/metaclass.cpp | 60 + indra/llcommon/metaclass.h | 64 + indra/llcommon/metaclasst.h | 42 + indra/llcommon/metaproperty.cpp | 35 + indra/llcommon/metaproperty.h | 55 + indra/llcommon/metapropertyt.h | 165 + indra/llcommon/reflective.cpp | 20 + indra/llcommon/reflective.h | 24 + indra/llcommon/reflectivet.h | 30 + indra/llcommon/roles_constants.h | 168 + indra/llcommon/stdenums.h | 115 + indra/llcommon/stdtypes.h | 89 + indra/llcommon/string_table.h | 8 + indra/llcommon/timer.h | 8 + indra/llcommon/timing.cpp | 7 + indra/llcommon/timing.h | 26 + indra/llcommon/u64.cpp | 91 + indra/llcommon/u64.h | 19 + indra/llimage/llimage.cpp | 1772 ++++ indra/llimage/llimage.h | 298 + indra/llimage/llimagebmp.cpp | 624 ++ indra/llimage/llimagebmp.h | 45 + indra/llimage/llimagedxt.cpp | 475 + indra/llimage/llimagedxt.h | 120 + indra/llimage/llimagej2c.cpp | 249 + indra/llimage/llimagej2c.h | 77 + indra/llimage/llimagejpeg.cpp | 602 ++ indra/llimage/llimagejpeg.h | 63 + indra/llimage/llimagetga.cpp | 1090 +++ indra/llimage/llimagetga.h | 89 + indra/llimage/llmapimagetype.h | 22 + indra/llinventory/llcategory.cpp | 161 + indra/llinventory/llcategory.h | 80 + indra/llinventory/lleconomy.cpp | 227 + indra/llinventory/lleconomy.h | 116 + indra/llinventory/llinventory.cpp | 1773 ++++ indra/llinventory/llinventory.h | 411 + indra/llinventory/llnotecard.cpp | 286 + indra/llinventory/llnotecard.h | 41 + indra/llinventory/llparcel.cpp | 1813 ++++ indra/llinventory/llparcel.h | 576 ++ indra/llinventory/llparcelflags.h | 105 + indra/llinventory/llpermissions.cpp | 1171 +++ indra/llinventory/llpermissions.h | 426 + indra/llinventory/llpermissionsflags.h | 78 + indra/llinventory/llsaleinfo.cpp | 356 + indra/llinventory/llsaleinfo.h | 110 + indra/llinventory/lltransactionflags.cpp | 43 + indra/llinventory/lltransactionflags.h | 27 + indra/llinventory/lltransactiontypes.h | 93 + indra/llinventory/lluserrelations.cpp | 89 + indra/llinventory/lluserrelations.h | 154 + indra/llmath/camera.h | 9 + indra/llmath/coordframe.h | 9 + indra/llmath/llbboxlocal.cpp | 37 + indra/llmath/llbboxlocal.h | 50 + indra/llmath/llcamera.cpp | 591 ++ indra/llmath/llcamera.h | 169 + indra/llmath/llcoord.h | 78 + indra/llmath/llcoordframe.cpp | 729 ++ indra/llmath/llcoordframe.h | 156 + indra/llmath/llinterp.h | 400 + indra/llmath/llmath.h | 402 + indra/llmath/lloctree.h | 693 ++ indra/llmath/llperlin.cpp | 276 + indra/llmath/llperlin.h | 28 + indra/llmath/llplane.h | 49 + indra/llmath/llquantize.h | 103 + indra/llmath/llquaternion.cpp | 830 ++ indra/llmath/llquaternion.h | 442 + indra/llmath/llrect.cpp | 10 + indra/llmath/llrect.h | 270 + indra/llmath/lltreenode.h | 161 + indra/llmath/llvolume.cpp | 4557 +++++++++ indra/llmath/llvolume.h | 882 ++ indra/llmath/llvolumemgr.cpp | 295 + indra/llmath/llvolumemgr.h | 82 + indra/llmath/m3math.cpp | 530 ++ indra/llmath/m3math.h | 198 + indra/llmath/m4math.cpp | 794 ++ indra/llmath/m4math.h | 365 + indra/llmath/raytrace.cpp | 1256 +++ indra/llmath/raytrace.h | 214 + indra/llmath/v2math.cpp | 92 + indra/llmath/v2math.h | 307 + indra/llmath/v3color.cpp | 44 + indra/llmath/v3color.h | 362 + indra/llmath/v3dmath.cpp | 129 + indra/llmath/v3dmath.h | 409 + indra/llmath/v3math.cpp | 213 + indra/llmath/v3math.h | 446 + indra/llmath/v4color.cpp | 561 ++ indra/llmath/v4color.h | 539 ++ indra/llmath/v4coloru.cpp | 102 + indra/llmath/v4coloru.h | 501 + indra/llmath/v4math.cpp | 124 + indra/llmath/v4math.h | 385 + indra/llmath/xform.cpp | 96 + indra/llmath/xform.h | 269 + indra/llmessage/llassetstorage.cpp | 1116 +++ indra/llmessage/llassetstorage.h | 328 + indra/llmessage/llbuffer.cpp | 746 ++ indra/llmessage/llbuffer.h | 493 + indra/llmessage/llbufferstream.cpp | 265 + indra/llmessage/llbufferstream.h | 134 + indra/llmessage/llcachename.cpp | 763 ++ indra/llmessage/llcachename.h | 90 + indra/llmessage/llchainio.cpp | 70 + indra/llmessage/llchainio.h | 117 + indra/llmessage/llcircuit.cpp | 1382 +++ indra/llmessage/llcircuit.h | 315 + indra/llmessage/llclassifiedflags.cpp | 49 + indra/llmessage/llclassifiedflags.h | 31 + indra/llmessage/lldatapacker.cpp | 1866 ++++ indra/llmessage/lldatapacker.h | 398 + indra/llmessage/lldbstrings.h | 208 + indra/llmessage/lldispatcher.cpp | 126 + indra/llmessage/lldispatcher.h | 95 + indra/llmessage/lleventflags.h | 17 + indra/llmessage/llfiltersd2xmlrpc.cpp | 728 ++ indra/llmessage/llfiltersd2xmlrpc.h | 253 + indra/llmessage/llfollowcamparams.h | 43 + indra/llmessage/llhost.cpp | 216 + indra/llmessage/llhost.h | 133 + indra/llmessage/llhttpassetstorage.cpp | 996 ++ indra/llmessage/llhttpassetstorage.h | 116 + indra/llmessage/llhttpclient.cpp | 278 + indra/llmessage/llhttpclient.h | 83 + indra/llmessage/llhttpnode.cpp | 449 + indra/llmessage/llhttpnode.h | 306 + indra/llmessage/llinstantmessage.cpp | 299 + indra/llmessage/llinstantmessage.h | 308 + indra/llmessage/llinvite.h | 15 + indra/llmessage/lliobuffer.cpp | 96 + indra/llmessage/lliobuffer.h | 117 + indra/llmessage/lliohttpserver.cpp | 833 ++ indra/llmessage/lliohttpserver.h | 95 + indra/llmessage/lliopipe.cpp | 93 + indra/llmessage/lliopipe.h | 291 + indra/llmessage/lliosocket.cpp | 596 ++ indra/llmessage/lliosocket.h | 355 + indra/llmessage/llioutil.cpp | 76 + indra/llmessage/llioutil.h | 154 + indra/llmessage/llloginflags.h | 37 + indra/llmessage/llmail.cpp | 285 + indra/llmessage/llmail.h | 66 + indra/llmessage/llmessagethrottle.cpp | 135 + indra/llmessage/llmessagethrottle.h | 62 + indra/llmessage/llmime.cpp | 613 ++ indra/llmessage/llmime.h | 274 + indra/llmessage/llnamevalue.cpp | 2141 +++++ indra/llmessage/llnamevalue.h | 186 + indra/llmessage/llnullcipher.cpp | 40 + indra/llmessage/llpacketack.h | 143 + indra/llmessage/llpacketbuffer.cpp | 75 + indra/llmessage/llpacketbuffer.h | 37 + indra/llmessage/llpacketring.cpp | 293 + indra/llmessage/llpacketring.h | 74 + indra/llmessage/llpartdata.cpp | 307 + indra/llmessage/llpartdata.h | 217 + indra/llmessage/llpumpio.cpp | 1006 ++ indra/llmessage/llpumpio.h | 406 + indra/llmessage/llqueryflags.h | 32 + indra/llmessage/llregionflags.h | 157 + indra/llmessage/llregionhandle.h | 110 + indra/llmessage/llsdappservices.cpp | 259 + indra/llmessage/llsdappservices.h | 40 + indra/llmessage/llsdhttpserver.cpp | 132 + indra/llmessage/llsdhttpserver.h | 33 + indra/llmessage/llsdrpcclient.cpp | 230 + indra/llmessage/llsdrpcclient.h | 291 + indra/llmessage/llsdrpcserver.cpp | 322 + indra/llmessage/llsdrpcserver.h | 342 + indra/llmessage/llservice.cpp | 93 + indra/llmessage/llservice.h | 167 + indra/llmessage/lltaskname.h | 42 + indra/llmessage/llteleportflags.h | 43 + indra/llmessage/llthrottle.cpp | 542 ++ indra/llmessage/llthrottle.h | 81 + indra/llmessage/lltransfermanager.cpp | 1270 +++ indra/llmessage/lltransfermanager.h | 466 + indra/llmessage/lltransfersourceasset.cpp | 235 + indra/llmessage/lltransfersourceasset.h | 62 + indra/llmessage/lltransfersourcefile.cpp | 151 + indra/llmessage/lltransfersourcefile.h | 58 + indra/llmessage/lltransfertargetfile.cpp | 104 + indra/llmessage/lltransfertargetfile.h | 53 + indra/llmessage/lltransfertargetvfile.cpp | 187 + indra/llmessage/lltransfertargetvfile.h | 70 + indra/llmessage/llurlrequest.cpp | 650 ++ indra/llmessage/llurlrequest.h | 395 + indra/llmessage/lluseroperation.cpp | 161 + indra/llmessage/lluseroperation.h | 75 + indra/llmessage/llvehicleparams.h | 105 + indra/llmessage/llxfer.cpp | 350 + indra/llmessage/llxfer.h | 98 + indra/llmessage/llxfer_file.cpp | 416 + indra/llmessage/llxfer_file.h | 68 + indra/llmessage/llxfer_mem.cpp | 199 + indra/llmessage/llxfer_mem.h | 61 + indra/llmessage/llxfer_vfile.cpp | 320 + indra/llmessage/llxfer_vfile.h | 74 + indra/llmessage/llxfermanager.cpp | 1133 +++ indra/llmessage/llxfermanager.h | 187 + indra/llmessage/llxorcipher.cpp | 108 + indra/llmessage/machine.h | 101 + indra/llmessage/mean_collision_data.h | 81 + indra/llmessage/message.cpp | 5876 ++++++++++++ indra/llmessage/message.h | 1253 +++ indra/llmessage/message_prehash.cpp | 2964 ++++++ indra/llmessage/message_prehash.h | 1498 +++ indra/llmessage/message_string_table.cpp | 75 + indra/llmessage/net.cpp | 516 + indra/llmessage/net.h | 50 + indra/llmessage/partsyspacket.cpp | 1277 +++ indra/llmessage/partsyspacket.h | 243 + indra/llmessage/patch_code.cpp | 390 + indra/llmessage/patch_code.h | 28 + indra/llmessage/patch_dct.cpp | 751 ++ indra/llmessage/patch_dct.h | 73 + indra/llmessage/patch_idct.cpp | 666 ++ indra/llmessage/sound_ids.h | 293 + indra/llprimitive/legacy_object_types.h | 59 + indra/llprimitive/llmaterialtable.cpp | 657 ++ indra/llprimitive/llmaterialtable.h | 116 + indra/llprimitive/llprimitive.cpp | 1749 ++++ indra/llprimitive/llprimitive.h | 510 + indra/llprimitive/lltextureanim.cpp | 221 + indra/llprimitive/lltextureanim.h | 54 + indra/llprimitive/lltextureentry.cpp | 348 + indra/llprimitive/lltextureentry.h | 126 + indra/llprimitive/lltreeparams.cpp | 187 + indra/llprimitive/lltreeparams.h | 183 + indra/llprimitive/llvolumemessage.cpp | 534 ++ indra/llprimitive/llvolumemessage.h | 74 + indra/llprimitive/llvolumexml.cpp | 57 + indra/llprimitive/llvolumexml.h | 27 + indra/llprimitive/material_codes.h | 35 + indra/llrender/llfontgl.cpp | 1434 +++ indra/llrender/llfontgl.h | 233 + indra/llrender/llgldbg.cpp | 204 + indra/llrender/llgldbg.h | 16 + indra/llrender/llimagegl.cpp | 1205 +++ indra/llrender/llimagegl.h | 185 + indra/llui/llbutton.cpp | 1012 ++ indra/llui/llbutton.h | 261 + indra/llui/llcallbackmap.h | 36 + indra/llui/llcheckboxctrl.cpp | 315 + indra/llui/llcheckboxctrl.h | 112 + indra/llui/llclipboard.cpp | 71 + indra/llui/llclipboard.h | 41 + indra/llui/llcombobox.cpp | 1133 +++ indra/llui/llcombobox.h | 178 + indra/llui/llctrlselectioninterface.cpp | 44 + indra/llui/llctrlselectioninterface.h | 84 + indra/llui/lldraghandle.cpp | 353 + indra/llui/lldraghandle.h | 98 + indra/llui/lleditmenuhandler.cpp | 107 + indra/llui/lleditmenuhandler.h | 50 + indra/llui/llfloater.cpp | 2933 ++++++ indra/llui/llfloater.h | 399 + indra/llui/llfocusmgr.cpp | 369 + indra/llui/llfocusmgr.h | 102 + indra/llui/lliconctrl.cpp | 139 + indra/llui/lliconctrl.h | 55 + indra/llui/llkeywords.cpp | 502 + indra/llui/llkeywords.h | 91 + indra/llui/lllineeditor.cpp | 2301 +++++ indra/llui/lllineeditor.h | 298 + indra/llui/llmenugl.cpp | 4341 +++++++++ indra/llui/llmenugl.h | 728 ++ indra/llui/llmodaldialog.cpp | 288 + indra/llui/llmodaldialog.h | 60 + indra/llui/llpanel.cpp | 1030 ++ indra/llui/llpanel.h | 229 + indra/llui/llradiogroup.cpp | 440 + indra/llui/llradiogroup.h | 102 + indra/llui/llresizebar.cpp | 257 + indra/llui/llresizebar.h | 47 + indra/llui/llresizehandle.cpp | 321 + indra/llui/llresizehandle.h | 56 + indra/llui/llresmgr.cpp | 447 + indra/llui/llresmgr.h | 147 + indra/llui/llscrollbar.cpp | 618 ++ indra/llui/llscrollbar.h | 123 + indra/llui/llscrollcontainer.cpp | 772 ++ indra/llui/llscrollcontainer.h | 109 + indra/llui/llscrollingpanellist.cpp | 150 + indra/llui/llscrollingpanellist.h | 53 + indra/llui/llscrolllistctrl.cpp | 2673 ++++++ indra/llui/llscrolllistctrl.h | 527 ++ indra/llui/llslider.cpp | 352 + indra/llui/llslider.h | 79 + indra/llui/llsliderctrl.cpp | 538 ++ indra/llui/llsliderctrl.h | 124 + indra/llui/llspinctrl.cpp | 509 + indra/llui/llspinctrl.h | 121 + indra/llui/llstyle.cpp | 223 + indra/llui/llstyle.h | 75 + indra/llui/lltabcontainer.cpp | 1469 +++ indra/llui/lltabcontainer.h | 242 + indra/llui/lltextbox.cpp | 438 + indra/llui/lltextbox.h | 106 + indra/llui/lltexteditor.cpp | 4144 ++++++++ indra/llui/lltexteditor.h | 483 + indra/llui/llui.cpp | 1764 ++++ indra/llui/llui.h | 255 + indra/llui/lluiconstants.h | 32 + indra/llui/lluictrl.cpp | 341 + indra/llui/lluictrl.h | 153 + indra/llui/lluictrlfactory.cpp | 722 ++ indra/llui/lluictrlfactory.h | 142 + indra/llui/lluistring.cpp | 87 + indra/llui/lluistring.h | 87 + indra/llui/llundo.cpp | 161 + indra/llui/llundo.h | 52 + indra/llui/llview.cpp | 2924 ++++++ indra/llui/llview.h | 489 + indra/llui/llviewborder.cpp | 337 + indra/llui/llviewborder.h | 77 + indra/llui/llviewquery.cpp | 126 + indra/llui/llviewquery.h | 89 + indra/llvfs/lldir.cpp | 461 + indra/llvfs/lldir.h | 102 + indra/llvfs/lldir_linux.cpp | 329 + indra/llvfs/lldir_linux.h | 41 + indra/llvfs/lldir_mac.cpp | 362 + indra/llvfs/lldir_mac.h | 40 + indra/llvfs/lldir_win32.cpp | 382 + indra/llvfs/lldir_win32.h | 37 + indra/llvfs/lllfsthread.cpp | 308 + indra/llvfs/lllfsthread.h | 119 + indra/llvfs/llvfile.cpp | 415 + indra/llvfs/llvfile.h | 75 + indra/llvfs/llvfs.cpp | 2048 ++++ indra/llvfs/llvfs.h | 151 + indra/llvfs/llvfsthread.cpp | 294 + indra/llvfs/llvfsthread.h | 125 + indra/llwindow/lldxhardware.cpp | 508 + indra/llwindow/lldxhardware.h | 90 + indra/llwindow/llkeyboard.cpp | 387 + indra/llwindow/llkeyboard.h | 124 + indra/llwindow/llkeyboardmacosx.cpp | 309 + indra/llwindow/llkeyboardmacosx.h | 36 + indra/llwindow/llkeyboardsdl.cpp | 324 + indra/llwindow/llkeyboardsdl.h | 37 + indra/llwindow/llkeyboardwin32.cpp | 372 + indra/llwindow/llkeyboardwin32.h | 40 + indra/llwindow/llmousehandler.h | 39 + indra/llwindow/llwindow.cpp | 399 + indra/llwindow/llwindow.h | 328 + indra/llwindow/llwindowheadless.cpp | 32 + indra/llwindow/llwindowheadless.h | 98 + indra/llwindow/llwindowmacosx-objc.h | 19 + indra/llwindow/llwindowmacosx-objc.mm | 87 + indra/llwindow/llwindowmacosx.cpp | 2904 ++++++ indra/llwindow/llwindowmacosx.h | 189 + indra/llwindow/llwindowmesaheadless.cpp | 64 + indra/llwindow/llwindowmesaheadless.h | 104 + indra/llwindow/llwindowsdl.cpp | 2487 +++++ indra/llwindow/llwindowsdl.h | 198 + indra/llwindow/llwindowwin32.cpp | 3247 +++++++ indra/llwindow/llwindowwin32.h | 187 + indra/llxml/llcontrol.cpp | 1401 +++ indra/llxml/llcontrol.h | 255 + indra/llxml/llxmlnode.cpp | 3008 ++++++ indra/llxml/llxmlnode.h | 266 + indra/llxml/llxmlparser.cpp | 398 + indra/llxml/llxmlparser.h | 109 + indra/llxml/llxmltree.cpp | 671 ++ indra/llxml/llxmltree.h | 216 + indra/lscript/lscript_alloc.h | 344 + indra/lscript/lscript_byteconvert.h | 1087 +++ indra/lscript/lscript_byteformat.h | 544 ++ indra/lscript/lscript_compile/indra.l | 834 ++ indra/lscript/lscript_compile/indra.y | 1680 ++++ indra/lscript/lscript_compile/lscript_alloc.cpp | 8 + indra/lscript/lscript_compile/lscript_bytecode.cpp | 299 + indra/lscript/lscript_compile/lscript_bytecode.h | 71 + indra/lscript/lscript_compile/lscript_error.cpp | 77 + indra/lscript/lscript_compile/lscript_error.h | 132 + indra/lscript/lscript_compile/lscript_heap.cpp | 49 + indra/lscript/lscript_compile/lscript_heap.h | 40 + indra/lscript/lscript_compile/lscript_resource.cpp | 18 + indra/lscript/lscript_compile/lscript_resource.h | 21 + indra/lscript/lscript_compile/lscript_scope.cpp | 13 + indra/lscript/lscript_compile/lscript_scope.h | 388 + indra/lscript/lscript_compile/lscript_tree.cpp | 9998 ++++++++++++++++++++ indra/lscript/lscript_compile/lscript_tree.h | 2279 +++++ .../lscript/lscript_compile/lscript_typecheck.cpp | 562 ++ indra/lscript/lscript_compile/lscript_typecheck.h | 100 + indra/lscript/lscript_execute.h | 364 + indra/lscript/lscript_execute/lscript_execute.cpp | 3910 ++++++++ .../lscript_execute/lscript_heapruntime.cpp | 501 + .../lscript/lscript_execute/lscript_heapruntime.h | 22 + indra/lscript/lscript_execute/lscript_readlso.cpp | 1553 +++ indra/lscript/lscript_execute/lscript_readlso.h | 147 + indra/lscript/lscript_export.h | 16 + indra/lscript/lscript_http.h | 27 + indra/lscript/lscript_library.h | 382 + indra/lscript/lscript_library/lscript_alloc.cpp | 1102 +++ indra/lscript/lscript_library/lscript_export.cpp | 8 + indra/lscript/lscript_library/lscript_library.cpp | 557 ++ indra/lscript/lscript_rt_interface.h | 18 + indra/mac_crash_logger/mac_crash_logger.cpp | 669 ++ indra/mac_updater/AutoUpdater.nib/classes.nib | 4 + indra/mac_updater/AutoUpdater.nib/info.nib | 14 + indra/mac_updater/AutoUpdater.nib/objects.xib | 56 + indra/mac_updater/mac_updater.cpp | 1087 +++ indra/newview/English.lproj/InfoPlist.strings | 5 + indra/newview/Info-SecondLife.plist | 39 + indra/newview/Info-SecondLifeVorbis.plist | 28 + indra/newview/SecondLife.nib/classes.nib | 4 + indra/newview/SecondLife.nib/info.nib | 23 + indra/newview/SecondLife.nib/objects.xib | 259 + indra/newview/VertexCache.h | 87 + indra/newview/VorbisFramework.h | 62 + indra/newview/app_settings/CA.pem | 1811 ++++ indra/newview/app_settings/anim.ini | 87 + indra/newview/app_settings/grass.xml | 47 + indra/newview/app_settings/keys.ini | 310 + indra/newview/app_settings/keywords.ini | 513 + .../shaders/class1/avatar/avatarF.glsl | 7 + .../shaders/class1/avatar/avatarSkinV.glsl | 19 + .../shaders/class1/avatar/avatarV.glsl | 35 + .../shaders/class1/avatar/eyeballF.glsl | 7 + .../shaders/class1/avatar/eyeballV.glsl | 20 + .../shaders/class1/avatar/pickAvatarF.glsl | 6 + .../shaders/class1/avatar/pickAvatarV.glsl | 17 + .../shaders/class1/environment/terrainF.glsl | 19 + .../shaders/class1/environment/terrainV.glsl | 37 + .../shaders/class1/environment/waterF.glsl | 22 + .../shaders/class1/environment/waterV.glsl | 41 + .../shaders/class1/interface/highlightF.glsl | 6 + .../shaders/class1/interface/highlightV.glsl | 20 + .../shaders/class1/lighting/lightF.glsl | 31 + .../shaders/class1/lighting/lightV.glsl | 99 + .../shaders/class1/objects/simpleF.glsl | 6 + .../shaders/class1/objects/simpleV.glsl | 21 + .../shaders/class2/avatar/eyeballV.glsl | 23 + .../shaders/class2/environment/waterF.glsl | 136 + .../shaders/class2/lighting/lightF.glsl | 36 + .../shaders/class2/lighting/lightV.glsl | 126 + .../shaders/class3/avatar/avatarV.glsl | 128 + indra/newview/app_settings/std_bump.ini | 18 + indra/newview/app_settings/trees.xml | 24 + indra/newview/app_settings/viewerart.xml | 504 + indra/newview/cursors_mac/UI_CURSOR_ARROW.tif | Bin 0 -> 14336 bytes indra/newview/cursors_mac/UI_CURSOR_ARROWDRAG.tif | Bin 0 -> 14368 bytes .../newview/cursors_mac/UI_CURSOR_ARROWLOCKED.tif | Bin 0 -> 14376 bytes indra/newview/cursors_mac/UI_CURSOR_GRABLOCKED.tif | Bin 0 -> 14392 bytes indra/newview/cursors_mac/UI_CURSOR_NO.tif | Bin 0 -> 14380 bytes indra/newview/cursors_mac/UI_CURSOR_NOLOCKED.tif | Bin 0 -> 14416 bytes indra/newview/cursors_mac/UI_CURSOR_SIZENESW.tif | Bin 0 -> 14336 bytes indra/newview/cursors_mac/UI_CURSOR_SIZENS.tif | Bin 0 -> 14344 bytes indra/newview/cursors_mac/UI_CURSOR_SIZENWSE.tif | Bin 0 -> 14340 bytes indra/newview/cursors_mac/UI_CURSOR_SIZEWE.tif | Bin 0 -> 14328 bytes indra/newview/cursors_mac/UI_CURSOR_TOOLBUY.tif | Bin 0 -> 14776 bytes indra/newview/cursors_mac/UI_CURSOR_TOOLCAMERA.tif | Bin 0 -> 14356 bytes indra/newview/cursors_mac/UI_CURSOR_TOOLCREATE.tif | Bin 0 -> 14368 bytes indra/newview/cursors_mac/UI_CURSOR_TOOLFOCUS.tif | Bin 0 -> 14348 bytes indra/newview/cursors_mac/UI_CURSOR_TOOLGRAB.tif | Bin 0 -> 14364 bytes indra/newview/cursors_mac/UI_CURSOR_TOOLLAND.tif | Bin 0 -> 14348 bytes indra/newview/cursors_mac/UI_CURSOR_TOOLOPEN.tif | Bin 0 -> 15144 bytes indra/newview/cursors_mac/UI_CURSOR_TOOLPAN.tif | Bin 0 -> 14368 bytes .../cursors_mac/UI_CURSOR_TOOLPICKOBJECT3.tif | Bin 0 -> 14392 bytes indra/newview/cursors_mac/UI_CURSOR_TOOLROTATE.tif | Bin 0 -> 14380 bytes indra/newview/cursors_mac/UI_CURSOR_TOOLSCALE.tif | Bin 0 -> 14384 bytes indra/newview/cursors_mac/UI_CURSOR_TOOLSIT.tif | Bin 0 -> 15176 bytes .../cursors_mac/UI_CURSOR_TOOLTRANSLATE.tif | Bin 0 -> 14388 bytes indra/newview/cursors_mac/UI_CURSOR_TOOLZOOMIN.tif | Bin 0 -> 14364 bytes indra/newview/cursors_mac/UI_CURSOR_WORKING.tif | Bin 0 -> 14392 bytes indra/newview/featuretable.txt | 170 + indra/newview/featuretable_mac.txt | 151 + indra/newview/fmod_hidden_symbols.exp | 140 + indra/newview/fmodwrapper.cpp | 19 + indra/newview/gpu_table.txt | 118 + indra/newview/licenses-linux.txt | 437 + indra/newview/licenses-mac.txt | 388 + indra/newview/licenses-win32.txt | 388 + indra/newview/linux_tools/client-readme.txt | 202 + indra/newview/linux_tools/launch_url.sh | 87 + indra/newview/linux_tools/wrapper.sh | 50 + indra/newview/llagent.cpp | 7233 ++++++++++++++ indra/newview/llagent.h | 896 ++ indra/newview/llagentdata.cpp | 15 + indra/newview/llagentdata.h | 16 + indra/newview/llagentpilot.cpp | 250 + indra/newview/llagentpilot.h | 77 + indra/newview/llappearance.h | 33 + indra/newview/llassetuploadresponders.cpp | 197 + indra/newview/llassetuploadresponders.h | 26 + indra/newview/llaudiosourcevo.cpp | 129 + indra/newview/llaudiosourcevo.h | 34 + indra/newview/llbox.cpp | 105 + indra/newview/llbox.h | 32 + indra/newview/llcallbacklist.cpp | 173 + indra/newview/llcallbacklist.h | 39 + indra/newview/llcallingcard.cpp | 797 ++ indra/newview/llcallingcard.h | 224 + indra/newview/llchatbar.cpp | 706 ++ indra/newview/llchatbar.h | 98 + indra/newview/llclassifiedinfo.cpp | 49 + indra/newview/llclassifiedinfo.h | 31 + indra/newview/llcloud.cpp | 555 ++ indra/newview/llcloud.h | 182 + indra/newview/llcolorswatch.cpp | 386 + indra/newview/llcolorswatch.h | 88 + indra/newview/llcompilequeue.cpp | 756 ++ indra/newview/llcompilequeue.h | 211 + indra/newview/llconfirmationmanager.cpp | 100 + indra/newview/llconfirmationmanager.h | 67 + indra/newview/llcurrencyuimanager.cpp | 497 + indra/newview/llcurrencyuimanager.h | 72 + indra/newview/llcylinder.cpp | 308 + indra/newview/llcylinder.h | 67 + indra/newview/lldebugmessagebox.cpp | 226 + indra/newview/lldebugmessagebox.h | 72 + indra/newview/lldebugview.cpp | 133 + indra/newview/lldebugview.h | 46 + indra/newview/lldirpicker.cpp | 266 + indra/newview/lldirpicker.h | 84 + indra/newview/lldrawable.cpp | 1320 +++ indra/newview/lldrawable.h | 311 + indra/newview/lldrawpool.cpp | 1402 +++ indra/newview/lldrawpool.h | 348 + indra/newview/lldrawpoolalpha.cpp | 584 ++ indra/newview/lldrawpoolalpha.h | 64 + indra/newview/lldrawpoolavatar.cpp | 610 ++ indra/newview/lldrawpoolavatar.h | 54 + indra/newview/lldrawpoolbump.cpp | 1135 +++ indra/newview/lldrawpoolbump.h | 141 + indra/newview/lldrawpoolclouds.cpp | 87 + indra/newview/lldrawpoolclouds.h | 28 + indra/newview/lldrawpoolground.cpp | 85 + indra/newview/lldrawpoolground.h | 28 + indra/newview/lldrawpoolsimple.cpp | 227 + indra/newview/lldrawpoolsimple.h | 74 + indra/newview/lldrawpoolsky.cpp | 180 + indra/newview/lldrawpoolsky.h | 42 + indra/newview/lldrawpoolterrain.cpp | 1091 +++ indra/newview/lldrawpoolterrain.h | 49 + indra/newview/lldrawpooltree.cpp | 342 + indra/newview/lldrawpooltree.h | 41 + indra/newview/lldrawpoolwater.cpp | 729 ++ indra/newview/lldrawpoolwater.h | 57 + indra/newview/lldriverparam.cpp | 435 + indra/newview/lldriverparam.h | 90 + indra/newview/lldynamictexture.cpp | 215 + indra/newview/lldynamictexture.h | 67 + indra/newview/llemote.cpp | 126 + indra/newview/llemote.h | 104 + indra/newview/lleventinfo.cpp | 134 + indra/newview/lleventinfo.h | 50 + indra/newview/lleventnotifier.cpp | 308 + indra/newview/lleventnotifier.h | 68 + indra/newview/lleventpoll.cpp | 166 + indra/newview/lleventpoll.h | 37 + indra/newview/llface.cpp | 1939 ++++ indra/newview/llface.h | 250 + indra/newview/llface.inl | 222 + indra/newview/llfasttimerview.cpp | 1084 +++ indra/newview/llfasttimerview.h | 52 + indra/newview/llfeaturemanager.cpp | 842 ++ indra/newview/llfeaturemanager.h | 98 + indra/newview/llfilepicker.cpp | 1339 +++ indra/newview/llfilepicker.h | 167 + indra/newview/llfirstuse.cpp | 196 + indra/newview/llfirstuse.h | 83 + indra/newview/llflexibleobject.cpp | 832 ++ indra/newview/llflexibleobject.h | 154 + indra/newview/llfloaterabout.cpp | 167 + indra/newview/llfloaterabout.h | 28 + indra/newview/llfloateranimpreview.cpp | 1139 +++ indra/newview/llfloateranimpreview.h | 108 + indra/newview/llfloaterauction.cpp | 307 + indra/newview/llfloaterauction.h | 53 + indra/newview/llfloateravatarpicker.cpp | 374 + indra/newview/llfloateravatarpicker.h | 78 + indra/newview/llfloateravatartextures.cpp | 180 + indra/newview/llfloateravatartextures.h | 59 + indra/newview/llfloaterbuildoptions.cpp | 73 + indra/newview/llfloaterbuildoptions.h | 36 + indra/newview/llfloaterbump.cpp | 138 + indra/newview/llfloaterbump.h | 33 + indra/newview/llfloaterbuy.cpp | 288 + indra/newview/llfloaterbuy.h | 53 + indra/newview/llfloaterbuycontents.cpp | 274 + indra/newview/llfloaterbuycontents.h | 49 + indra/newview/llfloaterbuycurrency.cpp | 356 + indra/newview/llfloaterbuycurrency.h | 28 + indra/newview/llfloaterbuyland.cpp | 1342 +++ indra/newview/llfloaterbuyland.h | 28 + indra/newview/llfloaterchat.cpp | 420 + indra/newview/llfloaterchat.h | 61 + indra/newview/llfloatercolorpicker.cpp | 1259 +++ indra/newview/llfloatercolorpicker.h | 179 + indra/newview/llfloaterfriends.cpp | 736 ++ indra/newview/llfloaterfriends.h | 124 + indra/newview/llfloatergesture.cpp | 396 + indra/newview/llfloatergesture.h | 64 + indra/newview/llfloatergodtools.cpp | 1446 +++ indra/newview/llfloatergodtools.h | 262 + indra/newview/llfloatergroupinvite.cpp | 118 + indra/newview/llfloatergroupinvite.h | 34 + indra/newview/llfloatergroups.cpp | 454 + indra/newview/llfloatergroups.h | 110 + indra/newview/llfloaterimagepreview.cpp | 679 ++ indra/newview/llfloaterimagepreview.h | 83 + indra/newview/llfloaterland.cpp | 3208 +++++++ indra/newview/llfloaterland.h | 472 + indra/newview/llfloaterlandholdings.cpp | 305 + indra/newview/llfloaterlandholdings.h | 62 + indra/newview/llfloatermap.cpp | 199 + indra/newview/llfloatermap.h | 50 + indra/newview/llfloaternamedesc.cpp | 202 + indra/newview/llfloaternamedesc.h | 42 + indra/newview/llfloateropenobject.cpp | 196 + indra/newview/llfloateropenobject.h | 56 + indra/newview/llfloaterpostcard.cpp | 319 + indra/newview/llfloaterpostcard.h | 58 + indra/newview/llfloaterpreference.cpp | 365 + indra/newview/llfloaterpreference.h | 106 + indra/newview/llfloaterproperties.cpp | 927 ++ indra/newview/llfloaterproperties.h | 82 + indra/newview/llfloaterregioninfo.cpp | 2947 ++++++ indra/newview/llfloaterregioninfo.h | 387 + indra/newview/llfloaterreporter.cpp | 819 ++ indra/newview/llfloaterreporter.h | 107 + indra/newview/llfloaterscriptdebug.cpp | 230 + indra/newview/llfloaterscriptdebug.h | 60 + indra/newview/llfloatersellland.cpp | 486 + indra/newview/llfloatersellland.h | 21 + indra/newview/llfloatersnapshot.cpp | 1523 +++ indra/newview/llfloatersnapshot.h | 53 + indra/newview/llfloatertelehub.cpp | 289 + indra/newview/llfloatertelehub.h | 57 + indra/newview/llfloatertools.cpp | 931 ++ indra/newview/llfloatertools.h | 164 + indra/newview/llfloatertopobjects.cpp | 441 + indra/newview/llfloatertopobjects.h | 86 + indra/newview/llfloatertos.cpp | 269 + indra/newview/llfloatertos.h | 64 + indra/newview/llfloaterworldmap.cpp | 1579 ++++ indra/newview/llfloaterworldmap.h | 170 + indra/newview/llfolderview.cpp | 4899 ++++++++++ indra/newview/llfolderview.h | 864 ++ indra/newview/llfollowcam.cpp | 882 ++ indra/newview/llfollowcam.h | 216 + indra/newview/llgesturemgr.cpp | 1067 +++ indra/newview/llgesturemgr.h | 138 + indra/newview/llglsandbox.cpp | 1222 +++ indra/newview/llgroupmgr.cpp | 1816 ++++ indra/newview/llgroupmgr.h | 343 + indra/newview/llhudeffect.cpp | 115 + indra/newview/llhudeffect.h | 62 + indra/newview/llhudeffectbeam.cpp | 371 + indra/newview/llhudeffectbeam.h | 51 + indra/newview/llhudeffectlookat.cpp | 501 + indra/newview/llhudeffectlookat.h | 76 + indra/newview/llhudeffectpointat.cpp | 432 + indra/newview/llhudeffectpointat.h | 65 + indra/newview/llhudeffecttrail.cpp | 271 + indra/newview/llhudeffecttrail.h | 76 + indra/newview/llhudicon.cpp | 253 + indra/newview/llhudicon.h | 72 + indra/newview/llhudmanager.cpp | 299 + indra/newview/llhudmanager.h | 60 + indra/newview/llhudobject.cpp | 277 + indra/newview/llhudobject.h | 96 + indra/newview/llhudrender.cpp | 113 + indra/newview/llhudrender.h | 39 + indra/newview/llhudtext.cpp | 991 ++ indra/newview/llhudtext.h | 155 + indra/newview/llhudview.cpp | 78 + indra/newview/llhudview.h | 37 + indra/newview/llimpanel.cpp | 793 ++ indra/newview/llimpanel.h | 155 + indra/newview/llimview.cpp | 717 ++ indra/newview/llimview.h | 150 + indra/newview/llinventorybridge.cpp | 4405 +++++++++ indra/newview/llinventorybridge.h | 579 ++ indra/newview/llinventoryclipboard.cpp | 80 + indra/newview/llinventoryclipboard.h | 65 + indra/newview/llinventorymodel.cpp | 3493 +++++++ indra/newview/llinventorymodel.h | 757 ++ indra/newview/lljoystickbutton.cpp | 847 ++ indra/newview/lljoystickbutton.h | 168 + indra/newview/lllandmarklist.cpp | 116 + indra/newview/lllandmarklist.h | 51 + indra/newview/lllightconstants.h | 15 + indra/newview/lllogchat.cpp | 82 + indra/newview/lllogchat.h | 21 + indra/newview/llmanip.cpp | 581 ++ indra/newview/llmanip.h | 137 + indra/newview/llmaniprotate.cpp | 1876 ++++ indra/newview/llmaniprotate.h | 100 + indra/newview/llmanipscale.cpp | 2024 ++++ indra/newview/llmanipscale.h | 137 + indra/newview/llmaniptranslate.cpp | 2126 +++++ indra/newview/llmaniptranslate.h | 97 + indra/newview/llmemoryview.cpp | 223 + indra/newview/llmemoryview.h | 31 + indra/newview/llmenucommands.cpp | 135 + indra/newview/llmenucommands.h | 27 + indra/newview/llmorphview.cpp | 193 + indra/newview/llmorphview.h | 68 + indra/newview/llmoveview.cpp | 198 + indra/newview/llmoveview.h | 68 + indra/newview/llmutelist.cpp | 553 ++ indra/newview/llmutelist.h | 128 + indra/newview/llnamebox.cpp | 102 + indra/newview/llnamebox.h | 46 + indra/newview/llnameeditor.cpp | 184 + indra/newview/llnameeditor.h | 70 + indra/newview/llnamelistctrl.cpp | 409 + indra/newview/llnamelistctrl.h | 71 + indra/newview/llnetmap.cpp | 806 ++ indra/newview/llnetmap.h | 104 + indra/newview/lloverlaybar.cpp | 573 ++ indra/newview/lloverlaybar.h | 77 + indra/newview/llpanelavatar.cpp | 2347 +++++ indra/newview/llpanelavatar.h | 335 + indra/newview/llpanelclassified.cpp | 852 ++ indra/newview/llpanelclassified.h | 148 + indra/newview/llpanelcontents.cpp | 189 + indra/newview/llpanelcontents.h | 39 + indra/newview/llpanelface.cpp | 956 ++ indra/newview/llpanelface.h | 136 + indra/newview/llpanelgroup.cpp | 651 ++ indra/newview/llpanelgroup.h | 173 + indra/newview/llpanelgroupgeneral.cpp | 771 ++ indra/newview/llpanelgroupgeneral.h | 91 + indra/newview/llpanelgroupinvite.cpp | 392 + indra/newview/llpanelgroupinvite.h | 32 + indra/newview/llpanelgrouplandmoney.cpp | 1387 +++ indra/newview/llpanelgrouplandmoney.h | 127 + indra/newview/llpanelgroupnotices.cpp | 565 ++ indra/newview/llpanelgroupnotices.h | 98 + indra/newview/llpanelgrouproles.cpp | 2642 ++++++ indra/newview/llpanelgrouproles.h | 297 + indra/newview/llpanelland.cpp | 236 + indra/newview/llpanelland.h | 54 + indra/newview/llpanellogin.cpp | 748 ++ indra/newview/llpanellogin.h | 83 + indra/newview/llpanelobject.cpp | 1679 ++++ indra/newview/llpanelobject.h | 147 + indra/newview/llpanelpermissions.cpp | 1028 ++ indra/newview/llpanelpermissions.h | 92 + indra/newview/llpanelpick.cpp | 494 + indra/newview/llpanelpick.h | 99 + indra/newview/llpanelplace.cpp | 271 + indra/newview/llpanelplace.h | 70 + indra/newview/llpanelvolume.cpp | 499 + indra/newview/llpanelvolume.h | 82 + indra/newview/llpatchvertexarray.cpp | 231 + indra/newview/llpatchvertexarray.h | 51 + indra/newview/llpolymesh.cpp | 1117 +++ indra/newview/llpolymesh.h | 412 + indra/newview/llpolymorph.cpp | 639 ++ indra/newview/llpolymorph.h | 163 + indra/newview/llpreview.cpp | 489 + indra/newview/llpreview.h | 139 + indra/newview/llpreviewanim.cpp | 197 + indra/newview/llpreviewanim.h | 39 + indra/newview/llpreviewgesture.cpp | 1698 ++++ indra/newview/llpreviewgesture.h | 149 + indra/newview/llpreviewnotecard.cpp | 579 ++ indra/newview/llpreviewnotecard.h | 84 + indra/newview/llpreviewscript.cpp | 1984 ++++ indra/newview/llpreviewscript.h | 212 + indra/newview/llpreviewsound.cpp | 87 + indra/newview/llpreviewsound.h | 26 + indra/newview/llpreviewtexture.cpp | 365 + indra/newview/llpreviewtexture.h | 78 + indra/newview/llprogressview.cpp | 326 + indra/newview/llprogressview.h | 54 + indra/newview/llregionposition.cpp | 76 + indra/newview/llregionposition.h | 43 + indra/newview/llsavedsettingsglue.cpp | 51 + indra/newview/llsavedsettingsglue.h | 28 + indra/newview/llselectmgr.cpp | 6732 +++++++++++++ indra/newview/llselectmgr.h | 625 ++ indra/newview/llsky.cpp | 427 + indra/newview/llsky.h | 101 + indra/newview/llspatialpartition.cpp | 2255 +++++ indra/newview/llspatialpartition.h | 209 + indra/newview/llsprite.cpp | 296 + indra/newview/llsprite.h | 89 + indra/newview/llstartup.cpp | 3959 ++++++++ indra/newview/llstartup.h | 72 + indra/newview/llstatusbar.cpp | 612 ++ indra/newview/llstatusbar.h | 108 + indra/newview/llsurface.cpp | 1337 +++ indra/newview/llsurface.h | 249 + indra/newview/llsurfacepatch.cpp | 974 ++ indra/newview/llsurfacepatch.h | 158 + indra/newview/lltable.h | 48 + indra/newview/lltexlayer.cpp | 2839 ++++++ indra/newview/lltexlayer.h | 566 ++ indra/newview/lltexturectrl.cpp | 1368 +++ indra/newview/lltexturectrl.h | 158 + indra/newview/lltexturefetch.cpp | 744 ++ indra/newview/lltexturefetch.h | 28 + indra/newview/lltextureview.cpp | 304 + indra/newview/lltextureview.h | 55 + indra/newview/lltool.cpp | 151 + indra/newview/lltool.h | 84 + indra/newview/lltoolbar.cpp | 436 + indra/newview/lltoolbar.h | 76 + indra/newview/lltoolbrush.cpp | 585 ++ indra/newview/lltoolbrush.h | 88 + indra/newview/lltoolcomp.cpp | 673 ++ indra/newview/lltoolcomp.h | 200 + indra/newview/lltooldraganddrop.cpp | 2969 ++++++ indra/newview/lltooldraganddrop.h | 239 + indra/newview/lltoolface.cpp | 141 + indra/newview/lltoolface.h | 34 + indra/newview/lltoolfocus.cpp | 442 + indra/newview/lltoolfocus.h | 58 + indra/newview/lltoolgrab.cpp | 875 ++ indra/newview/lltoolgrab.h | 112 + indra/newview/lltoolgun.cpp | 122 + indra/newview/lltoolgun.h | 35 + indra/newview/lltoolindividual.cpp | 102 + indra/newview/lltoolindividual.h | 41 + indra/newview/lltoolmgr.cpp | 532 ++ indra/newview/lltoolmgr.h | 99 + indra/newview/lltoolmorph.cpp | 279 + indra/newview/lltoolmorph.h | 88 + indra/newview/lltoolobjpicker.cpp | 162 + indra/newview/lltoolobjpicker.h | 45 + indra/newview/lltoolpie.cpp | 635 ++ indra/newview/lltoolpie.h | 59 + indra/newview/lltoolpipette.cpp | 120 + indra/newview/lltoolpipette.h | 47 + indra/newview/lltoolplacer.cpp | 225 + indra/newview/lltoolplacer.h | 79 + indra/newview/lltoolselect.cpp | 244 + indra/newview/lltoolselect.h | 39 + indra/newview/lltoolselectland.cpp | 223 + indra/newview/lltoolselectland.h | 56 + indra/newview/lltoolselectrect.cpp | 182 + indra/newview/lltoolselectrect.h | 44 + indra/newview/lltoolview.cpp | 175 + indra/newview/lltoolview.h | 69 + indra/newview/lltracker.cpp | 801 ++ indra/newview/lltracker.h | 135 + indra/newview/lluiconstants.h | 32 + indra/newview/lluploaddialog.cpp | 149 + indra/newview/lluploaddialog.h | 38 + indra/newview/llurl.cpp | 256 + indra/newview/llurl.h | 76 + indra/newview/llurlwhitelist.cpp | 221 + indra/newview/llurlwhitelist.h | 48 + indra/newview/llviewchildren.cpp | 99 + indra/newview/llviewchildren.h | 49 + indra/newview/llviewerassetstorage.cpp | 196 + indra/newview/llviewerassetstorage.h | 47 + indra/newview/llviewercamera.cpp | 611 ++ indra/newview/llviewercamera.h | 95 + indra/newview/llviewercontrol.cpp | 465 + indra/newview/llviewercontrol.h | 60 + indra/newview/llviewerdisplay.cpp | 832 ++ indra/newview/llviewerdisplay.h | 17 + indra/newview/llviewergesture.cpp | 260 + indra/newview/llviewergesture.h | 71 + indra/newview/llviewerinventory.cpp | 705 ++ indra/newview/llviewerinventory.h | 253 + indra/newview/llviewerjoint.cpp | 607 ++ indra/newview/llviewerjoint.h | 137 + indra/newview/llviewerjointattachment.cpp | 381 + indra/newview/llviewerjointattachment.h | 92 + indra/newview/llviewerjointmesh.cpp | 1601 ++++ indra/newview/llviewerjointmesh.h | 138 + indra/newview/llviewerkeyboard.cpp | 833 ++ indra/newview/llviewerkeyboard.h | 71 + indra/newview/llviewerlayer.cpp | 82 + indra/newview/llviewerlayer.h | 30 + indra/newview/llviewermenu.cpp | 8810 +++++++++++++++++ indra/newview/llviewermenu.h | 174 + indra/newview/llviewermessage.cpp | 5017 ++++++++++ indra/newview/llviewermessage.h | 203 + indra/newview/llviewernetwork.cpp | 70 + indra/newview/llviewernetwork.h | 52 + indra/newview/llviewerobject.cpp | 4683 +++++++++ indra/newview/llviewerobject.h | 626 ++ indra/newview/llviewerobjectlist.cpp | 1484 +++ indra/newview/llviewerobjectlist.h | 256 + indra/newview/llviewerparcelmgr.cpp | 2568 +++++ indra/newview/llviewerparcelmgr.h | 327 + indra/newview/llviewerparceloverlay.cpp | 850 ++ indra/newview/llviewerparceloverlay.h | 97 + indra/newview/llviewerpartsim.cpp | 626 ++ indra/newview/llviewerpartsim.h | 141 + indra/newview/llviewerpartsource.cpp | 719 ++ indra/newview/llviewerpartsource.h | 188 + indra/newview/llviewerprecompiledheaders.cpp | 16 + indra/newview/llviewerprecompiledheaders.h | 218 + indra/newview/llviewerregion.cpp | 1301 +++ indra/newview/llviewerregion.h | 348 + indra/newview/llviewerstats.cpp | 301 + indra/newview/llviewerstats.h | 167 + indra/newview/llviewertexteditor.cpp | 1475 +++ indra/newview/llviewertexteditor.h | 102 + indra/newview/llviewertextureanim.cpp | 191 + indra/newview/llviewertextureanim.h | 33 + indra/newview/llviewerthrottle.cpp | 313 + indra/newview/llviewerthrottle.h | 72 + indra/newview/llviewervisualparam.cpp | 132 + indra/newview/llviewervisualparam.h | 83 + indra/newview/llviewerwindow.cpp | 4816 ++++++++++ indra/newview/llviewerwindow.h | 391 + indra/newview/llvlcomposition.cpp | 456 + indra/newview/llvlcomposition.h | 67 + indra/newview/llvlmanager.cpp | 147 + indra/newview/llvlmanager.h | 61 + indra/newview/llvoavatar.cpp | 9426 ++++++++++++++++++ indra/newview/llvoavatar.h | 897 ++ indra/newview/llvocache.cpp | 134 + indra/newview/llvocache.h | 51 + indra/newview/llvoclouds.cpp | 188 + indra/newview/llvoclouds.h | 48 + indra/newview/llvograss.cpp | 499 + indra/newview/llvograss.h | 84 + indra/newview/llvoground.cpp | 165 + indra/newview/llvoground.h | 36 + indra/newview/llvoinventorylistener.cpp | 45 + indra/newview/llvoinventorylistener.h | 43 + indra/newview/llvopartgroup.cpp | 266 + indra/newview/llvopartgroup.h | 42 + indra/newview/llvosky.cpp | 2429 +++++ indra/newview/llvosky.h | 907 ++ indra/newview/llvosurfacepatch.cpp | 924 ++ indra/newview/llvosurfacepatch.h | 93 + indra/newview/llvotextbubble.cpp | 204 + indra/newview/llvotextbubble.h | 37 + indra/newview/llvotree.cpp | 906 ++ indra/newview/llvotree.h | 118 + indra/newview/llvotreenew.h | 200 + indra/newview/llvovolume.cpp | 2027 ++++ indra/newview/llvovolume.h | 213 + indra/newview/llvowater.cpp | 1028 ++ indra/newview/llvowater.h | 240 + indra/newview/llwearable.cpp | 952 ++ indra/newview/llwearable.h | 119 + indra/newview/llwearablelist.cpp | 325 + indra/newview/llwearablelist.h | 50 + indra/newview/llweb.cpp | 69 + indra/newview/llweb.h | 31 + indra/newview/llwind.cpp | 340 + indra/newview/llwind.h | 50 + indra/newview/llwindebug.cpp | 254 + indra/newview/llwindebug.h | 24 + indra/newview/llworld.cpp | 1141 +++ indra/newview/llworld.h | 178 + indra/newview/llworldmap.cpp | 915 ++ indra/newview/llworldmap.h | 192 + indra/newview/llworldmapview.cpp | 1940 ++++ indra/newview/llworldmapview.h | 173 + indra/newview/llxmlrpctransaction.cpp | 577 ++ indra/newview/llxmlrpctransaction.h | 115 + indra/newview/macmain.h | 26 + indra/newview/macutil_Prefix.h | 18 + indra/newview/macview.r | 123 + indra/newview/macview_Prefix.h | 221 + indra/newview/noise.cpp | 66 + indra/newview/noise.h | 334 + indra/newview/pipeline.cpp | 5316 +++++++++++ indra/newview/pipeline.h | 659 ++ indra/newview/res-sdl/arrow.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/arrowcop.BMP | Bin 0 -> 3126 bytes indra/newview/res-sdl/arrowcopmulti.BMP | Bin 0 -> 3126 bytes indra/newview/res-sdl/arrowdrag.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/circleandline.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/cross.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/hand.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/ibeam.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/ll_icon.BMP | Bin 0 -> 5174 bytes indra/newview/res-sdl/llarrow.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/llarrowdrag.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/llarrowdragmulti.BMP | Bin 0 -> 3126 bytes indra/newview/res-sdl/llarrowlocked.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/llgrablocked.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/llno.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/llnolocked.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/lltoolcamera.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/lltoolcreate.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/lltoolfocus.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/lltoolgrab.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/lltoolland.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/lltoolpan.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/lltoolpipette.BMP | Bin 0 -> 3126 bytes indra/newview/res-sdl/lltoolrotate.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/lltoolscale.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/lltooltranslate.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/lltoolzoomin.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/lltoolzoomout.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/sizenesw.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/sizens.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/sizenwse.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/sizewe.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/toolbuy.BMP | Bin 0 -> 3126 bytes indra/newview/res-sdl/toolopen.BMP | Bin 0 -> 3126 bytes indra/newview/res-sdl/toolpickobject.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/toolpickobject2.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/toolpickobject3.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/toolsit.BMP | Bin 0 -> 3126 bytes indra/newview/res-sdl/wait.BMP | Bin 0 -> 2102 bytes indra/newview/res-sdl/working.BMP | Bin 0 -> 2102 bytes indra/newview/res/arrow.cur | Bin 0 -> 326 bytes indra/newview/res/arrowcop.cur | Bin 0 -> 326 bytes indra/newview/res/arrowcopmulti.cur | Bin 0 -> 326 bytes indra/newview/res/arrowdrag.cur | Bin 0 -> 326 bytes indra/newview/res/bitmap2.bmp | Bin 0 -> 25118 bytes indra/newview/res/circleandline.cur | Bin 0 -> 326 bytes indra/newview/res/install_icon.BMP | Bin 0 -> 262198 bytes indra/newview/res/ll_icon.BMP | Bin 0 -> 262198 bytes indra/newview/res/ll_icon.ico | Bin 0 -> 364590 bytes indra/newview/res/llarrow.cur | Bin 0 -> 326 bytes indra/newview/res/llarrowdrag.cur | Bin 0 -> 326 bytes indra/newview/res/llarrowdragmulti.cur | Bin 0 -> 326 bytes indra/newview/res/llarrowlocked.cur | Bin 0 -> 326 bytes indra/newview/res/llgrablocked.cur | Bin 0 -> 326 bytes indra/newview/res/llno.cur | Bin 0 -> 326 bytes indra/newview/res/llnolocked.cur | Bin 0 -> 326 bytes indra/newview/res/lltoolcamera.cur | Bin 0 -> 326 bytes indra/newview/res/lltoolcreate.cur | Bin 0 -> 326 bytes indra/newview/res/lltoolfocus.cur | Bin 0 -> 326 bytes indra/newview/res/lltoolgrab.cur | Bin 0 -> 326 bytes indra/newview/res/lltoolland.cur | Bin 0 -> 326 bytes indra/newview/res/lltoolpan.cur | Bin 0 -> 326 bytes indra/newview/res/lltoolpipette.cur | Bin 0 -> 326 bytes indra/newview/res/lltoolrotate.cur | Bin 0 -> 326 bytes indra/newview/res/lltoolscale.cur | Bin 0 -> 326 bytes indra/newview/res/lltooltranslate.cur | Bin 0 -> 326 bytes indra/newview/res/lltoolzoomin.cur | Bin 0 -> 326 bytes indra/newview/res/lltoolzoomout.cur | Bin 0 -> 326 bytes indra/newview/res/loginbackground.bmp | Bin 0 -> 336054 bytes indra/newview/res/resource.h | 159 + indra/newview/res/toolbuy.cur | Bin 0 -> 2238 bytes indra/newview/res/toolopen.cur | Bin 0 -> 2238 bytes indra/newview/res/toolpickobject.cur | Bin 0 -> 326 bytes indra/newview/res/toolpickobject2.cur | Bin 0 -> 326 bytes indra/newview/res/toolpickobject3.cur | Bin 0 -> 326 bytes indra/newview/res/toolpipette.cur | Bin 0 -> 326 bytes indra/newview/res/toolsit.cur | Bin 0 -> 2238 bytes indra/newview/res/uninstall_icon.BMP | Bin 0 -> 262198 bytes indra/newview/secondlife.icns | Bin 0 -> 115923 bytes indra/newview/skins/paths.xml | 4 + indra/test/io.cpp | 1368 +++ indra/test/llhttpclient_tut.cpp | 296 + indra/test/llhttpnode_tut.cpp | 409 + indra/test/lliohttpserver_tut.cpp | 284 + indra/test/llpipeutil.cpp | 139 + indra/test/llpipeutil.h | 125 + indra/test/llsd_new_tut.cpp | 821 ++ indra/test/lltut.cpp | 149 + indra/test/lltut.h | 76 + indra/test/lluserrelations_tut.cpp | 138 + indra/test/test.cpp | 248 + indra/win_crash_logger/StdAfx.cpp | 16 + indra/win_crash_logger/StdAfx.h | 40 + indra/win_crash_logger/ll_icon.ico | Bin 0 -> 2238 bytes indra/win_crash_logger/resource.h | 44 + indra/win_crash_logger/win_crash_logger.cpp | 915 ++ indra/win_crash_logger/win_crash_logger.h | 20 + indra/win_crash_logger/win_crash_logger.ico | Bin 0 -> 1078 bytes indra/win_crash_logger/win_crash_logger.rc | 207 + indra/win_updater/updater.cpp | 527 ++ scripts/messages/message_template.msg | 9991 +++++++++++++++++++ scripts/update_version_files.py | 141 + 1229 files changed, 501302 insertions(+) create mode 100644 indra/lib/python/indra/__init__.py create mode 100644 indra/linux_crash_logger/linux_crash_logger.cpp create mode 100644 indra/llaudio/llaudiodecodemgr.cpp create mode 100644 indra/llaudio/llaudiodecodemgr.h create mode 100644 indra/llcharacter/llanimationstates.cpp create mode 100644 indra/llcharacter/llanimationstates.h create mode 100644 indra/llcharacter/llbvhloader.cpp create mode 100644 indra/llcharacter/llbvhloader.h create mode 100644 indra/llcharacter/llcharacter.cpp create mode 100644 indra/llcharacter/llcharacter.h create mode 100644 indra/llcharacter/lleditingmotion.cpp create mode 100644 indra/llcharacter/lleditingmotion.h create mode 100644 indra/llcharacter/llgesture.cpp create mode 100644 indra/llcharacter/llgesture.h create mode 100644 indra/llcharacter/llhandmotion.cpp create mode 100644 indra/llcharacter/llhandmotion.h create mode 100644 indra/llcharacter/llheadrotmotion.cpp create mode 100644 indra/llcharacter/llheadrotmotion.h create mode 100644 indra/llcharacter/lljoint.cpp create mode 100644 indra/llcharacter/lljoint.h create mode 100644 indra/llcharacter/lljointsolverrp3.cpp create mode 100644 indra/llcharacter/lljointsolverrp3.h create mode 100644 indra/llcharacter/lljointstate.h create mode 100644 indra/llcharacter/llkeyframefallmotion.cpp create mode 100644 indra/llcharacter/llkeyframefallmotion.h create mode 100644 indra/llcharacter/llkeyframemotion.cpp create mode 100644 indra/llcharacter/llkeyframemotion.h create mode 100644 indra/llcharacter/llkeyframemotionparam.cpp create mode 100644 indra/llcharacter/llkeyframemotionparam.h create mode 100644 indra/llcharacter/llkeyframestandmotion.cpp create mode 100644 indra/llcharacter/llkeyframestandmotion.h create mode 100644 indra/llcharacter/llkeyframewalkmotion.cpp create mode 100644 indra/llcharacter/llkeyframewalkmotion.h create mode 100644 indra/llcharacter/llmotion.cpp create mode 100644 indra/llcharacter/llmotion.h create mode 100644 indra/llcharacter/llmotioncontroller.cpp create mode 100644 indra/llcharacter/llmotioncontroller.h create mode 100644 indra/llcharacter/llmultigesture.cpp create mode 100644 indra/llcharacter/llmultigesture.h create mode 100644 indra/llcharacter/llpose.cpp create mode 100644 indra/llcharacter/llpose.h create mode 100644 indra/llcharacter/llstatemachine.cpp create mode 100644 indra/llcharacter/llstatemachine.h create mode 100644 indra/llcharacter/lltargetingmotion.cpp create mode 100644 indra/llcharacter/lltargetingmotion.h create mode 100644 indra/llcharacter/llvisualparam.cpp create mode 100644 indra/llcharacter/llvisualparam.h create mode 100644 indra/llcommon/bitpack.cpp create mode 100644 indra/llcommon/bitpack.h create mode 100644 indra/llcommon/ctype_workaround.h create mode 100644 indra/llcommon/doublelinkedlist.h create mode 100644 indra/llcommon/imageids.h create mode 100644 indra/llcommon/indra_constants.h create mode 100644 indra/llcommon/linden_common.h create mode 100644 indra/llcommon/linked_lists.h create mode 100644 indra/llcommon/llagentconstants.h create mode 100644 indra/llcommon/llapp.cpp create mode 100644 indra/llcommon/llapp.h create mode 100644 indra/llcommon/llapr.cpp create mode 100644 indra/llcommon/llapr.h create mode 100644 indra/llcommon/llassettype.cpp create mode 100644 indra/llcommon/llassettype.h create mode 100644 indra/llcommon/llassoclist.h create mode 100644 indra/llcommon/llavatarconstants.h create mode 100644 indra/llcommon/llboost.h create mode 100644 indra/llcommon/llchat.h create mode 100644 indra/llcommon/llclickaction.h create mode 100644 indra/llcommon/llcommon.cpp create mode 100644 indra/llcommon/llcommon.h create mode 100644 indra/llcommon/llcriticaldamp.cpp create mode 100644 indra/llcommon/llcriticaldamp.h create mode 100644 indra/llcommon/lldarray.h create mode 100644 indra/llcommon/lldarrayptr.h create mode 100644 indra/llcommon/lldate.cpp create mode 100644 indra/llcommon/lldate.h create mode 100644 indra/llcommon/lldefs.h create mode 100644 indra/llcommon/lldepthstack.h create mode 100644 indra/llcommon/lldlinked.h create mode 100644 indra/llcommon/lldqueueptr.h create mode 100644 indra/llcommon/llendianswizzle.h create mode 100644 indra/llcommon/llenum.h create mode 100644 indra/llcommon/llerror.cpp create mode 100644 indra/llcommon/llerror.h create mode 100644 indra/llcommon/llerrorthread.cpp create mode 100644 indra/llcommon/llerrorthread.h create mode 100644 indra/llcommon/llevent.cpp create mode 100644 indra/llcommon/llevent.h create mode 100644 indra/llcommon/lleventemitter.h create mode 100644 indra/llcommon/llfasttimer.h create mode 100644 indra/llcommon/llfile.cpp create mode 100644 indra/llcommon/llfile.h create mode 100644 indra/llcommon/llfixedbuffer.cpp create mode 100644 indra/llcommon/llfixedbuffer.h create mode 100644 indra/llcommon/llframetimer.cpp create mode 100644 indra/llcommon/llframetimer.h create mode 100644 indra/llcommon/llhash.h create mode 100644 indra/llcommon/llindexedqueue.h create mode 100644 indra/llcommon/lllinkedqueue.h create mode 100644 indra/llcommon/lllivefile.cpp create mode 100644 indra/llcommon/lllivefile.h create mode 100644 indra/llcommon/lllocalidhashmap.h create mode 100644 indra/llcommon/lllslconstants.h create mode 100644 indra/llcommon/llmap.h create mode 100644 indra/llcommon/llmemory.cpp create mode 100644 indra/llcommon/llmemory.h create mode 100644 indra/llcommon/llmemorystream.cpp create mode 100644 indra/llcommon/llmemorystream.h create mode 100644 indra/llcommon/llmemtype.h create mode 100644 indra/llcommon/llmortician.cpp create mode 100644 indra/llcommon/llmortician.h create mode 100644 indra/llcommon/llnametable.h create mode 100644 indra/llcommon/llpreprocessor.h create mode 100644 indra/llcommon/llpriqueuemap.h create mode 100644 indra/llcommon/llprocessor.cpp create mode 100644 indra/llcommon/llprocessor.h create mode 100644 indra/llcommon/llptrskiplist.h create mode 100644 indra/llcommon/llptrskipmap.h create mode 100644 indra/llcommon/llqueuedthread.cpp create mode 100644 indra/llcommon/llqueuedthread.h create mode 100644 indra/llcommon/llrun.cpp create mode 100644 indra/llcommon/llrun.h create mode 100644 indra/llcommon/llsd.cpp create mode 100644 indra/llcommon/llsd.h create mode 100644 indra/llcommon/llsdserialize.cpp create mode 100644 indra/llcommon/llsdserialize.h create mode 100644 indra/llcommon/llsdserialize_xml.cpp create mode 100644 indra/llcommon/llsdserialize_xml.h create mode 100644 indra/llcommon/llsdutil.cpp create mode 100644 indra/llcommon/llsdutil.h create mode 100644 indra/llcommon/llsecondlifeurls.cpp create mode 100644 indra/llcommon/llsecondlifeurls.h create mode 100644 indra/llcommon/llsimplehash.h create mode 100644 indra/llcommon/llskiplist.h create mode 100644 indra/llcommon/llskipmap.h create mode 100644 indra/llcommon/llstack.h create mode 100644 indra/llcommon/llstat.cpp create mode 100644 indra/llcommon/llstat.h create mode 100644 indra/llcommon/llstatenums.h create mode 100644 indra/llcommon/llstl.h create mode 100644 indra/llcommon/llstreamtools.cpp create mode 100644 indra/llcommon/llstreamtools.h create mode 100644 indra/llcommon/llstrider.h create mode 100644 indra/llcommon/llstring.cpp create mode 100644 indra/llcommon/llstring.h create mode 100644 indra/llcommon/llstringtable.cpp create mode 100644 indra/llcommon/llstringtable.h create mode 100644 indra/llcommon/llsys.cpp create mode 100644 indra/llcommon/llsys.h create mode 100644 indra/llcommon/llthread.cpp create mode 100644 indra/llcommon/llthread.h create mode 100644 indra/llcommon/lltimer.cpp create mode 100644 indra/llcommon/lltimer.h create mode 100644 indra/llcommon/lluri.cpp create mode 100644 indra/llcommon/lluri.h create mode 100644 indra/llcommon/lluuidhashmap.h create mode 100644 indra/llcommon/llworkerthread.cpp create mode 100644 indra/llcommon/llworkerthread.h create mode 100644 indra/llcommon/metaclass.cpp create mode 100644 indra/llcommon/metaclass.h create mode 100644 indra/llcommon/metaclasst.h create mode 100644 indra/llcommon/metaproperty.cpp create mode 100644 indra/llcommon/metaproperty.h create mode 100644 indra/llcommon/metapropertyt.h create mode 100644 indra/llcommon/reflective.cpp create mode 100644 indra/llcommon/reflective.h create mode 100644 indra/llcommon/reflectivet.h create mode 100644 indra/llcommon/roles_constants.h create mode 100644 indra/llcommon/stdenums.h create mode 100644 indra/llcommon/stdtypes.h create mode 100644 indra/llcommon/string_table.h create mode 100644 indra/llcommon/timer.h create mode 100644 indra/llcommon/timing.cpp create mode 100644 indra/llcommon/timing.h create mode 100644 indra/llcommon/u64.cpp create mode 100644 indra/llcommon/u64.h create mode 100644 indra/llimage/llimage.cpp create mode 100644 indra/llimage/llimage.h create mode 100644 indra/llimage/llimagebmp.cpp create mode 100644 indra/llimage/llimagebmp.h create mode 100644 indra/llimage/llimagedxt.cpp create mode 100644 indra/llimage/llimagedxt.h create mode 100644 indra/llimage/llimagej2c.cpp create mode 100644 indra/llimage/llimagej2c.h create mode 100644 indra/llimage/llimagejpeg.cpp create mode 100644 indra/llimage/llimagejpeg.h create mode 100644 indra/llimage/llimagetga.cpp create mode 100644 indra/llimage/llimagetga.h create mode 100644 indra/llimage/llmapimagetype.h create mode 100644 indra/llinventory/llcategory.cpp create mode 100644 indra/llinventory/llcategory.h create mode 100644 indra/llinventory/lleconomy.cpp create mode 100644 indra/llinventory/lleconomy.h create mode 100644 indra/llinventory/llinventory.cpp create mode 100644 indra/llinventory/llinventory.h create mode 100644 indra/llinventory/llnotecard.cpp create mode 100644 indra/llinventory/llnotecard.h create mode 100644 indra/llinventory/llparcel.cpp create mode 100644 indra/llinventory/llparcel.h create mode 100644 indra/llinventory/llparcelflags.h create mode 100644 indra/llinventory/llpermissions.cpp create mode 100644 indra/llinventory/llpermissions.h create mode 100644 indra/llinventory/llpermissionsflags.h create mode 100644 indra/llinventory/llsaleinfo.cpp create mode 100644 indra/llinventory/llsaleinfo.h create mode 100644 indra/llinventory/lltransactionflags.cpp create mode 100644 indra/llinventory/lltransactionflags.h create mode 100644 indra/llinventory/lltransactiontypes.h create mode 100644 indra/llinventory/lluserrelations.cpp create mode 100644 indra/llinventory/lluserrelations.h create mode 100644 indra/llmath/camera.h create mode 100644 indra/llmath/coordframe.h create mode 100644 indra/llmath/llbboxlocal.cpp create mode 100644 indra/llmath/llbboxlocal.h create mode 100644 indra/llmath/llcamera.cpp create mode 100644 indra/llmath/llcamera.h create mode 100644 indra/llmath/llcoord.h create mode 100644 indra/llmath/llcoordframe.cpp create mode 100644 indra/llmath/llcoordframe.h create mode 100644 indra/llmath/llinterp.h create mode 100644 indra/llmath/llmath.h create mode 100644 indra/llmath/lloctree.h create mode 100644 indra/llmath/llperlin.cpp create mode 100644 indra/llmath/llperlin.h create mode 100644 indra/llmath/llplane.h create mode 100644 indra/llmath/llquantize.h create mode 100644 indra/llmath/llquaternion.cpp create mode 100644 indra/llmath/llquaternion.h create mode 100644 indra/llmath/llrect.cpp create mode 100644 indra/llmath/llrect.h create mode 100644 indra/llmath/lltreenode.h create mode 100644 indra/llmath/llvolume.cpp create mode 100644 indra/llmath/llvolume.h create mode 100644 indra/llmath/llvolumemgr.cpp create mode 100644 indra/llmath/llvolumemgr.h create mode 100644 indra/llmath/m3math.cpp create mode 100644 indra/llmath/m3math.h create mode 100644 indra/llmath/m4math.cpp create mode 100644 indra/llmath/m4math.h create mode 100644 indra/llmath/raytrace.cpp create mode 100644 indra/llmath/raytrace.h create mode 100644 indra/llmath/v2math.cpp create mode 100644 indra/llmath/v2math.h create mode 100644 indra/llmath/v3color.cpp create mode 100644 indra/llmath/v3color.h create mode 100644 indra/llmath/v3dmath.cpp create mode 100644 indra/llmath/v3dmath.h create mode 100644 indra/llmath/v3math.cpp create mode 100644 indra/llmath/v3math.h create mode 100644 indra/llmath/v4color.cpp create mode 100644 indra/llmath/v4color.h create mode 100644 indra/llmath/v4coloru.cpp create mode 100644 indra/llmath/v4coloru.h create mode 100644 indra/llmath/v4math.cpp create mode 100644 indra/llmath/v4math.h create mode 100644 indra/llmath/xform.cpp create mode 100644 indra/llmath/xform.h create mode 100644 indra/llmessage/llassetstorage.cpp create mode 100644 indra/llmessage/llassetstorage.h create mode 100644 indra/llmessage/llbuffer.cpp create mode 100644 indra/llmessage/llbuffer.h create mode 100644 indra/llmessage/llbufferstream.cpp create mode 100644 indra/llmessage/llbufferstream.h create mode 100644 indra/llmessage/llcachename.cpp create mode 100644 indra/llmessage/llcachename.h create mode 100644 indra/llmessage/llchainio.cpp create mode 100644 indra/llmessage/llchainio.h create mode 100644 indra/llmessage/llcircuit.cpp create mode 100644 indra/llmessage/llcircuit.h create mode 100644 indra/llmessage/llclassifiedflags.cpp create mode 100644 indra/llmessage/llclassifiedflags.h create mode 100644 indra/llmessage/lldatapacker.cpp create mode 100644 indra/llmessage/lldatapacker.h create mode 100644 indra/llmessage/lldbstrings.h create mode 100644 indra/llmessage/lldispatcher.cpp create mode 100644 indra/llmessage/lldispatcher.h create mode 100644 indra/llmessage/lleventflags.h create mode 100644 indra/llmessage/llfiltersd2xmlrpc.cpp create mode 100644 indra/llmessage/llfiltersd2xmlrpc.h create mode 100644 indra/llmessage/llfollowcamparams.h create mode 100644 indra/llmessage/llhost.cpp create mode 100644 indra/llmessage/llhost.h create mode 100644 indra/llmessage/llhttpassetstorage.cpp create mode 100644 indra/llmessage/llhttpassetstorage.h create mode 100644 indra/llmessage/llhttpclient.cpp create mode 100644 indra/llmessage/llhttpclient.h create mode 100644 indra/llmessage/llhttpnode.cpp create mode 100644 indra/llmessage/llhttpnode.h create mode 100644 indra/llmessage/llinstantmessage.cpp create mode 100644 indra/llmessage/llinstantmessage.h create mode 100644 indra/llmessage/llinvite.h create mode 100644 indra/llmessage/lliobuffer.cpp create mode 100644 indra/llmessage/lliobuffer.h create mode 100644 indra/llmessage/lliohttpserver.cpp create mode 100644 indra/llmessage/lliohttpserver.h create mode 100644 indra/llmessage/lliopipe.cpp create mode 100644 indra/llmessage/lliopipe.h create mode 100644 indra/llmessage/lliosocket.cpp create mode 100644 indra/llmessage/lliosocket.h create mode 100644 indra/llmessage/llioutil.cpp create mode 100644 indra/llmessage/llioutil.h create mode 100644 indra/llmessage/llloginflags.h create mode 100644 indra/llmessage/llmail.cpp create mode 100644 indra/llmessage/llmail.h create mode 100644 indra/llmessage/llmessagethrottle.cpp create mode 100644 indra/llmessage/llmessagethrottle.h create mode 100644 indra/llmessage/llmime.cpp create mode 100644 indra/llmessage/llmime.h create mode 100644 indra/llmessage/llnamevalue.cpp create mode 100644 indra/llmessage/llnamevalue.h create mode 100644 indra/llmessage/llnullcipher.cpp create mode 100644 indra/llmessage/llpacketack.h create mode 100644 indra/llmessage/llpacketbuffer.cpp create mode 100644 indra/llmessage/llpacketbuffer.h create mode 100644 indra/llmessage/llpacketring.cpp create mode 100644 indra/llmessage/llpacketring.h create mode 100644 indra/llmessage/llpartdata.cpp create mode 100644 indra/llmessage/llpartdata.h create mode 100644 indra/llmessage/llpumpio.cpp create mode 100644 indra/llmessage/llpumpio.h create mode 100644 indra/llmessage/llqueryflags.h create mode 100644 indra/llmessage/llregionflags.h create mode 100644 indra/llmessage/llregionhandle.h create mode 100644 indra/llmessage/llsdappservices.cpp create mode 100644 indra/llmessage/llsdappservices.h create mode 100644 indra/llmessage/llsdhttpserver.cpp create mode 100644 indra/llmessage/llsdhttpserver.h create mode 100644 indra/llmessage/llsdrpcclient.cpp create mode 100644 indra/llmessage/llsdrpcclient.h create mode 100644 indra/llmessage/llsdrpcserver.cpp create mode 100644 indra/llmessage/llsdrpcserver.h create mode 100644 indra/llmessage/llservice.cpp create mode 100644 indra/llmessage/llservice.h create mode 100644 indra/llmessage/lltaskname.h create mode 100644 indra/llmessage/llteleportflags.h create mode 100644 indra/llmessage/llthrottle.cpp create mode 100644 indra/llmessage/llthrottle.h create mode 100644 indra/llmessage/lltransfermanager.cpp create mode 100644 indra/llmessage/lltransfermanager.h create mode 100644 indra/llmessage/lltransfersourceasset.cpp create mode 100644 indra/llmessage/lltransfersourceasset.h create mode 100644 indra/llmessage/lltransfersourcefile.cpp create mode 100644 indra/llmessage/lltransfersourcefile.h create mode 100644 indra/llmessage/lltransfertargetfile.cpp create mode 100644 indra/llmessage/lltransfertargetfile.h create mode 100644 indra/llmessage/lltransfertargetvfile.cpp create mode 100644 indra/llmessage/lltransfertargetvfile.h create mode 100644 indra/llmessage/llurlrequest.cpp create mode 100644 indra/llmessage/llurlrequest.h create mode 100644 indra/llmessage/lluseroperation.cpp create mode 100644 indra/llmessage/lluseroperation.h create mode 100644 indra/llmessage/llvehicleparams.h create mode 100644 indra/llmessage/llxfer.cpp create mode 100644 indra/llmessage/llxfer.h create mode 100644 indra/llmessage/llxfer_file.cpp create mode 100644 indra/llmessage/llxfer_file.h create mode 100644 indra/llmessage/llxfer_mem.cpp create mode 100644 indra/llmessage/llxfer_mem.h create mode 100644 indra/llmessage/llxfer_vfile.cpp create mode 100644 indra/llmessage/llxfer_vfile.h create mode 100644 indra/llmessage/llxfermanager.cpp create mode 100644 indra/llmessage/llxfermanager.h create mode 100644 indra/llmessage/llxorcipher.cpp create mode 100644 indra/llmessage/machine.h create mode 100644 indra/llmessage/mean_collision_data.h create mode 100644 indra/llmessage/message.cpp create mode 100644 indra/llmessage/message.h create mode 100644 indra/llmessage/message_prehash.cpp create mode 100644 indra/llmessage/message_prehash.h create mode 100644 indra/llmessage/message_string_table.cpp create mode 100644 indra/llmessage/net.cpp create mode 100644 indra/llmessage/net.h create mode 100644 indra/llmessage/partsyspacket.cpp create mode 100644 indra/llmessage/partsyspacket.h create mode 100644 indra/llmessage/patch_code.cpp create mode 100644 indra/llmessage/patch_code.h create mode 100644 indra/llmessage/patch_dct.cpp create mode 100644 indra/llmessage/patch_dct.h create mode 100644 indra/llmessage/patch_idct.cpp create mode 100644 indra/llmessage/sound_ids.h create mode 100644 indra/llprimitive/legacy_object_types.h create mode 100644 indra/llprimitive/llmaterialtable.cpp create mode 100644 indra/llprimitive/llmaterialtable.h create mode 100644 indra/llprimitive/llprimitive.cpp create mode 100644 indra/llprimitive/llprimitive.h create mode 100644 indra/llprimitive/lltextureanim.cpp create mode 100644 indra/llprimitive/lltextureanim.h create mode 100644 indra/llprimitive/lltextureentry.cpp create mode 100644 indra/llprimitive/lltextureentry.h create mode 100644 indra/llprimitive/lltreeparams.cpp create mode 100644 indra/llprimitive/lltreeparams.h create mode 100644 indra/llprimitive/llvolumemessage.cpp create mode 100644 indra/llprimitive/llvolumemessage.h create mode 100644 indra/llprimitive/llvolumexml.cpp create mode 100644 indra/llprimitive/llvolumexml.h create mode 100644 indra/llprimitive/material_codes.h create mode 100644 indra/llrender/llfontgl.cpp create mode 100644 indra/llrender/llfontgl.h create mode 100644 indra/llrender/llgldbg.cpp create mode 100644 indra/llrender/llgldbg.h create mode 100644 indra/llrender/llimagegl.cpp create mode 100644 indra/llrender/llimagegl.h create mode 100644 indra/llui/llbutton.cpp create mode 100644 indra/llui/llbutton.h create mode 100644 indra/llui/llcallbackmap.h create mode 100644 indra/llui/llcheckboxctrl.cpp create mode 100644 indra/llui/llcheckboxctrl.h create mode 100644 indra/llui/llclipboard.cpp create mode 100644 indra/llui/llclipboard.h create mode 100644 indra/llui/llcombobox.cpp create mode 100644 indra/llui/llcombobox.h create mode 100644 indra/llui/llctrlselectioninterface.cpp create mode 100644 indra/llui/llctrlselectioninterface.h create mode 100644 indra/llui/lldraghandle.cpp create mode 100644 indra/llui/lldraghandle.h create mode 100644 indra/llui/lleditmenuhandler.cpp create mode 100644 indra/llui/lleditmenuhandler.h create mode 100644 indra/llui/llfloater.cpp create mode 100644 indra/llui/llfloater.h create mode 100644 indra/llui/llfocusmgr.cpp create mode 100644 indra/llui/llfocusmgr.h create mode 100644 indra/llui/lliconctrl.cpp create mode 100644 indra/llui/lliconctrl.h create mode 100644 indra/llui/llkeywords.cpp create mode 100644 indra/llui/llkeywords.h create mode 100644 indra/llui/lllineeditor.cpp create mode 100644 indra/llui/lllineeditor.h create mode 100644 indra/llui/llmenugl.cpp create mode 100644 indra/llui/llmenugl.h create mode 100644 indra/llui/llmodaldialog.cpp create mode 100644 indra/llui/llmodaldialog.h create mode 100644 indra/llui/llpanel.cpp create mode 100644 indra/llui/llpanel.h create mode 100644 indra/llui/llradiogroup.cpp create mode 100644 indra/llui/llradiogroup.h create mode 100644 indra/llui/llresizebar.cpp create mode 100644 indra/llui/llresizebar.h create mode 100644 indra/llui/llresizehandle.cpp create mode 100644 indra/llui/llresizehandle.h create mode 100644 indra/llui/llresmgr.cpp create mode 100644 indra/llui/llresmgr.h create mode 100644 indra/llui/llscrollbar.cpp create mode 100644 indra/llui/llscrollbar.h create mode 100644 indra/llui/llscrollcontainer.cpp create mode 100644 indra/llui/llscrollcontainer.h create mode 100644 indra/llui/llscrollingpanellist.cpp create mode 100644 indra/llui/llscrollingpanellist.h create mode 100644 indra/llui/llscrolllistctrl.cpp create mode 100644 indra/llui/llscrolllistctrl.h create mode 100644 indra/llui/llslider.cpp create mode 100644 indra/llui/llslider.h create mode 100644 indra/llui/llsliderctrl.cpp create mode 100644 indra/llui/llsliderctrl.h create mode 100644 indra/llui/llspinctrl.cpp create mode 100644 indra/llui/llspinctrl.h create mode 100644 indra/llui/llstyle.cpp create mode 100644 indra/llui/llstyle.h create mode 100644 indra/llui/lltabcontainer.cpp create mode 100644 indra/llui/lltabcontainer.h create mode 100644 indra/llui/lltextbox.cpp create mode 100644 indra/llui/lltextbox.h create mode 100644 indra/llui/lltexteditor.cpp create mode 100644 indra/llui/lltexteditor.h create mode 100644 indra/llui/llui.cpp create mode 100644 indra/llui/llui.h create mode 100644 indra/llui/lluiconstants.h create mode 100644 indra/llui/lluictrl.cpp create mode 100644 indra/llui/lluictrl.h create mode 100644 indra/llui/lluictrlfactory.cpp create mode 100644 indra/llui/lluictrlfactory.h create mode 100755 indra/llui/lluistring.cpp create mode 100755 indra/llui/lluistring.h create mode 100644 indra/llui/llundo.cpp create mode 100644 indra/llui/llundo.h create mode 100644 indra/llui/llview.cpp create mode 100644 indra/llui/llview.h create mode 100644 indra/llui/llviewborder.cpp create mode 100644 indra/llui/llviewborder.h create mode 100644 indra/llui/llviewquery.cpp create mode 100644 indra/llui/llviewquery.h create mode 100644 indra/llvfs/lldir.cpp create mode 100644 indra/llvfs/lldir.h create mode 100644 indra/llvfs/lldir_linux.cpp create mode 100644 indra/llvfs/lldir_linux.h create mode 100644 indra/llvfs/lldir_mac.cpp create mode 100644 indra/llvfs/lldir_mac.h create mode 100644 indra/llvfs/lldir_win32.cpp create mode 100644 indra/llvfs/lldir_win32.h create mode 100644 indra/llvfs/lllfsthread.cpp create mode 100644 indra/llvfs/lllfsthread.h create mode 100644 indra/llvfs/llvfile.cpp create mode 100644 indra/llvfs/llvfile.h create mode 100644 indra/llvfs/llvfs.cpp create mode 100644 indra/llvfs/llvfs.h create mode 100644 indra/llvfs/llvfsthread.cpp create mode 100644 indra/llvfs/llvfsthread.h create mode 100644 indra/llwindow/lldxhardware.cpp create mode 100644 indra/llwindow/lldxhardware.h create mode 100644 indra/llwindow/llkeyboard.cpp create mode 100644 indra/llwindow/llkeyboard.h create mode 100644 indra/llwindow/llkeyboardmacosx.cpp create mode 100644 indra/llwindow/llkeyboardmacosx.h create mode 100644 indra/llwindow/llkeyboardsdl.cpp create mode 100644 indra/llwindow/llkeyboardsdl.h create mode 100644 indra/llwindow/llkeyboardwin32.cpp create mode 100644 indra/llwindow/llkeyboardwin32.h create mode 100644 indra/llwindow/llmousehandler.h create mode 100644 indra/llwindow/llwindow.cpp create mode 100644 indra/llwindow/llwindow.h create mode 100644 indra/llwindow/llwindowheadless.cpp create mode 100644 indra/llwindow/llwindowheadless.h create mode 100644 indra/llwindow/llwindowmacosx-objc.h create mode 100644 indra/llwindow/llwindowmacosx-objc.mm create mode 100644 indra/llwindow/llwindowmacosx.cpp create mode 100644 indra/llwindow/llwindowmacosx.h create mode 100644 indra/llwindow/llwindowmesaheadless.cpp create mode 100644 indra/llwindow/llwindowmesaheadless.h create mode 100644 indra/llwindow/llwindowsdl.cpp create mode 100644 indra/llwindow/llwindowsdl.h create mode 100644 indra/llwindow/llwindowwin32.cpp create mode 100644 indra/llwindow/llwindowwin32.h create mode 100644 indra/llxml/llcontrol.cpp create mode 100644 indra/llxml/llcontrol.h create mode 100644 indra/llxml/llxmlnode.cpp create mode 100644 indra/llxml/llxmlnode.h create mode 100644 indra/llxml/llxmlparser.cpp create mode 100644 indra/llxml/llxmlparser.h create mode 100644 indra/llxml/llxmltree.cpp create mode 100644 indra/llxml/llxmltree.h create mode 100644 indra/lscript/lscript_alloc.h create mode 100644 indra/lscript/lscript_byteconvert.h create mode 100644 indra/lscript/lscript_byteformat.h create mode 100644 indra/lscript/lscript_compile/indra.l create mode 100644 indra/lscript/lscript_compile/indra.y create mode 100644 indra/lscript/lscript_compile/lscript_alloc.cpp create mode 100644 indra/lscript/lscript_compile/lscript_bytecode.cpp create mode 100644 indra/lscript/lscript_compile/lscript_bytecode.h create mode 100644 indra/lscript/lscript_compile/lscript_error.cpp create mode 100644 indra/lscript/lscript_compile/lscript_error.h create mode 100644 indra/lscript/lscript_compile/lscript_heap.cpp create mode 100644 indra/lscript/lscript_compile/lscript_heap.h create mode 100644 indra/lscript/lscript_compile/lscript_resource.cpp create mode 100644 indra/lscript/lscript_compile/lscript_resource.h create mode 100644 indra/lscript/lscript_compile/lscript_scope.cpp create mode 100644 indra/lscript/lscript_compile/lscript_scope.h create mode 100644 indra/lscript/lscript_compile/lscript_tree.cpp create mode 100644 indra/lscript/lscript_compile/lscript_tree.h create mode 100644 indra/lscript/lscript_compile/lscript_typecheck.cpp create mode 100644 indra/lscript/lscript_compile/lscript_typecheck.h create mode 100644 indra/lscript/lscript_execute.h create mode 100644 indra/lscript/lscript_execute/lscript_execute.cpp create mode 100644 indra/lscript/lscript_execute/lscript_heapruntime.cpp create mode 100644 indra/lscript/lscript_execute/lscript_heapruntime.h create mode 100644 indra/lscript/lscript_execute/lscript_readlso.cpp create mode 100644 indra/lscript/lscript_execute/lscript_readlso.h create mode 100644 indra/lscript/lscript_export.h create mode 100644 indra/lscript/lscript_http.h create mode 100644 indra/lscript/lscript_library.h create mode 100644 indra/lscript/lscript_library/lscript_alloc.cpp create mode 100644 indra/lscript/lscript_library/lscript_export.cpp create mode 100644 indra/lscript/lscript_library/lscript_library.cpp create mode 100644 indra/lscript/lscript_rt_interface.h create mode 100644 indra/mac_crash_logger/mac_crash_logger.cpp create mode 100644 indra/mac_updater/AutoUpdater.nib/classes.nib create mode 100644 indra/mac_updater/AutoUpdater.nib/info.nib create mode 100644 indra/mac_updater/AutoUpdater.nib/objects.xib create mode 100644 indra/mac_updater/mac_updater.cpp create mode 100644 indra/newview/English.lproj/InfoPlist.strings create mode 100644 indra/newview/Info-SecondLife.plist create mode 100644 indra/newview/Info-SecondLifeVorbis.plist create mode 100644 indra/newview/SecondLife.nib/classes.nib create mode 100644 indra/newview/SecondLife.nib/info.nib create mode 100644 indra/newview/SecondLife.nib/objects.xib create mode 100644 indra/newview/VertexCache.h create mode 100644 indra/newview/VorbisFramework.h create mode 100644 indra/newview/app_settings/CA.pem create mode 100644 indra/newview/app_settings/anim.ini create mode 100644 indra/newview/app_settings/grass.xml create mode 100644 indra/newview/app_settings/keys.ini create mode 100644 indra/newview/app_settings/keywords.ini create mode 100644 indra/newview/app_settings/shaders/class1/avatar/avatarF.glsl create mode 100644 indra/newview/app_settings/shaders/class1/avatar/avatarSkinV.glsl create mode 100644 indra/newview/app_settings/shaders/class1/avatar/avatarV.glsl create mode 100644 indra/newview/app_settings/shaders/class1/avatar/eyeballF.glsl create mode 100644 indra/newview/app_settings/shaders/class1/avatar/eyeballV.glsl create mode 100644 indra/newview/app_settings/shaders/class1/avatar/pickAvatarF.glsl create mode 100644 indra/newview/app_settings/shaders/class1/avatar/pickAvatarV.glsl create mode 100644 indra/newview/app_settings/shaders/class1/environment/terrainF.glsl create mode 100644 indra/newview/app_settings/shaders/class1/environment/terrainV.glsl create mode 100644 indra/newview/app_settings/shaders/class1/environment/waterF.glsl create mode 100644 indra/newview/app_settings/shaders/class1/environment/waterV.glsl create mode 100644 indra/newview/app_settings/shaders/class1/interface/highlightF.glsl create mode 100644 indra/newview/app_settings/shaders/class1/interface/highlightV.glsl create mode 100644 indra/newview/app_settings/shaders/class1/lighting/lightF.glsl create mode 100644 indra/newview/app_settings/shaders/class1/lighting/lightV.glsl create mode 100644 indra/newview/app_settings/shaders/class1/objects/simpleF.glsl create mode 100644 indra/newview/app_settings/shaders/class1/objects/simpleV.glsl create mode 100644 indra/newview/app_settings/shaders/class2/avatar/eyeballV.glsl create mode 100644 indra/newview/app_settings/shaders/class2/environment/waterF.glsl create mode 100644 indra/newview/app_settings/shaders/class2/lighting/lightF.glsl create mode 100644 indra/newview/app_settings/shaders/class2/lighting/lightV.glsl create mode 100644 indra/newview/app_settings/shaders/class3/avatar/avatarV.glsl create mode 100644 indra/newview/app_settings/std_bump.ini create mode 100644 indra/newview/app_settings/trees.xml create mode 100644 indra/newview/app_settings/viewerart.xml create mode 100644 indra/newview/cursors_mac/UI_CURSOR_ARROW.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_ARROWDRAG.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_ARROWLOCKED.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_GRABLOCKED.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_NO.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_NOLOCKED.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_SIZENESW.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_SIZENS.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_SIZENWSE.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_SIZEWE.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_TOOLBUY.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_TOOLCAMERA.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_TOOLCREATE.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_TOOLFOCUS.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_TOOLGRAB.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_TOOLLAND.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_TOOLOPEN.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_TOOLPAN.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_TOOLPICKOBJECT3.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_TOOLROTATE.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_TOOLSCALE.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_TOOLSIT.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_TOOLTRANSLATE.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_TOOLZOOMIN.tif create mode 100644 indra/newview/cursors_mac/UI_CURSOR_WORKING.tif create mode 100644 indra/newview/featuretable.txt create mode 100644 indra/newview/featuretable_mac.txt create mode 100644 indra/newview/fmod_hidden_symbols.exp create mode 100644 indra/newview/fmodwrapper.cpp create mode 100644 indra/newview/gpu_table.txt create mode 100644 indra/newview/licenses-linux.txt create mode 100644 indra/newview/licenses-mac.txt create mode 100644 indra/newview/licenses-win32.txt create mode 100644 indra/newview/linux_tools/client-readme.txt create mode 100755 indra/newview/linux_tools/launch_url.sh create mode 100755 indra/newview/linux_tools/wrapper.sh create mode 100644 indra/newview/llagent.cpp create mode 100644 indra/newview/llagent.h create mode 100644 indra/newview/llagentdata.cpp create mode 100644 indra/newview/llagentdata.h create mode 100644 indra/newview/llagentpilot.cpp create mode 100644 indra/newview/llagentpilot.h create mode 100644 indra/newview/llappearance.h create mode 100644 indra/newview/llassetuploadresponders.cpp create mode 100644 indra/newview/llassetuploadresponders.h create mode 100644 indra/newview/llaudiosourcevo.cpp create mode 100644 indra/newview/llaudiosourcevo.h create mode 100644 indra/newview/llbox.cpp create mode 100644 indra/newview/llbox.h create mode 100644 indra/newview/llcallbacklist.cpp create mode 100644 indra/newview/llcallbacklist.h create mode 100644 indra/newview/llcallingcard.cpp create mode 100644 indra/newview/llcallingcard.h create mode 100644 indra/newview/llchatbar.cpp create mode 100644 indra/newview/llchatbar.h create mode 100644 indra/newview/llclassifiedinfo.cpp create mode 100644 indra/newview/llclassifiedinfo.h create mode 100644 indra/newview/llcloud.cpp create mode 100644 indra/newview/llcloud.h create mode 100644 indra/newview/llcolorswatch.cpp create mode 100644 indra/newview/llcolorswatch.h create mode 100644 indra/newview/llcompilequeue.cpp create mode 100644 indra/newview/llcompilequeue.h create mode 100644 indra/newview/llconfirmationmanager.cpp create mode 100644 indra/newview/llconfirmationmanager.h create mode 100644 indra/newview/llcurrencyuimanager.cpp create mode 100644 indra/newview/llcurrencyuimanager.h create mode 100644 indra/newview/llcylinder.cpp create mode 100644 indra/newview/llcylinder.h create mode 100644 indra/newview/lldebugmessagebox.cpp create mode 100644 indra/newview/lldebugmessagebox.h create mode 100644 indra/newview/lldebugview.cpp create mode 100644 indra/newview/lldebugview.h create mode 100644 indra/newview/lldirpicker.cpp create mode 100644 indra/newview/lldirpicker.h create mode 100644 indra/newview/lldrawable.cpp create mode 100644 indra/newview/lldrawable.h create mode 100644 indra/newview/lldrawpool.cpp create mode 100644 indra/newview/lldrawpool.h create mode 100644 indra/newview/lldrawpoolalpha.cpp create mode 100644 indra/newview/lldrawpoolalpha.h create mode 100644 indra/newview/lldrawpoolavatar.cpp create mode 100644 indra/newview/lldrawpoolavatar.h create mode 100644 indra/newview/lldrawpoolbump.cpp create mode 100644 indra/newview/lldrawpoolbump.h create mode 100644 indra/newview/lldrawpoolclouds.cpp create mode 100644 indra/newview/lldrawpoolclouds.h create mode 100644 indra/newview/lldrawpoolground.cpp create mode 100644 indra/newview/lldrawpoolground.h create mode 100644 indra/newview/lldrawpoolsimple.cpp create mode 100644 indra/newview/lldrawpoolsimple.h create mode 100644 indra/newview/lldrawpoolsky.cpp create mode 100644 indra/newview/lldrawpoolsky.h create mode 100644 indra/newview/lldrawpoolterrain.cpp create mode 100644 indra/newview/lldrawpoolterrain.h create mode 100644 indra/newview/lldrawpooltree.cpp create mode 100644 indra/newview/lldrawpooltree.h create mode 100644 indra/newview/lldrawpoolwater.cpp create mode 100644 indra/newview/lldrawpoolwater.h create mode 100644 indra/newview/lldriverparam.cpp create mode 100644 indra/newview/lldriverparam.h create mode 100644 indra/newview/lldynamictexture.cpp create mode 100644 indra/newview/lldynamictexture.h create mode 100644 indra/newview/llemote.cpp create mode 100644 indra/newview/llemote.h create mode 100644 indra/newview/lleventinfo.cpp create mode 100644 indra/newview/lleventinfo.h create mode 100644 indra/newview/lleventnotifier.cpp create mode 100644 indra/newview/lleventnotifier.h create mode 100644 indra/newview/lleventpoll.cpp create mode 100644 indra/newview/lleventpoll.h create mode 100644 indra/newview/llface.cpp create mode 100644 indra/newview/llface.h create mode 100644 indra/newview/llface.inl create mode 100644 indra/newview/llfasttimerview.cpp create mode 100644 indra/newview/llfasttimerview.h create mode 100644 indra/newview/llfeaturemanager.cpp create mode 100644 indra/newview/llfeaturemanager.h create mode 100644 indra/newview/llfilepicker.cpp create mode 100644 indra/newview/llfilepicker.h create mode 100644 indra/newview/llfirstuse.cpp create mode 100644 indra/newview/llfirstuse.h create mode 100644 indra/newview/llflexibleobject.cpp create mode 100644 indra/newview/llflexibleobject.h create mode 100644 indra/newview/llfloaterabout.cpp create mode 100644 indra/newview/llfloaterabout.h create mode 100644 indra/newview/llfloateranimpreview.cpp create mode 100644 indra/newview/llfloateranimpreview.h create mode 100644 indra/newview/llfloaterauction.cpp create mode 100644 indra/newview/llfloaterauction.h create mode 100644 indra/newview/llfloateravatarpicker.cpp create mode 100644 indra/newview/llfloateravatarpicker.h create mode 100644 indra/newview/llfloateravatartextures.cpp create mode 100644 indra/newview/llfloateravatartextures.h create mode 100644 indra/newview/llfloaterbuildoptions.cpp create mode 100644 indra/newview/llfloaterbuildoptions.h create mode 100644 indra/newview/llfloaterbump.cpp create mode 100644 indra/newview/llfloaterbump.h create mode 100644 indra/newview/llfloaterbuy.cpp create mode 100644 indra/newview/llfloaterbuy.h create mode 100644 indra/newview/llfloaterbuycontents.cpp create mode 100644 indra/newview/llfloaterbuycontents.h create mode 100644 indra/newview/llfloaterbuycurrency.cpp create mode 100644 indra/newview/llfloaterbuycurrency.h create mode 100644 indra/newview/llfloaterbuyland.cpp create mode 100644 indra/newview/llfloaterbuyland.h create mode 100644 indra/newview/llfloaterchat.cpp create mode 100644 indra/newview/llfloaterchat.h create mode 100644 indra/newview/llfloatercolorpicker.cpp create mode 100644 indra/newview/llfloatercolorpicker.h create mode 100644 indra/newview/llfloaterfriends.cpp create mode 100644 indra/newview/llfloaterfriends.h create mode 100644 indra/newview/llfloatergesture.cpp create mode 100644 indra/newview/llfloatergesture.h create mode 100644 indra/newview/llfloatergodtools.cpp create mode 100644 indra/newview/llfloatergodtools.h create mode 100644 indra/newview/llfloatergroupinvite.cpp create mode 100644 indra/newview/llfloatergroupinvite.h create mode 100644 indra/newview/llfloatergroups.cpp create mode 100644 indra/newview/llfloatergroups.h create mode 100644 indra/newview/llfloaterimagepreview.cpp create mode 100644 indra/newview/llfloaterimagepreview.h create mode 100644 indra/newview/llfloaterland.cpp create mode 100644 indra/newview/llfloaterland.h create mode 100644 indra/newview/llfloaterlandholdings.cpp create mode 100644 indra/newview/llfloaterlandholdings.h create mode 100644 indra/newview/llfloatermap.cpp create mode 100644 indra/newview/llfloatermap.h create mode 100644 indra/newview/llfloaternamedesc.cpp create mode 100644 indra/newview/llfloaternamedesc.h create mode 100644 indra/newview/llfloateropenobject.cpp create mode 100644 indra/newview/llfloateropenobject.h create mode 100644 indra/newview/llfloaterpostcard.cpp create mode 100644 indra/newview/llfloaterpostcard.h create mode 100644 indra/newview/llfloaterpreference.cpp create mode 100644 indra/newview/llfloaterpreference.h create mode 100644 indra/newview/llfloaterproperties.cpp create mode 100644 indra/newview/llfloaterproperties.h create mode 100644 indra/newview/llfloaterregioninfo.cpp create mode 100644 indra/newview/llfloaterregioninfo.h create mode 100644 indra/newview/llfloaterreporter.cpp create mode 100644 indra/newview/llfloaterreporter.h create mode 100644 indra/newview/llfloaterscriptdebug.cpp create mode 100644 indra/newview/llfloaterscriptdebug.h create mode 100755 indra/newview/llfloatersellland.cpp create mode 100755 indra/newview/llfloatersellland.h create mode 100644 indra/newview/llfloatersnapshot.cpp create mode 100644 indra/newview/llfloatersnapshot.h create mode 100644 indra/newview/llfloatertelehub.cpp create mode 100644 indra/newview/llfloatertelehub.h create mode 100644 indra/newview/llfloatertools.cpp create mode 100644 indra/newview/llfloatertools.h create mode 100644 indra/newview/llfloatertopobjects.cpp create mode 100644 indra/newview/llfloatertopobjects.h create mode 100644 indra/newview/llfloatertos.cpp create mode 100644 indra/newview/llfloatertos.h create mode 100644 indra/newview/llfloaterworldmap.cpp create mode 100644 indra/newview/llfloaterworldmap.h create mode 100644 indra/newview/llfolderview.cpp create mode 100644 indra/newview/llfolderview.h create mode 100644 indra/newview/llfollowcam.cpp create mode 100644 indra/newview/llfollowcam.h create mode 100644 indra/newview/llgesturemgr.cpp create mode 100644 indra/newview/llgesturemgr.h create mode 100644 indra/newview/llglsandbox.cpp create mode 100644 indra/newview/llgroupmgr.cpp create mode 100644 indra/newview/llgroupmgr.h create mode 100644 indra/newview/llhudeffect.cpp create mode 100644 indra/newview/llhudeffect.h create mode 100644 indra/newview/llhudeffectbeam.cpp create mode 100644 indra/newview/llhudeffectbeam.h create mode 100644 indra/newview/llhudeffectlookat.cpp create mode 100644 indra/newview/llhudeffectlookat.h create mode 100644 indra/newview/llhudeffectpointat.cpp create mode 100644 indra/newview/llhudeffectpointat.h create mode 100644 indra/newview/llhudeffecttrail.cpp create mode 100644 indra/newview/llhudeffecttrail.h create mode 100644 indra/newview/llhudicon.cpp create mode 100644 indra/newview/llhudicon.h create mode 100644 indra/newview/llhudmanager.cpp create mode 100644 indra/newview/llhudmanager.h create mode 100644 indra/newview/llhudobject.cpp create mode 100644 indra/newview/llhudobject.h create mode 100644 indra/newview/llhudrender.cpp create mode 100644 indra/newview/llhudrender.h create mode 100644 indra/newview/llhudtext.cpp create mode 100644 indra/newview/llhudtext.h create mode 100644 indra/newview/llhudview.cpp create mode 100644 indra/newview/llhudview.h create mode 100644 indra/newview/llimpanel.cpp create mode 100644 indra/newview/llimpanel.h create mode 100644 indra/newview/llimview.cpp create mode 100644 indra/newview/llimview.h create mode 100755 indra/newview/llinventorybridge.cpp create mode 100755 indra/newview/llinventorybridge.h create mode 100644 indra/newview/llinventoryclipboard.cpp create mode 100644 indra/newview/llinventoryclipboard.h create mode 100644 indra/newview/llinventorymodel.cpp create mode 100644 indra/newview/llinventorymodel.h create mode 100644 indra/newview/lljoystickbutton.cpp create mode 100644 indra/newview/lljoystickbutton.h create mode 100644 indra/newview/lllandmarklist.cpp create mode 100644 indra/newview/lllandmarklist.h create mode 100644 indra/newview/lllightconstants.h create mode 100644 indra/newview/lllogchat.cpp create mode 100644 indra/newview/lllogchat.h create mode 100644 indra/newview/llmanip.cpp create mode 100644 indra/newview/llmanip.h create mode 100644 indra/newview/llmaniprotate.cpp create mode 100644 indra/newview/llmaniprotate.h create mode 100644 indra/newview/llmanipscale.cpp create mode 100644 indra/newview/llmanipscale.h create mode 100644 indra/newview/llmaniptranslate.cpp create mode 100644 indra/newview/llmaniptranslate.h create mode 100644 indra/newview/llmemoryview.cpp create mode 100644 indra/newview/llmemoryview.h create mode 100644 indra/newview/llmenucommands.cpp create mode 100644 indra/newview/llmenucommands.h create mode 100644 indra/newview/llmorphview.cpp create mode 100644 indra/newview/llmorphview.h create mode 100644 indra/newview/llmoveview.cpp create mode 100644 indra/newview/llmoveview.h create mode 100644 indra/newview/llmutelist.cpp create mode 100644 indra/newview/llmutelist.h create mode 100644 indra/newview/llnamebox.cpp create mode 100644 indra/newview/llnamebox.h create mode 100644 indra/newview/llnameeditor.cpp create mode 100644 indra/newview/llnameeditor.h create mode 100644 indra/newview/llnamelistctrl.cpp create mode 100644 indra/newview/llnamelistctrl.h create mode 100644 indra/newview/llnetmap.cpp create mode 100644 indra/newview/llnetmap.h create mode 100644 indra/newview/lloverlaybar.cpp create mode 100644 indra/newview/lloverlaybar.h create mode 100644 indra/newview/llpanelavatar.cpp create mode 100644 indra/newview/llpanelavatar.h create mode 100644 indra/newview/llpanelclassified.cpp create mode 100644 indra/newview/llpanelclassified.h create mode 100644 indra/newview/llpanelcontents.cpp create mode 100644 indra/newview/llpanelcontents.h create mode 100644 indra/newview/llpanelface.cpp create mode 100644 indra/newview/llpanelface.h create mode 100644 indra/newview/llpanelgroup.cpp create mode 100644 indra/newview/llpanelgroup.h create mode 100644 indra/newview/llpanelgroupgeneral.cpp create mode 100644 indra/newview/llpanelgroupgeneral.h create mode 100644 indra/newview/llpanelgroupinvite.cpp create mode 100644 indra/newview/llpanelgroupinvite.h create mode 100644 indra/newview/llpanelgrouplandmoney.cpp create mode 100644 indra/newview/llpanelgrouplandmoney.h create mode 100644 indra/newview/llpanelgroupnotices.cpp create mode 100644 indra/newview/llpanelgroupnotices.h create mode 100644 indra/newview/llpanelgrouproles.cpp create mode 100644 indra/newview/llpanelgrouproles.h create mode 100644 indra/newview/llpanelland.cpp create mode 100644 indra/newview/llpanelland.h create mode 100644 indra/newview/llpanellogin.cpp create mode 100644 indra/newview/llpanellogin.h create mode 100644 indra/newview/llpanelobject.cpp create mode 100644 indra/newview/llpanelobject.h create mode 100644 indra/newview/llpanelpermissions.cpp create mode 100644 indra/newview/llpanelpermissions.h create mode 100644 indra/newview/llpanelpick.cpp create mode 100644 indra/newview/llpanelpick.h create mode 100644 indra/newview/llpanelplace.cpp create mode 100644 indra/newview/llpanelplace.h create mode 100644 indra/newview/llpanelvolume.cpp create mode 100644 indra/newview/llpanelvolume.h create mode 100644 indra/newview/llpatchvertexarray.cpp create mode 100644 indra/newview/llpatchvertexarray.h create mode 100644 indra/newview/llpolymesh.cpp create mode 100644 indra/newview/llpolymesh.h create mode 100644 indra/newview/llpolymorph.cpp create mode 100644 indra/newview/llpolymorph.h create mode 100644 indra/newview/llpreview.cpp create mode 100644 indra/newview/llpreview.h create mode 100644 indra/newview/llpreviewanim.cpp create mode 100644 indra/newview/llpreviewanim.h create mode 100644 indra/newview/llpreviewgesture.cpp create mode 100644 indra/newview/llpreviewgesture.h create mode 100644 indra/newview/llpreviewnotecard.cpp create mode 100644 indra/newview/llpreviewnotecard.h create mode 100644 indra/newview/llpreviewscript.cpp create mode 100644 indra/newview/llpreviewscript.h create mode 100644 indra/newview/llpreviewsound.cpp create mode 100644 indra/newview/llpreviewsound.h create mode 100644 indra/newview/llpreviewtexture.cpp create mode 100644 indra/newview/llpreviewtexture.h create mode 100644 indra/newview/llprogressview.cpp create mode 100644 indra/newview/llprogressview.h create mode 100644 indra/newview/llregionposition.cpp create mode 100644 indra/newview/llregionposition.h create mode 100644 indra/newview/llsavedsettingsglue.cpp create mode 100644 indra/newview/llsavedsettingsglue.h create mode 100644 indra/newview/llselectmgr.cpp create mode 100644 indra/newview/llselectmgr.h create mode 100644 indra/newview/llsky.cpp create mode 100644 indra/newview/llsky.h create mode 100644 indra/newview/llspatialpartition.cpp create mode 100644 indra/newview/llspatialpartition.h create mode 100644 indra/newview/llsprite.cpp create mode 100644 indra/newview/llsprite.h create mode 100644 indra/newview/llstartup.cpp create mode 100644 indra/newview/llstartup.h create mode 100644 indra/newview/llstatusbar.cpp create mode 100644 indra/newview/llstatusbar.h create mode 100644 indra/newview/llsurface.cpp create mode 100644 indra/newview/llsurface.h create mode 100644 indra/newview/llsurfacepatch.cpp create mode 100644 indra/newview/llsurfacepatch.h create mode 100644 indra/newview/lltable.h create mode 100644 indra/newview/lltexlayer.cpp create mode 100644 indra/newview/lltexlayer.h create mode 100644 indra/newview/lltexturectrl.cpp create mode 100644 indra/newview/lltexturectrl.h create mode 100644 indra/newview/lltexturefetch.cpp create mode 100644 indra/newview/lltexturefetch.h create mode 100644 indra/newview/lltextureview.cpp create mode 100644 indra/newview/lltextureview.h create mode 100644 indra/newview/lltool.cpp create mode 100644 indra/newview/lltool.h create mode 100644 indra/newview/lltoolbar.cpp create mode 100644 indra/newview/lltoolbar.h create mode 100644 indra/newview/lltoolbrush.cpp create mode 100644 indra/newview/lltoolbrush.h create mode 100644 indra/newview/lltoolcomp.cpp create mode 100644 indra/newview/lltoolcomp.h create mode 100644 indra/newview/lltooldraganddrop.cpp create mode 100644 indra/newview/lltooldraganddrop.h create mode 100644 indra/newview/lltoolface.cpp create mode 100644 indra/newview/lltoolface.h create mode 100644 indra/newview/lltoolfocus.cpp create mode 100644 indra/newview/lltoolfocus.h create mode 100644 indra/newview/lltoolgrab.cpp create mode 100644 indra/newview/lltoolgrab.h create mode 100644 indra/newview/lltoolgun.cpp create mode 100644 indra/newview/lltoolgun.h create mode 100644 indra/newview/lltoolindividual.cpp create mode 100644 indra/newview/lltoolindividual.h create mode 100644 indra/newview/lltoolmgr.cpp create mode 100644 indra/newview/lltoolmgr.h create mode 100644 indra/newview/lltoolmorph.cpp create mode 100644 indra/newview/lltoolmorph.h create mode 100644 indra/newview/lltoolobjpicker.cpp create mode 100644 indra/newview/lltoolobjpicker.h create mode 100644 indra/newview/lltoolpie.cpp create mode 100644 indra/newview/lltoolpie.h create mode 100755 indra/newview/lltoolpipette.cpp create mode 100755 indra/newview/lltoolpipette.h create mode 100644 indra/newview/lltoolplacer.cpp create mode 100644 indra/newview/lltoolplacer.h create mode 100644 indra/newview/lltoolselect.cpp create mode 100644 indra/newview/lltoolselect.h create mode 100644 indra/newview/lltoolselectland.cpp create mode 100644 indra/newview/lltoolselectland.h create mode 100644 indra/newview/lltoolselectrect.cpp create mode 100644 indra/newview/lltoolselectrect.h create mode 100644 indra/newview/lltoolview.cpp create mode 100644 indra/newview/lltoolview.h create mode 100644 indra/newview/lltracker.cpp create mode 100644 indra/newview/lltracker.h create mode 100644 indra/newview/lluiconstants.h create mode 100644 indra/newview/lluploaddialog.cpp create mode 100644 indra/newview/lluploaddialog.h create mode 100644 indra/newview/llurl.cpp create mode 100644 indra/newview/llurl.h create mode 100644 indra/newview/llurlwhitelist.cpp create mode 100644 indra/newview/llurlwhitelist.h create mode 100644 indra/newview/llviewchildren.cpp create mode 100644 indra/newview/llviewchildren.h create mode 100644 indra/newview/llviewerassetstorage.cpp create mode 100644 indra/newview/llviewerassetstorage.h create mode 100644 indra/newview/llviewercamera.cpp create mode 100644 indra/newview/llviewercamera.h create mode 100644 indra/newview/llviewercontrol.cpp create mode 100644 indra/newview/llviewercontrol.h create mode 100644 indra/newview/llviewerdisplay.cpp create mode 100644 indra/newview/llviewerdisplay.h create mode 100644 indra/newview/llviewergesture.cpp create mode 100644 indra/newview/llviewergesture.h create mode 100644 indra/newview/llviewerinventory.cpp create mode 100644 indra/newview/llviewerinventory.h create mode 100644 indra/newview/llviewerjoint.cpp create mode 100644 indra/newview/llviewerjoint.h create mode 100644 indra/newview/llviewerjointattachment.cpp create mode 100644 indra/newview/llviewerjointattachment.h create mode 100644 indra/newview/llviewerjointmesh.cpp create mode 100644 indra/newview/llviewerjointmesh.h create mode 100644 indra/newview/llviewerkeyboard.cpp create mode 100644 indra/newview/llviewerkeyboard.h create mode 100644 indra/newview/llviewerlayer.cpp create mode 100644 indra/newview/llviewerlayer.h create mode 100644 indra/newview/llviewermenu.cpp create mode 100644 indra/newview/llviewermenu.h create mode 100644 indra/newview/llviewermessage.cpp create mode 100644 indra/newview/llviewermessage.h create mode 100644 indra/newview/llviewernetwork.cpp create mode 100644 indra/newview/llviewernetwork.h create mode 100644 indra/newview/llviewerobject.cpp create mode 100644 indra/newview/llviewerobject.h create mode 100644 indra/newview/llviewerobjectlist.cpp create mode 100644 indra/newview/llviewerobjectlist.h create mode 100644 indra/newview/llviewerparcelmgr.cpp create mode 100644 indra/newview/llviewerparcelmgr.h create mode 100644 indra/newview/llviewerparceloverlay.cpp create mode 100644 indra/newview/llviewerparceloverlay.h create mode 100644 indra/newview/llviewerpartsim.cpp create mode 100644 indra/newview/llviewerpartsim.h create mode 100644 indra/newview/llviewerpartsource.cpp create mode 100644 indra/newview/llviewerpartsource.h create mode 100644 indra/newview/llviewerprecompiledheaders.cpp create mode 100644 indra/newview/llviewerprecompiledheaders.h create mode 100644 indra/newview/llviewerregion.cpp create mode 100644 indra/newview/llviewerregion.h create mode 100644 indra/newview/llviewerstats.cpp create mode 100644 indra/newview/llviewerstats.h create mode 100644 indra/newview/llviewertexteditor.cpp create mode 100644 indra/newview/llviewertexteditor.h create mode 100644 indra/newview/llviewertextureanim.cpp create mode 100644 indra/newview/llviewertextureanim.h create mode 100644 indra/newview/llviewerthrottle.cpp create mode 100644 indra/newview/llviewerthrottle.h create mode 100644 indra/newview/llviewervisualparam.cpp create mode 100644 indra/newview/llviewervisualparam.h create mode 100644 indra/newview/llviewerwindow.cpp create mode 100644 indra/newview/llviewerwindow.h create mode 100644 indra/newview/llvlcomposition.cpp create mode 100644 indra/newview/llvlcomposition.h create mode 100644 indra/newview/llvlmanager.cpp create mode 100644 indra/newview/llvlmanager.h create mode 100644 indra/newview/llvoavatar.cpp create mode 100644 indra/newview/llvoavatar.h create mode 100644 indra/newview/llvocache.cpp create mode 100644 indra/newview/llvocache.h create mode 100644 indra/newview/llvoclouds.cpp create mode 100644 indra/newview/llvoclouds.h create mode 100644 indra/newview/llvograss.cpp create mode 100644 indra/newview/llvograss.h create mode 100644 indra/newview/llvoground.cpp create mode 100644 indra/newview/llvoground.h create mode 100644 indra/newview/llvoinventorylistener.cpp create mode 100644 indra/newview/llvoinventorylistener.h create mode 100644 indra/newview/llvopartgroup.cpp create mode 100644 indra/newview/llvopartgroup.h create mode 100644 indra/newview/llvosky.cpp create mode 100644 indra/newview/llvosky.h create mode 100644 indra/newview/llvosurfacepatch.cpp create mode 100644 indra/newview/llvosurfacepatch.h create mode 100644 indra/newview/llvotextbubble.cpp create mode 100644 indra/newview/llvotextbubble.h create mode 100644 indra/newview/llvotree.cpp create mode 100644 indra/newview/llvotree.h create mode 100644 indra/newview/llvotreenew.h create mode 100644 indra/newview/llvovolume.cpp create mode 100644 indra/newview/llvovolume.h create mode 100644 indra/newview/llvowater.cpp create mode 100644 indra/newview/llvowater.h create mode 100644 indra/newview/llwearable.cpp create mode 100644 indra/newview/llwearable.h create mode 100644 indra/newview/llwearablelist.cpp create mode 100644 indra/newview/llwearablelist.h create mode 100644 indra/newview/llweb.cpp create mode 100644 indra/newview/llweb.h create mode 100644 indra/newview/llwind.cpp create mode 100644 indra/newview/llwind.h create mode 100644 indra/newview/llwindebug.cpp create mode 100644 indra/newview/llwindebug.h create mode 100644 indra/newview/llworld.cpp create mode 100644 indra/newview/llworld.h create mode 100644 indra/newview/llworldmap.cpp create mode 100644 indra/newview/llworldmap.h create mode 100644 indra/newview/llworldmapview.cpp create mode 100644 indra/newview/llworldmapview.h create mode 100644 indra/newview/llxmlrpctransaction.cpp create mode 100644 indra/newview/llxmlrpctransaction.h create mode 100644 indra/newview/macmain.h create mode 100644 indra/newview/macutil_Prefix.h create mode 100644 indra/newview/macview.r create mode 100644 indra/newview/macview_Prefix.h create mode 100644 indra/newview/noise.cpp create mode 100644 indra/newview/noise.h create mode 100644 indra/newview/pipeline.cpp create mode 100644 indra/newview/pipeline.h create mode 100644 indra/newview/res-sdl/arrow.BMP create mode 100644 indra/newview/res-sdl/arrowcop.BMP create mode 100644 indra/newview/res-sdl/arrowcopmulti.BMP create mode 100644 indra/newview/res-sdl/arrowdrag.BMP create mode 100644 indra/newview/res-sdl/circleandline.BMP create mode 100644 indra/newview/res-sdl/cross.BMP create mode 100644 indra/newview/res-sdl/hand.BMP create mode 100644 indra/newview/res-sdl/ibeam.BMP create mode 100644 indra/newview/res-sdl/ll_icon.BMP create mode 100644 indra/newview/res-sdl/llarrow.BMP create mode 100644 indra/newview/res-sdl/llarrowdrag.BMP create mode 100644 indra/newview/res-sdl/llarrowdragmulti.BMP create mode 100644 indra/newview/res-sdl/llarrowlocked.BMP create mode 100644 indra/newview/res-sdl/llgrablocked.BMP create mode 100644 indra/newview/res-sdl/llno.BMP create mode 100644 indra/newview/res-sdl/llnolocked.BMP create mode 100644 indra/newview/res-sdl/lltoolcamera.BMP create mode 100644 indra/newview/res-sdl/lltoolcreate.BMP create mode 100644 indra/newview/res-sdl/lltoolfocus.BMP create mode 100644 indra/newview/res-sdl/lltoolgrab.BMP create mode 100644 indra/newview/res-sdl/lltoolland.BMP create mode 100644 indra/newview/res-sdl/lltoolpan.BMP create mode 100644 indra/newview/res-sdl/lltoolpipette.BMP create mode 100644 indra/newview/res-sdl/lltoolrotate.BMP create mode 100644 indra/newview/res-sdl/lltoolscale.BMP create mode 100644 indra/newview/res-sdl/lltooltranslate.BMP create mode 100644 indra/newview/res-sdl/lltoolzoomin.BMP create mode 100644 indra/newview/res-sdl/lltoolzoomout.BMP create mode 100644 indra/newview/res-sdl/sizenesw.BMP create mode 100644 indra/newview/res-sdl/sizens.BMP create mode 100644 indra/newview/res-sdl/sizenwse.BMP create mode 100644 indra/newview/res-sdl/sizewe.BMP create mode 100644 indra/newview/res-sdl/toolbuy.BMP create mode 100644 indra/newview/res-sdl/toolopen.BMP create mode 100644 indra/newview/res-sdl/toolpickobject.BMP create mode 100644 indra/newview/res-sdl/toolpickobject2.BMP create mode 100644 indra/newview/res-sdl/toolpickobject3.BMP create mode 100644 indra/newview/res-sdl/toolsit.BMP create mode 100644 indra/newview/res-sdl/wait.BMP create mode 100644 indra/newview/res-sdl/working.BMP create mode 100644 indra/newview/res/arrow.cur create mode 100644 indra/newview/res/arrowcop.cur create mode 100644 indra/newview/res/arrowcopmulti.cur create mode 100644 indra/newview/res/arrowdrag.cur create mode 100644 indra/newview/res/bitmap2.bmp create mode 100644 indra/newview/res/circleandline.cur create mode 100644 indra/newview/res/install_icon.BMP create mode 100644 indra/newview/res/ll_icon.BMP create mode 100644 indra/newview/res/ll_icon.ico create mode 100644 indra/newview/res/llarrow.cur create mode 100644 indra/newview/res/llarrowdrag.cur create mode 100644 indra/newview/res/llarrowdragmulti.cur create mode 100644 indra/newview/res/llarrowlocked.cur create mode 100644 indra/newview/res/llgrablocked.cur create mode 100644 indra/newview/res/llno.cur create mode 100644 indra/newview/res/llnolocked.cur create mode 100644 indra/newview/res/lltoolcamera.cur create mode 100644 indra/newview/res/lltoolcreate.cur create mode 100644 indra/newview/res/lltoolfocus.cur create mode 100644 indra/newview/res/lltoolgrab.cur create mode 100644 indra/newview/res/lltoolland.cur create mode 100644 indra/newview/res/lltoolpan.cur create mode 100644 indra/newview/res/lltoolpipette.cur create mode 100644 indra/newview/res/lltoolrotate.cur create mode 100644 indra/newview/res/lltoolscale.cur create mode 100644 indra/newview/res/lltooltranslate.cur create mode 100644 indra/newview/res/lltoolzoomin.cur create mode 100644 indra/newview/res/lltoolzoomout.cur create mode 100644 indra/newview/res/loginbackground.bmp create mode 100644 indra/newview/res/resource.h create mode 100644 indra/newview/res/toolbuy.cur create mode 100644 indra/newview/res/toolopen.cur create mode 100644 indra/newview/res/toolpickobject.cur create mode 100644 indra/newview/res/toolpickobject2.cur create mode 100644 indra/newview/res/toolpickobject3.cur create mode 100644 indra/newview/res/toolpipette.cur create mode 100644 indra/newview/res/toolsit.cur create mode 100644 indra/newview/res/uninstall_icon.BMP create mode 100644 indra/newview/secondlife.icns create mode 100644 indra/newview/skins/paths.xml create mode 100644 indra/test/io.cpp create mode 100644 indra/test/llhttpclient_tut.cpp create mode 100644 indra/test/llhttpnode_tut.cpp create mode 100644 indra/test/lliohttpserver_tut.cpp create mode 100644 indra/test/llpipeutil.cpp create mode 100644 indra/test/llpipeutil.h create mode 100644 indra/test/llsd_new_tut.cpp create mode 100644 indra/test/lltut.cpp create mode 100644 indra/test/lltut.h create mode 100644 indra/test/lluserrelations_tut.cpp create mode 100644 indra/test/test.cpp create mode 100644 indra/win_crash_logger/StdAfx.cpp create mode 100644 indra/win_crash_logger/StdAfx.h create mode 100644 indra/win_crash_logger/ll_icon.ico create mode 100644 indra/win_crash_logger/resource.h create mode 100644 indra/win_crash_logger/win_crash_logger.cpp create mode 100644 indra/win_crash_logger/win_crash_logger.h create mode 100644 indra/win_crash_logger/win_crash_logger.ico create mode 100644 indra/win_crash_logger/win_crash_logger.rc create mode 100644 indra/win_updater/updater.cpp create mode 100644 scripts/messages/message_template.msg create mode 100755 scripts/update_version_files.py diff --git a/indra/lib/python/indra/__init__.py b/indra/lib/python/indra/__init__.py new file mode 100644 index 0000000000..8f5987696c --- /dev/null +++ b/indra/lib/python/indra/__init__.py @@ -0,0 +1,6 @@ +"""@file __init__.py +@brief + +Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. +$License$ +""" diff --git a/indra/linux_crash_logger/linux_crash_logger.cpp b/indra/linux_crash_logger/linux_crash_logger.cpp new file mode 100644 index 0000000000..a8acced4cf --- /dev/null +++ b/indra/linux_crash_logger/linux_crash_logger.cpp @@ -0,0 +1,553 @@ +/** + * @file linux_crash_logger.cpp + * @brief Linux crash logger implementation + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include +#include +#include +#include +#include +#include + +#include + +#if LL_GTK +# include "gtk/gtk.h" +#endif // LL_GTK + +#include "indra_constants.h" // CRASH_BEHAVIOR_ASK +#include "llerror.h" +#include "lltimer.h" +#include "lldir.h" + +#include "llstring.h" + + +// These need to be localized. +static const char dialog_text[] = +"Second Life appears to have crashed.\n" +"This crash reporter collects information about your computer's hardware, operating system, and some Second Life logs, which are used for debugging purposes only.\n" +"Sending crash reports is the best way to help us improve the quality of Second Life.\n" +"If you continue to experience this problem, please try one of the following:\n" +"- Contact support by email at support@lindenlab.com\n" +"- If you can log-in, please contact Live Help by using menu Help > Live Help.\n" +"- Search the Second Life Knowledge Base at http://secondlife.com/knowledgebase/\n" +"\n" +"Send crash report?"; + +static const char dialog_title[] = +"Second Life Crash Logger"; + + +class LLFileEncoder +{ +public: + LLFileEncoder(const char *formname, const char *filename, bool isCrashLog = false); + + BOOL isValid() const { return mIsValid; } + LLString encodeURL(const S32 max_length = 0); +public: + BOOL mIsValid; + LLString mFilename; + LLString mFormname; + LLString mBuf; +}; + +LLString encode_string(const char *formname, const LLString &str); + +LLString gServerResponse; +BOOL gSendReport = FALSE; +LLString gUserserver; +LLString gUserText; +BOOL gCrashInPreviousExec = FALSE; +time_t gLaunchTime; + +static size_t curl_download_callback(void *data, size_t size, size_t nmemb, + void *user_data) +{ + S32 bytes = size * nmemb; + char *cdata = (char *) data; + for (int i =0; i < bytes; i += 1) + { + gServerResponse += (cdata[i]); + } + return bytes; +} + +#if LL_GTK +static void response_callback (GtkDialog *dialog, + gint arg1, + gpointer user_data) +{ + gint *response = (gint*)user_data; + *response = arg1; + gtk_widget_destroy(GTK_WIDGET(dialog)); + gtk_main_quit(); +} +#endif // LL_GTK + +static BOOL do_ask_dialog(void) +{ +#if LL_GTK + gtk_disable_setlocale(); + if (!gtk_init_check(NULL, NULL)) { + llinfos << "Could not initialize GTK for 'ask to send crash report' dialog; not sending report." << llendl; + return FALSE; + } + + GtkWidget *win = NULL; + GtkDialogFlags flags = GTK_DIALOG_MODAL; + GtkMessageType messagetype = GTK_MESSAGE_QUESTION; + GtkButtonsType buttons = GTK_BUTTONS_YES_NO; + gint response = GTK_RESPONSE_NONE; + + win = gtk_message_dialog_new(NULL, + flags, messagetype, buttons, + dialog_text); + gtk_window_set_type_hint(GTK_WINDOW(win), + GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_title(GTK_WINDOW(win), dialog_title); + g_signal_connect (win, + "response", + G_CALLBACK (response_callback), + &response); + gtk_widget_show_all (win); + gtk_main(); + + return (GTK_RESPONSE_OK == response || + GTK_RESPONSE_YES == response || + GTK_RESPONSE_APPLY == response); +#else + return FALSE; +#endif // LL_GTK +} + + +int main(int argc, char **argv) +{ + const S32 BT_MAX_SIZE = 100000; // Maximum size to transmit of the backtrace file + const S32 SL_MAX_SIZE = 100000; // Maximum size of the Second Life log file. + int i; + S32 crash_behavior = CRASH_BEHAVIOR_ALWAYS_SEND; + + time(&gLaunchTime); + + llinfos << "Starting Second Life Viewer Crash Reporter" << llendl; + + for(i=1; iinitAppDirs("SecondLife"); + + // Lots of silly variable, replicated for each log file. + LLString db_file_name; + LLString sl_file_name; + LLString bt_file_name; // stack_trace.log file + LLString st_file_name; // stats.log file + LLString si_file_name; // settings.xml file + + LLFileEncoder *db_filep = NULL; + LLFileEncoder *sl_filep = NULL; + LLFileEncoder *st_filep = NULL; + LLFileEncoder *bt_filep = NULL; + LLFileEncoder *si_filep = NULL; + + /////////////////////////////////// + // + // We do the parsing for the debug_info file first, as that will + // give us the location of the SecondLife.log file. + // + + // Figure out the filename of the debug log + db_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"debug_info.log").c_str(); + db_filep = new LLFileEncoder("DB", db_file_name.c_str()); + + // Get the filename of the SecondLife.log file + //FIXME tofu - get right MAX_PATH +#define MAX_PATH PATH_MAX + char tmp_sl_name[MAX_PATH]; + tmp_sl_name[0] = '\0'; + char tmp_space[256]; + tmp_space[0] = '\0'; + + // Look for it in the debug_info.log file + if (db_filep->isValid()) + { + // This was originally scanning for "SL Log: %[^\r\n]", which happily skipped to the next line + // on debug logs (which don't have anything after "SL Log:" and tried to open a nonsensical filename. + sscanf(db_filep->mBuf.c_str(), "SL Log:%[ ]%[^\r\n]", tmp_space, tmp_sl_name); + } + else + { + delete db_filep; + db_filep = NULL; + } + + // If we actually have a legitimate file name, use it. + if (gCrashInPreviousExec) + { + // If we froze, the crash log this time around isn't useful. + // Use the old one. + sl_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.old"); + } + else if (tmp_sl_name[0]) + { + sl_file_name = tmp_sl_name; + llinfos << "Using log file from debug log: " << sl_file_name << llendl; + } + else + { + // Figure out the filename of the second life log + sl_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.log").c_str(); + } + + // Now we get the SecondLife.log file if it's there, and recent enough... + sl_filep = new LLFileEncoder("SL", sl_file_name.c_str()); + if (!sl_filep->isValid()) + { + delete sl_filep; + sl_filep = NULL; + } + + st_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"stats.log").c_str(); + st_filep = new LLFileEncoder("ST", st_file_name.c_str()); + if (!st_filep->isValid()) + { + delete st_filep; + st_filep = NULL; + } + + si_file_name = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,"settings.xml").c_str(); + si_filep = new LLFileEncoder("SI", si_file_name.c_str()); + if (!si_filep->isValid()) + { + delete si_filep; + si_filep = NULL; + } + + // encode this as if it were a 'Dr Watson' plain-text backtrace + bt_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"stack_trace.log").c_str(); + bt_filep = new LLFileEncoder("DW", bt_file_name.c_str()); + if (!bt_filep->isValid()) + { + delete bt_filep; + bt_filep = NULL; + } + + LLString post_data; + LLString tmp_url_buf; + + // Append the userserver + tmp_url_buf = encode_string("USER", gUserserver); + post_data += tmp_url_buf; + llinfos << "PostData:" << post_data << llendl; + + if (gCrashInPreviousExec) + { + post_data.append("&"); + tmp_url_buf = encode_string("EF", "Y"); + post_data += tmp_url_buf; + } + + if (db_filep) + { + post_data.append("&"); + tmp_url_buf = db_filep->encodeURL(); + post_data += tmp_url_buf; + llinfos << "Sending DB log file" << llendl; + } + else + { + llinfos << "Not sending DB log file" << llendl; + } + + if (sl_filep) + { + post_data.append("&"); + tmp_url_buf = sl_filep->encodeURL(SL_MAX_SIZE); + post_data += tmp_url_buf; + llinfos << "Sending SL log file" << llendl; + } + else + { + llinfos << "Not sending SL log file" << llendl; + } + + if (st_filep) + { + post_data.append("&"); + tmp_url_buf = st_filep->encodeURL(SL_MAX_SIZE); + post_data += tmp_url_buf; + llinfos << "Sending stats log file" << llendl; + } + else + { + llinfos << "Not sending stats log file" << llendl; + } + + if (bt_filep) + { + post_data.append("&"); + tmp_url_buf = bt_filep->encodeURL(BT_MAX_SIZE); + post_data += tmp_url_buf; + llinfos << "Sending crash log file" << llendl; + } + else + { + llinfos << "Not sending crash log file" << llendl; + } + + if (si_filep) + { + post_data.append("&"); + tmp_url_buf = si_filep->encodeURL(); + post_data += tmp_url_buf; + llinfos << "Sending settings log file" << llendl; + } + else + { + llinfos << "Not sending settings.xml file" << llendl; + } + + if (gUserText.size()) + { + post_data.append("&"); + tmp_url_buf = encode_string("UN", gUserText); + post_data += tmp_url_buf; + } + + delete db_filep; + db_filep = NULL; + delete sl_filep; + sl_filep = NULL; + delete bt_filep; + bt_filep = NULL; + + // Debugging spam +#if 0 + printf("Crash report post data:\n--------\n"); + printf("%s", post_data.getString()); + printf("\n--------\n"); +#endif + + // Send the report. Yes, it's this easy. + { + CURL *curl = curl_easy_init(); + + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curl_download_callback); + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data.c_str()); + curl_easy_setopt(curl, CURLOPT_URL, "http://secondlife.com/cgi-bin/viewer_crash_reporter2"); + + llinfos << "Connecting to crash report server" << llendl; + CURLcode result = curl_easy_perform(curl); + + curl_easy_cleanup(curl); + + if(result != CURLE_OK) + { + llinfos << "Couldn't talk to crash report server" << llendl; + } + else + { + llinfos << "Response from crash report server:" << llendl; + llinfos << gServerResponse << llendl; + } + } + + return 0; +} + +LLFileEncoder::LLFileEncoder(const char *form_name, const char *filename, bool isCrashLog) +{ + mFormname = form_name; + mFilename = filename; + mIsValid = FALSE; + + int res; + + struct stat stat_data; + res = stat(mFilename.c_str(), &stat_data); + if (res) + { + llwarns << "File " << mFilename << " is missing!" << llendl; + return; + } + else + { + // Debugging spam +// llinfos << "File " << mFilename << " is present..." << llendl; + + if(!gCrashInPreviousExec && isCrashLog) + { + // Make sure the file isn't too old. + double age = difftime(gLaunchTime, stat_data.st_mtim.tv_sec); + +// llinfos << "age is " << age << llendl; + + if(age > 60.0) + { + // The file was last modified more than 60 seconds before the crash reporter was launched. Assume it's stale. + llwarns << "File " << mFilename << " is too old!" << llendl; + return; + } + } + + } + + S32 buf_size = stat_data.st_size; + FILE *fp = fopen(mFilename.c_str(), "rb"); + U8 *buf = new U8[buf_size + 1]; + fread(buf, 1, buf_size, fp); + fclose(fp); + buf[buf_size] = 0; + + mBuf = (char *)buf; + + if(isCrashLog) + { + // Crash logs consist of a number of entries, one per crash. + // Each entry is preceeded by "**********" on a line by itself. + // We want only the most recent (i.e. last) one. + const char *sep = "**********"; + const char *start = mBuf.c_str(); + const char *cur = start; + const char *temp = strstr(cur, sep); + + while(temp != NULL) + { + // Skip past the marker we just found + cur = temp + strlen(sep); + + // and try to find another + temp = strstr(cur, sep); + } + + // If there's more than one entry in the log file, strip all but the last one. + if(cur != start) + { + mBuf.erase(0, cur - start); + } + } + + mIsValid = TRUE; + delete[] buf; +} + +LLString LLFileEncoder::encodeURL(const S32 max_length) +{ + LLString result = mFormname; + result.append("="); + + S32 i = 0; + + if (max_length) + { + if ((S32)mBuf.size() > max_length) + { + i = mBuf.size() - max_length; + } + } + +#if 0 + // Plain text version for debugging + result.append(mBuf); +#else + // Not using LLString because of bad performance issues + S32 buf_size = mBuf.size(); + S32 url_buf_size = 3*mBuf.size() + 1; + char *url_buf = new char[url_buf_size]; + + S32 cur_pos = 0; + for (; i < buf_size; i++) + { + sprintf(url_buf + cur_pos, "%%%02x", mBuf[i]); + cur_pos += 3; + } + url_buf[i*3] = 0; + + result.append(url_buf); + delete[] url_buf; +#endif + return result; +} + +LLString encode_string(const char *formname, const LLString &str) +{ + LLString result = formname; + result.append("="); + // Not using LLString because of bad performance issues + S32 buf_size = str.size(); + S32 url_buf_size = 3*str.size() + 1; + char *url_buf = new char[url_buf_size]; + + S32 cur_pos = 0; + S32 i; + for (i = 0; i < buf_size; i++) + { + sprintf(url_buf + cur_pos, "%%%02x", str[i]); + cur_pos += 3; + } + url_buf[i*3] = 0; + + result.append(url_buf); + delete[] url_buf; + return result; +} diff --git a/indra/llaudio/llaudiodecodemgr.cpp b/indra/llaudio/llaudiodecodemgr.cpp new file mode 100644 index 0000000000..a819a9fa03 --- /dev/null +++ b/indra/llaudio/llaudiodecodemgr.cpp @@ -0,0 +1,616 @@ +/** + * @file llaudiodecodemgr.cpp + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include +#include +#include +#include + +#include "llaudiodecodemgr.h" + +#include "vorbisdecode.h" +#include "audioengine.h" +#include "lllfsthread.h" +#include "llvfile.h" +#include "llstring.h" +#include "lldir.h" +#include "llendianswizzle.h" +#include "audioengine.h" +#include "llassetstorage.h" + +#include "vorbis/codec.h" +#include "vorbis/vorbisfile.h" + +extern LLAudioEngine *gAudiop; + +LLAudioDecodeMgr *gAudioDecodeMgrp = NULL; + +const S32 wav_header_size = 44; + +class LLVorbisDecodeState +{ +public: + LLVorbisDecodeState(const LLUUID &uuid, const LLString &out_filename); + virtual ~LLVorbisDecodeState(); + + BOOL initDecode(); + BOOL decodeSection(); // Return TRUE if done. + BOOL finishDecode(); + + void flushBadFile(); + + BOOL isValid() const { return mValid; } + BOOL isDone() const { return mDone; } + const LLUUID &getUUID() const { return mUUID; } +protected: + BOOL mValid; + BOOL mDone; + LLUUID mUUID; + + std::vector mWAVBuffer; +#if !defined(USE_WAV_VFILE) + LLString mOutFilename; + LLLFSThread::handle_t mFileHandle; +#endif + + LLVFile *mInFilep; + OggVorbis_File mVF; + S32 mCurrentSection; +}; + +void LLVorbisDecodeState::flushBadFile() +{ + if (mInFilep) + { + llwarns << "Flushing bad vorbis file from VFS for " << mUUID << llendl; + mInFilep->remove(); + } +} + + +LLAudioDecodeMgr::LLAudioDecodeMgr() +{ + mCurrentDecodep = NULL; +} + +LLAudioDecodeMgr::~LLAudioDecodeMgr() +{ + delete mCurrentDecodep; + mCurrentDecodep = NULL; +} + + +void LLAudioDecodeMgr::processQueue(const F32 num_secs) +{ + LLUUID uuid; + + LLTimer decode_timer; + + BOOL done = FALSE; + while (!done) + { + if (mCurrentDecodep) + { + BOOL res; + + // Decode in a loop until we're done or have run out of time. + while(!(res = mCurrentDecodep->decodeSection()) && (decode_timer.getElapsedTimeF32() < num_secs)) + { + // decodeSection does all of the work above + } + + if (mCurrentDecodep->isDone() && !mCurrentDecodep->isValid()) + { + // We had an error when decoding, abort. + llwarns << mCurrentDecodep->getUUID() << " has invalid vorbis data, aborting decode" << llendl; + mCurrentDecodep->flushBadFile(); + LLAudioData *adp = gAudiop->getAudioData(mCurrentDecodep->getUUID()); + adp->setHasValidData(FALSE); + delete mCurrentDecodep; + mCurrentDecodep = NULL; + done = TRUE; + } + + if (!res) + { + // We've used up out time slice, bail... + done = TRUE; + } + else if (mCurrentDecodep) + { + if (mCurrentDecodep->finishDecode()) + { + // We finished! + if (mCurrentDecodep->isValid() && mCurrentDecodep->isDone()) + { + LLAudioData *adp = gAudiop->getAudioData(mCurrentDecodep->getUUID()); + adp->setHasDecodedData(TRUE); + adp->setHasValidData(TRUE); + + // At this point, we could see if anyone needs this sound immediately, but + // I'm not sure that there's a reason to - we need to poll all of the playing + // sounds anyway. + //llinfos << "Finished the vorbis decode, now what?" << llendl; + } + else + { + llinfos << "Vorbis decode failed!!!" << llendl; + } + delete mCurrentDecodep; + mCurrentDecodep = NULL; + } + done = TRUE; // done for now + } + } + + if (!done) + { + if (!mDecodeQueue.getLength()) + { + // Nothing else on the queue. + done = TRUE; + } + else + { + LLUUID uuid; + mDecodeQueue.pop(uuid); + if (gAudiop->hasDecodedFile(uuid)) + { + // This file has already been decoded, don't decode it again. + continue; + } + + lldebugs << "Decoding " << uuid << " from audio queue!" << llendl; + + char uuid_str[64]; /*Flawfinder: ignore*/ + char d_path[LL_MAX_PATH]; /*Flawfinder: ignore*/ + + LLTimer timer; + timer.reset(); + + uuid.toString(uuid_str); + snprintf(d_path, LL_MAX_PATH, "%s.dsf", gDirUtilp->getExpandedFilename(LL_PATH_CACHE,uuid_str).c_str()); /*Flawfinder: ignore*/ + + mCurrentDecodep = new LLVorbisDecodeState(uuid, d_path); + if (!mCurrentDecodep->initDecode()) + { + delete mCurrentDecodep; + mCurrentDecodep = NULL; + } + } + } + } +} + + +BOOL LLAudioDecodeMgr::addDecodeRequest(const LLUUID &uuid) +{ + if (gAudiop->hasDecodedFile(uuid)) + { + // Already have a decoded version, don't need to decode it. + return TRUE; + } + + if (gAssetStorage->hasLocalAsset(uuid, LLAssetType::AT_SOUND)) + { + // Just put it on the decode queue. + gAudioDecodeMgrp->mDecodeQueue.push(uuid); + return TRUE; + } + + return FALSE; +} + + +S32 LLAudioDecodeMgr::getRequestCount() +{ + /* + S32 count = 0; + if (mCurrentTransfer.notNull()) + { + count++; + } + + count += mRequestQueue.getLength(); + return count; + */ + return 0; +} + + + + + + + + +size_t vfs_read(void *ptr, size_t size, size_t nmemb, void *datasource) +{ + LLVFile *file = (LLVFile *)datasource; + + if (file->read((U8*)ptr, (S32)(size * nmemb))) /*Flawfinder: ignore*/ + { + S32 read = file->getLastBytesRead(); + return read / size; /*Flawfinder: ignore*/ + } + else + { + return 0; + } +} + +int vfs_seek(void *datasource, ogg_int64_t offset, int whence) +{ + LLVFile *file = (LLVFile *)datasource; + + // vfs has 31-bit files + if (offset > S32_MAX) + { + return -1; + } + + S32 origin; + switch (whence) { + case SEEK_SET: + origin = 0; + break; + case SEEK_END: + origin = file->getSize(); + break; + case SEEK_CUR: + origin = -1; + break; + default: + llerrs << "Invalid whence argument to vfs_seek" << llendl; + return -1; + } + + if (file->seek((S32)offset, origin)) + { + return 0; + } + else + { + return -1; + } +} + +int vfs_close (void *datasource) +{ + LLVFile *file = (LLVFile *)datasource; + + delete file; + + return 0; +} + +long vfs_tell (void *datasource) +{ + LLVFile *file = (LLVFile *)datasource; + + return file->tell(); +} + + + + +LLVorbisDecodeState::LLVorbisDecodeState(const LLUUID &uuid, const LLString &out_filename) +{ + mDone = FALSE; + mValid = FALSE; + mUUID = uuid; + mInFilep = NULL; + mCurrentSection = 0; +#if !defined(USE_WAV_VFILE) + mOutFilename = out_filename; + mFileHandle = LLLFSThread::nullHandle(); +#endif + // No default value for mVF, it's an ogg structure? +} + +LLVorbisDecodeState::~LLVorbisDecodeState() +{ + if (!mDone) + { + delete mInFilep; + mInFilep = NULL; + } +} + + +BOOL LLVorbisDecodeState::initDecode() +{ + ov_callbacks vfs_callbacks; + vfs_callbacks.read_func = vfs_read; + vfs_callbacks.seek_func = vfs_seek; + vfs_callbacks.close_func = vfs_close; + vfs_callbacks.tell_func = vfs_tell; + + //llinfos << "Initing decode from vfile: " << mUUID << llendl; + + mInFilep = new LLVFile(gVFS, mUUID, LLAssetType::AT_SOUND); + if (!mInFilep || !mInFilep->getSize()) + { + llwarns << "unable to open vorbis source vfile for reading" << llendl; + delete mInFilep; + mInFilep = NULL; + return FALSE; + } + + int r = ov_open_callbacks(mInFilep, &mVF, NULL, 0, vfs_callbacks); + if(r < 0) + { + llwarns << r << " Input to vorbis decode does not appear to be an Ogg bitstream: " << mUUID << llendl; + return(FALSE); + } + + size_t size_guess = (size_t)ov_pcm_total(&mVF, -1); + vorbis_info* vi = ov_info(&mVF, -1); + size_guess *= vi->channels; + size_guess *= 2; + size_guess += 2048; + mWAVBuffer.reserve(size_guess); + mWAVBuffer.resize(wav_header_size); + + { + // write the .wav format header + //"RIFF" + mWAVBuffer[0] = 0x52; + mWAVBuffer[1] = 0x49; + mWAVBuffer[2] = 0x46; + mWAVBuffer[3] = 0x46; + + // length = datalen + 36 (to be filled in later) + mWAVBuffer[4] = 0x00; + mWAVBuffer[5] = 0x00; + mWAVBuffer[6] = 0x00; + mWAVBuffer[7] = 0x00; + + //"WAVE" + mWAVBuffer[8] = 0x57; + mWAVBuffer[9] = 0x41; + mWAVBuffer[10] = 0x56; + mWAVBuffer[11] = 0x45; + + // "fmt " + mWAVBuffer[12] = 0x66; + mWAVBuffer[13] = 0x6D; + mWAVBuffer[14] = 0x74; + mWAVBuffer[15] = 0x20; + + // chunk size = 16 + mWAVBuffer[16] = 0x10; + mWAVBuffer[17] = 0x00; + mWAVBuffer[18] = 0x00; + mWAVBuffer[19] = 0x00; + + // format (1 = PCM) + mWAVBuffer[20] = 0x01; + mWAVBuffer[21] = 0x00; + + // number of channels + mWAVBuffer[22] = 0x01; + mWAVBuffer[23] = 0x00; + + // samples per second + mWAVBuffer[24] = 0x44; + mWAVBuffer[25] = 0xAC; + mWAVBuffer[26] = 0x00; + mWAVBuffer[27] = 0x00; + + // average bytes per second + mWAVBuffer[28] = 0x88; + mWAVBuffer[29] = 0x58; + mWAVBuffer[30] = 0x01; + mWAVBuffer[31] = 0x00; + + // bytes to output at a single time + mWAVBuffer[32] = 0x02; + mWAVBuffer[33] = 0x00; + + // 16 bits per sample + mWAVBuffer[34] = 0x10; + mWAVBuffer[35] = 0x00; + + // "data" + mWAVBuffer[36] = 0x64; + mWAVBuffer[37] = 0x61; + mWAVBuffer[38] = 0x74; + mWAVBuffer[39] = 0x61; + + // these are the length of the data chunk, to be filled in later + mWAVBuffer[40] = 0x00; + mWAVBuffer[41] = 0x00; + mWAVBuffer[42] = 0x00; + mWAVBuffer[43] = 0x00; + } + + //{ + //char **ptr=ov_comment(&mVF,-1)->user_comments; +// vorbis_info *vi=ov_info(&vf,-1); + //while(*ptr){ + // fprintf(stderr,"%s\n",*ptr); + // ++ptr; + //} +// fprintf(stderr,"\nBitstream is %d channel, %ldHz\n",vi->channels,vi->rate); +// fprintf(stderr,"\nDecoded length: %ld samples\n", (long)ov_pcm_total(&vf,-1)); +// fprintf(stderr,"Encoded by: %s\n\n",ov_comment(&vf,-1)->vendor); + //} + return TRUE; +} + +BOOL LLVorbisDecodeState::decodeSection() +{ + if (!mInFilep) + { + llwarns << "No VFS file to decode in vorbis!" << llendl; + return TRUE; + } + if (mDone) + { +// llwarns << "Already done with decode, aborting!" << llendl; + return TRUE; + } + char pcmout[4096]; /*Flawfinder: ignore*/ + + BOOL eof = FALSE; + long ret=ov_read(&mVF, pcmout, sizeof(pcmout), 0, 2, 1, &mCurrentSection); + if (ret == 0) + { + /* EOF */ + eof = TRUE; + mDone = TRUE; + mValid = TRUE; +// llinfos << "Vorbis EOF" << llendl; + } + else if (ret < 0) + { + /* error in the stream. Not a problem, just reporting it in + case we (the app) cares. In this case, we don't. */ + + llwarns << "BAD vorbis decode in decodeSection." << llendl; + + mValid = FALSE; + mDone = TRUE; + // We're done, return TRUE. + return TRUE; + } + else + { +// llinfos << "Vorbis read " << ret << "bytes" << llendl; + /* we don't bother dealing with sample rate changes, etc, but. + you'll have to*/ + std::copy(pcmout, pcmout+ret, std::back_inserter(mWAVBuffer)); + } + return eof; +} + +BOOL LLVorbisDecodeState::finishDecode() +{ + if (!isValid()) + { + llwarns << "Bogus vorbis decode state for " << getUUID() << ", aborting!" << llendl; + return TRUE; // We've finished + } + +#if !defined(USE_WAV_VFILE) + if (mFileHandle == LLLFSThread::nullHandle()) +#endif + { + ov_clear(&mVF); + + // write "data" chunk length, in little-endian format + S32 data_length = mWAVBuffer.size() - wav_header_size; + mWAVBuffer[40] = (data_length) & 0x000000FF; + mWAVBuffer[41] = (data_length >> 8) & 0x000000FF; + mWAVBuffer[42] = (data_length >> 16) & 0x000000FF; + mWAVBuffer[43] = (data_length >> 24) & 0x000000FF; + // write overall "RIFF" length, in little-endian format + data_length += 36; + mWAVBuffer[4] = (data_length) & 0x000000FF; + mWAVBuffer[5] = (data_length >> 8) & 0x000000FF; + mWAVBuffer[6] = (data_length >> 16) & 0x000000FF; + mWAVBuffer[7] = (data_length >> 24) & 0x000000FF; + + // + // FUCK!!! Vorbis encode/decode messes up loop point transitions (pop) + // do a cheap-and-cheesy crossfade + // + { + S16 *samplep; + S32 i; + S32 fade_length; + char pcmout[4096]; /*Flawfinder: ignore*/ + + fade_length = llmin((S32)128,(S32)(data_length-36)/8); + if (sizeof(mWAVBuffer) >= (wav_header_size + 2* fade_length)) + { + memcpy(pcmout, &mWAVBuffer[wav_header_size], (2 * fade_length)); /*Flawfinder: ignore*/ + } + llendianswizzle(&pcmout, 2, fade_length); + + samplep = (S16 *)pcmout; + for (i = 0 ;i < fade_length; i++) + { + *samplep = llfloor((F32)*samplep * ((F32)i/(F32)fade_length)); + samplep++; + } + + llendianswizzle(&pcmout, 2, fade_length); + if ((wav_header_size+(2 * fade_length)) < sizeof(mWAVBuffer)) + { + memcpy(&mWAVBuffer[wav_header_size], pcmout, (2 * fade_length)); /*Flawfinder: ignore*/ + } + S32 near_end = mWAVBuffer.size() - (2 * fade_length); + if (sizeof(mWAVBuffer) >= ( near_end + 2* fade_length)) + { + memcpy(pcmout, &mWAVBuffer[near_end], (2 * fade_length)); /*Flawfinder: ignore*/ + } + llendianswizzle(&pcmout, 2, fade_length); + + samplep = (S16 *)pcmout; + for (i = fade_length-1 ; i >= 0; i--) + { + *samplep = llfloor((F32)*samplep * ((F32)i/(F32)fade_length)); + samplep++; + } + + llendianswizzle(&pcmout, 2, fade_length); + if (near_end + (2 * fade_length) < sizeof(mWAVBuffer)) + { + memcpy(&mWAVBuffer[near_end], pcmout, (2 * fade_length));/*Flawfinder: ignore*/ + } + } + + if (36 == data_length) + { + llwarns << "BAD Vorbis decode in finishDecode!" << llendl; + mValid = FALSE; + return TRUE; // we've finished + } +#if !defined(USE_WAV_VFILE) + mFileHandle = LLLFSThread::sLocal->write(mOutFilename, &mWAVBuffer[0], 0, data_length); +#endif + } + + if (mFileHandle != LLLFSThread::nullHandle()) + { + LLLFSThread::status_t s = LLLFSThread::sLocal->getRequestStatus(mFileHandle); + if (s != LLLFSThread::STATUS_COMPLETE) + { + if (s != LLLFSThread::STATUS_QUEUED && s != LLLFSThread::STATUS_INPROGRESS) + { + llerrs << "Bad file status in LLVorbisDecodeState::finishDecode: " << s << llendl; + } + return FALSE; // not finished + } + else + { + LLLFSThread::Request* req = (LLLFSThread::Request*)LLLFSThread::sLocal->getRequest(mFileHandle); + if (req->getBytesRead() == 0) //!= req->getBytes() // should be safe, but needs testing + { + llwarns << "Unable to write file in LLVorbisDecodeState::finishDecode" << llendl; + mValid = FALSE; + return TRUE; // we've finished + } + } + LLLFSThread::sLocal->completeRequest(mFileHandle); + } + + mDone = TRUE; + +#if defined(USE_WAV_VFILE) + // write the data. + LLVFile output(gVFS, mUUID, LLAssetType::AT_SOUND_WAV); + output.write(&mWAVBuffer[0], mWAVBuffer.size()); +#endif + //llinfos << "Finished decode for " << getUUID() << llendl; + + return TRUE; +} diff --git a/indra/llaudio/llaudiodecodemgr.h b/indra/llaudio/llaudiodecodemgr.h new file mode 100644 index 0000000000..ea1358b8e9 --- /dev/null +++ b/indra/llaudio/llaudiodecodemgr.h @@ -0,0 +1,47 @@ +/** + * @file llaudiodecodemgr.h + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLAUDIODECODEMGR_H +#define LL_LLAUDIODECODEMG_H + +#include "stdtypes.h" + +#include "lllinkedqueue.h" +#include "lluuid.h" + +#include "llassettype.h" +#include "llframetimer.h" + +class LLVFS; +class LLVorbisDecodeState; + + +class LLAudioDecodeMgr +{ +public: + LLAudioDecodeMgr(); + ~LLAudioDecodeMgr(); + + void processQueue(const F32 num_secs = 0.005); + + LLLinkedQueue mDecodeQueue; + + LLVorbisDecodeState *mCurrentDecodep; + + BOOL addDecodeRequest(const LLUUID &uuid); + void addAudioRequest(const LLUUID &uuid); + + S32 getRequestCount(); + +protected: + LLLinkedQueue mDownloadQueue; + LLFrameTimer mCurrentTransferAge; +}; + +extern LLAudioDecodeMgr *gAudioDecodeMgrp; + +#endif diff --git a/indra/llcharacter/llanimationstates.cpp b/indra/llcharacter/llanimationstates.cpp new file mode 100644 index 0000000000..58ba252e04 --- /dev/null +++ b/indra/llcharacter/llanimationstates.cpp @@ -0,0 +1,319 @@ +/** + * @file llanimationstates.cpp + * @brief Implementation of animation state related functions. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Agent Animation State +//----------------------------------------------------------------------------- + +#include "linden_common.h" + +#include + +#include "llanimationstates.h" +#include "llstring.h" + +LLUUID AGENT_WALK_ANIMS[] = {ANIM_AGENT_WALK, ANIM_AGENT_RUN, ANIM_AGENT_CROUCHWALK, ANIM_AGENT_TURNLEFT, ANIM_AGENT_TURNRIGHT}; +S32 NUM_AGENT_WALK_ANIMS = sizeof(AGENT_WALK_ANIMS) / sizeof(LLUUID); + +LLUUID AGENT_GUN_HOLD_ANIMS[] = {ANIM_AGENT_HOLD_RIFLE_R, ANIM_AGENT_HOLD_HANDGUN_R, ANIM_AGENT_HOLD_BAZOOKA_R, ANIM_AGENT_HOLD_BOW_L}; +S32 NUM_AGENT_GUN_HOLD_ANIMS = sizeof(AGENT_GUN_HOLD_ANIMS) / sizeof(LLUUID); + +LLUUID AGENT_GUN_AIM_ANIMS[] = {ANIM_AGENT_AIM_RIFLE_R, ANIM_AGENT_AIM_HANDGUN_R, ANIM_AGENT_AIM_BAZOOKA_R, ANIM_AGENT_AIM_BOW_L}; +S32 NUM_AGENT_GUN_AIM_ANIMS = sizeof(AGENT_GUN_AIM_ANIMS) / sizeof(LLUUID); + +LLUUID AGENT_NO_ROTATE_ANIMS[] = {ANIM_AGENT_SIT_GROUND, ANIM_AGENT_SIT_GROUND_CONSTRAINED, ANIM_AGENT_STANDUP}; +S32 NUM_AGENT_NO_ROTATE_ANIMS = sizeof(AGENT_NO_ROTATE_ANIMS) / sizeof(LLUUID); + +LLUUID AGENT_STAND_ANIMS[] = {ANIM_AGENT_STAND, ANIM_AGENT_STAND_1, ANIM_AGENT_STAND_2, ANIM_AGENT_STAND_3, ANIM_AGENT_STAND_4}; +S32 NUM_AGENT_STAND_ANIMS = sizeof(AGENT_STAND_ANIMS) / sizeof(LLUUID); + + +LLAnimationLibrary gAnimLibrary; + +//----------------------------------------------------------------------------- +// LLAnimationLibrary() +//----------------------------------------------------------------------------- +LLAnimationLibrary::LLAnimationLibrary() : + mAnimStringTable(16384) +{ + //add animation names to animmap + mAnimMap[ANIM_AGENT_AFRAID]= mAnimStringTable.addString("express_afraid"); + mAnimMap[ANIM_AGENT_AIM_BAZOOKA_R]= mAnimStringTable.addString("aim_r_bazooka"); + mAnimMap[ANIM_AGENT_AIM_BOW_L]= mAnimStringTable.addString("aim_l_bow"); + mAnimMap[ANIM_AGENT_AIM_HANDGUN_R]= mAnimStringTable.addString("aim_r_handgun"); + mAnimMap[ANIM_AGENT_AIM_RIFLE_R]= mAnimStringTable.addString("aim_r_rifle"); + mAnimMap[ANIM_AGENT_ANGRY]= mAnimStringTable.addString("express_anger"); + mAnimMap[ANIM_AGENT_AWAY]= mAnimStringTable.addString("away"); + mAnimMap[ANIM_AGENT_BACKFLIP]= mAnimStringTable.addString("backflip"); + mAnimMap[ANIM_AGENT_BELLY_LAUGH]= mAnimStringTable.addString("express_laugh"); + mAnimMap[ANIM_AGENT_BLOW_KISS]= mAnimStringTable.addString("blowkiss"); + mAnimMap[ANIM_AGENT_BORED]= mAnimStringTable.addString("express_bored"); + mAnimMap[ANIM_AGENT_BOW]= mAnimStringTable.addString("bow"); + mAnimMap[ANIM_AGENT_BRUSH]= mAnimStringTable.addString("brush"); + mAnimMap[ANIM_AGENT_BUSY]= mAnimStringTable.addString("busy"); + mAnimMap[ANIM_AGENT_CLAP]= mAnimStringTable.addString("clap"); + mAnimMap[ANIM_AGENT_COURTBOW]= mAnimStringTable.addString("courtbow"); + mAnimMap[ANIM_AGENT_CROUCH]= mAnimStringTable.addString("crouch"); + mAnimMap[ANIM_AGENT_CROUCHWALK]= mAnimStringTable.addString("crouchwalk"); + mAnimMap[ANIM_AGENT_CRY]= mAnimStringTable.addString("express_cry"); + mAnimMap[ANIM_AGENT_CUSTOMIZE]= mAnimStringTable.addString("turn_180"); + mAnimMap[ANIM_AGENT_CUSTOMIZE_DONE]= mAnimStringTable.addString("turnback_180"); + mAnimMap[ANIM_AGENT_DANCE1]= mAnimStringTable.addString("dance1"); + mAnimMap[ANIM_AGENT_DANCE2]= mAnimStringTable.addString("dance2"); + mAnimMap[ANIM_AGENT_DANCE3]= mAnimStringTable.addString("dance3"); + mAnimMap[ANIM_AGENT_DANCE4]= mAnimStringTable.addString("dance4"); + mAnimMap[ANIM_AGENT_DANCE5]= mAnimStringTable.addString("dance5"); + mAnimMap[ANIM_AGENT_DANCE6]= mAnimStringTable.addString("dance6"); + mAnimMap[ANIM_AGENT_DANCE7]= mAnimStringTable.addString("dance7"); + mAnimMap[ANIM_AGENT_DANCE8]= mAnimStringTable.addString("dance8"); + mAnimMap[ANIM_AGENT_DEAD]= mAnimStringTable.addString("dead"); + mAnimMap[ANIM_AGENT_DRINK]= mAnimStringTable.addString("drink"); + mAnimMap[ANIM_AGENT_EMBARRASSED]= mAnimStringTable.addString("express_embarrased"); + mAnimMap[ANIM_AGENT_EXPRESS_AFRAID]= mAnimStringTable.addString("express_afraid_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_ANGER]= mAnimStringTable.addString("express_anger_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_BORED]= mAnimStringTable.addString("express_bored_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_CRY]= mAnimStringTable.addString("express_cry_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_DISDAIN]= mAnimStringTable.addString("express_disdain"); + mAnimMap[ANIM_AGENT_EXPRESS_EMBARRASSED]= mAnimStringTable.addString("express_embarrassed_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_FROWN]= mAnimStringTable.addString("express_frown"); + mAnimMap[ANIM_AGENT_EXPRESS_KISS]= mAnimStringTable.addString("express_kiss"); + mAnimMap[ANIM_AGENT_EXPRESS_LAUGH]= mAnimStringTable.addString("express_laugh_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_OPEN_MOUTH]= mAnimStringTable.addString("express_open_mouth"); + mAnimMap[ANIM_AGENT_EXPRESS_REPULSED]= mAnimStringTable.addString("express_repulsed_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_SAD]= mAnimStringTable.addString("express_sad_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_SHRUG]= mAnimStringTable.addString("express_shrug_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_SMILE]= mAnimStringTable.addString("express_smile"); + mAnimMap[ANIM_AGENT_EXPRESS_SURPRISE]= mAnimStringTable.addString("express_surprise_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_TONGUE_OUT]= mAnimStringTable.addString("express_tongue_out"); + mAnimMap[ANIM_AGENT_EXPRESS_TOOTHSMILE]= mAnimStringTable.addString("express_toothsmile"); + mAnimMap[ANIM_AGENT_EXPRESS_WINK]= mAnimStringTable.addString("express_wink_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_WORRY]= mAnimStringTable.addString("express_worry_emote"); + mAnimMap[ANIM_AGENT_FALLDOWN]= mAnimStringTable.addString("falldown"); + mAnimMap[ANIM_AGENT_FEMALE_WALK]= mAnimStringTable.addString("female_walk"); + mAnimMap[ANIM_AGENT_FINGER_WAG]= mAnimStringTable.addString("angry_fingerwag"); + mAnimMap[ANIM_AGENT_FIST_PUMP]= mAnimStringTable.addString("fist_pump"); + mAnimMap[ANIM_AGENT_FLY]= mAnimStringTable.addString("fly"); + mAnimMap[ANIM_AGENT_FLYSLOW]= mAnimStringTable.addString("flyslow"); + mAnimMap[ANIM_AGENT_HELLO]= mAnimStringTable.addString("hello"); + mAnimMap[ANIM_AGENT_HOLD_BAZOOKA_R]= mAnimStringTable.addString("hold_r_bazooka"); + mAnimMap[ANIM_AGENT_HOLD_BOW_L]= mAnimStringTable.addString("hold_l_bow"); + mAnimMap[ANIM_AGENT_HOLD_HANDGUN_R]= mAnimStringTable.addString("hold_r_handgun"); + mAnimMap[ANIM_AGENT_HOLD_RIFLE_R]= mAnimStringTable.addString("hold_r_rifle"); + mAnimMap[ANIM_AGENT_HOLD_THROW_R]= mAnimStringTable.addString("hold_throw_r"); + mAnimMap[ANIM_AGENT_HOVER]= mAnimStringTable.addString("hover"); + mAnimMap[ANIM_AGENT_HOVER_DOWN]= mAnimStringTable.addString("hover_down"); + mAnimMap[ANIM_AGENT_HOVER_UP]= mAnimStringTable.addString("hover_up"); + mAnimMap[ANIM_AGENT_IMPATIENT]= mAnimStringTable.addString("impatient"); + mAnimMap[ANIM_AGENT_JUMP]= mAnimStringTable.addString("jump"); + mAnimMap[ANIM_AGENT_JUMP_FOR_JOY]= mAnimStringTable.addString("jumpforjoy"); + mAnimMap[ANIM_AGENT_KISS_MY_BUTT]= mAnimStringTable.addString("kissmybutt"); + mAnimMap[ANIM_AGENT_LAND]= mAnimStringTable.addString("land"); + mAnimMap[ANIM_AGENT_LAUGH_SHORT]= mAnimStringTable.addString("laugh_short"); + mAnimMap[ANIM_AGENT_MEDIUM_LAND]= mAnimStringTable.addString("soft_land"); + mAnimMap[ANIM_AGENT_MOTORCYCLE_SIT]= mAnimStringTable.addString("motorcycle_sit"); + mAnimMap[ANIM_AGENT_MUSCLE_BEACH]= mAnimStringTable.addString("musclebeach"); + mAnimMap[ANIM_AGENT_NO]= mAnimStringTable.addString("no_head"); + mAnimMap[ANIM_AGENT_NO_UNHAPPY]= mAnimStringTable.addString("no_unhappy"); + mAnimMap[ANIM_AGENT_NYAH_NYAH]= mAnimStringTable.addString("nyanya"); + mAnimMap[ANIM_AGENT_ONETWO_PUNCH]= mAnimStringTable.addString("punch_onetwo"); + mAnimMap[ANIM_AGENT_PEACE]= mAnimStringTable.addString("peace"); + mAnimMap[ANIM_AGENT_POINT_ME]= mAnimStringTable.addString("point_me"); + mAnimMap[ANIM_AGENT_POINT_YOU]= mAnimStringTable.addString("point_you"); + mAnimMap[ANIM_AGENT_PRE_JUMP]= mAnimStringTable.addString("prejump"); + mAnimMap[ANIM_AGENT_PUNCH_LEFT]= mAnimStringTable.addString("punch_l"); + mAnimMap[ANIM_AGENT_PUNCH_RIGHT]= mAnimStringTable.addString("punch_r"); + mAnimMap[ANIM_AGENT_REPULSED]= mAnimStringTable.addString("express_repulsed"); + mAnimMap[ANIM_AGENT_ROUNDHOUSE_KICK]= mAnimStringTable.addString("kick_roundhouse_r"); + mAnimMap[ANIM_AGENT_RPS_COUNTDOWN]= mAnimStringTable.addString("rps_countdown"); + mAnimMap[ANIM_AGENT_RPS_PAPER]= mAnimStringTable.addString("rps_paper"); + mAnimMap[ANIM_AGENT_RPS_ROCK]= mAnimStringTable.addString("rps_rock"); + mAnimMap[ANIM_AGENT_RPS_SCISSORS]= mAnimStringTable.addString("rps_scissors"); + mAnimMap[ANIM_AGENT_RUN]= mAnimStringTable.addString("run"); + mAnimMap[ANIM_AGENT_SAD]= mAnimStringTable.addString("express_sad"); + mAnimMap[ANIM_AGENT_SALUTE]= mAnimStringTable.addString("salute"); + mAnimMap[ANIM_AGENT_SHOOT_BOW_L]= mAnimStringTable.addString("shoot_l_bow"); + mAnimMap[ANIM_AGENT_SHOUT]= mAnimStringTable.addString("shout"); + mAnimMap[ANIM_AGENT_SHRUG]= mAnimStringTable.addString("express_shrug"); + mAnimMap[ANIM_AGENT_SIT]= mAnimStringTable.addString("sit"); + mAnimMap[ANIM_AGENT_SIT_FEMALE]= mAnimStringTable.addString("sit_female"); + mAnimMap[ANIM_AGENT_SIT_GROUND]= mAnimStringTable.addString("sit_ground"); + mAnimMap[ANIM_AGENT_SIT_GROUND_CONSTRAINED]= mAnimStringTable.addString("sit_ground_constrained"); + mAnimMap[ANIM_AGENT_SIT_GENERIC]= mAnimStringTable.addString("sit_generic"); + mAnimMap[ANIM_AGENT_SIT_TO_STAND]= mAnimStringTable.addString("sit_to_stand"); + mAnimMap[ANIM_AGENT_SLEEP]= mAnimStringTable.addString("sleep"); + mAnimMap[ANIM_AGENT_SMOKE_IDLE]= mAnimStringTable.addString("smoke_idle"); + mAnimMap[ANIM_AGENT_SMOKE_INHALE]= mAnimStringTable.addString("smoke_inhale"); + mAnimMap[ANIM_AGENT_SMOKE_THROW_DOWN]= mAnimStringTable.addString("smoke_throw_down"); + mAnimMap[ANIM_AGENT_SNAPSHOT]= mAnimStringTable.addString("snapshot"); + mAnimMap[ANIM_AGENT_STAND]= mAnimStringTable.addString("stand"); + mAnimMap[ANIM_AGENT_STANDUP]= mAnimStringTable.addString("standup"); + mAnimMap[ANIM_AGENT_STAND_1]= mAnimStringTable.addString("stand_1"); + mAnimMap[ANIM_AGENT_STAND_2]= mAnimStringTable.addString("stand_2"); + mAnimMap[ANIM_AGENT_STAND_3]= mAnimStringTable.addString("stand_3"); + mAnimMap[ANIM_AGENT_STAND_4]= mAnimStringTable.addString("stand_4"); + mAnimMap[ANIM_AGENT_STRETCH]= mAnimStringTable.addString("stretch"); + mAnimMap[ANIM_AGENT_STRIDE]= mAnimStringTable.addString("stride"); + mAnimMap[ANIM_AGENT_SURF]= mAnimStringTable.addString("surf"); + mAnimMap[ANIM_AGENT_SURPRISE]= mAnimStringTable.addString("express_surprise"); + mAnimMap[ANIM_AGENT_SWORD_STRIKE]= mAnimStringTable.addString("sword_strike_r"); + mAnimMap[ANIM_AGENT_TALK]= mAnimStringTable.addString("talk"); + mAnimMap[ANIM_AGENT_TANTRUM]= mAnimStringTable.addString("angry_tantrum"); + mAnimMap[ANIM_AGENT_THROW_R]= mAnimStringTable.addString("throw_r"); + mAnimMap[ANIM_AGENT_TRYON_SHIRT]= mAnimStringTable.addString("tryon_shirt"); + mAnimMap[ANIM_AGENT_TURNLEFT]= mAnimStringTable.addString("turnleft"); + mAnimMap[ANIM_AGENT_TURNRIGHT]= mAnimStringTable.addString("turnright"); + mAnimMap[ANIM_AGENT_TYPE]= mAnimStringTable.addString("type"); + mAnimMap[ANIM_AGENT_WALK]= mAnimStringTable.addString("walk"); + mAnimMap[ANIM_AGENT_WHISPER]= mAnimStringTable.addString("whisper"); + mAnimMap[ANIM_AGENT_WHISTLE]= mAnimStringTable.addString("whistle"); + mAnimMap[ANIM_AGENT_WINK]= mAnimStringTable.addString("express_wink"); + mAnimMap[ANIM_AGENT_WINK_HOLLYWOOD]= mAnimStringTable.addString("wink_hollywood"); + mAnimMap[ANIM_AGENT_WORRY]= mAnimStringTable.addString("express_worry"); + mAnimMap[ANIM_AGENT_YES]= mAnimStringTable.addString("yes_head"); + mAnimMap[ANIM_AGENT_YES_HAPPY]= mAnimStringTable.addString("yes_happy"); + mAnimMap[ANIM_AGENT_YOGA_FLOAT]= mAnimStringTable.addString("yoga_float"); +} + +//----------------------------------------------------------------------------- +// ~LLAnimationLibrary() +//----------------------------------------------------------------------------- +LLAnimationLibrary::~LLAnimationLibrary() +{ +} + +//----------------------------------------------------------------------------- +// Return the text name of an animation state +//----------------------------------------------------------------------------- +const char *LLAnimationLibrary::animStateToString( const LLUUID& state ) +{ + if (state.isNull()) + { + return NULL; + } + if (mAnimMap.count(state)) + { + return mAnimMap[state]; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Return the animation state for a given name +//----------------------------------------------------------------------------- +LLUUID LLAnimationLibrary::stringToAnimState( const char *name, BOOL allow_ids ) +{ + LLString lower_case_name(name); + LLString::toLower(lower_case_name); + + char *true_name = mAnimStringTable.checkString(lower_case_name.c_str()); + + LLUUID id; + id.setNull(); + + if (true_name) + { + for (anim_map_t::iterator iter = mAnimMap.begin(); + iter != mAnimMap.end(); iter++) + { + if (iter->second == true_name) + { + id = iter->first; + break; + } + } + } + else if (allow_ids) + { + // try to convert string to LLUUID + id.set(name, FALSE); + } + + return id; +} + +// Animation states that the user can trigger as part of a gesture +const LLAnimStateEntry gUserAnimStates[] = { + LLAnimStateEntry("Afraid", "express_afraid", ANIM_AGENT_AFRAID), + LLAnimStateEntry("Angry", "express_anger", ANIM_AGENT_ANGRY), + LLAnimStateEntry("Away", "away", ANIM_AGENT_AWAY), + LLAnimStateEntry("Backflip", "backflip", ANIM_AGENT_BACKFLIP), + LLAnimStateEntry("Belly Laugh", "express_laugh", ANIM_AGENT_BELLY_LAUGH), + LLAnimStateEntry("BigSmile", "express_toothsmile", ANIM_AGENT_EXPRESS_TOOTHSMILE), + LLAnimStateEntry("Blow Kiss", "blowkiss", ANIM_AGENT_BLOW_KISS), + LLAnimStateEntry("Bored", "express_bored", ANIM_AGENT_BORED), + LLAnimStateEntry("Bow", "bow", ANIM_AGENT_BOW), + LLAnimStateEntry("Clap", "clap", ANIM_AGENT_CLAP), + LLAnimStateEntry("Court Bow", "courtbow", ANIM_AGENT_COURTBOW), + LLAnimStateEntry("Cry", "express_cry", ANIM_AGENT_CRY), + LLAnimStateEntry("Dance 1", "dance1", ANIM_AGENT_DANCE1), + LLAnimStateEntry("Dance 2", "dance2", ANIM_AGENT_DANCE2), + LLAnimStateEntry("Dance 3", "dance3", ANIM_AGENT_DANCE3), + LLAnimStateEntry("Dance 4", "dance4", ANIM_AGENT_DANCE4), + LLAnimStateEntry("Dance 5", "dance5", ANIM_AGENT_DANCE5), + LLAnimStateEntry("Dance 6", "dance6", ANIM_AGENT_DANCE6), + LLAnimStateEntry("Dance 7", "dance7", ANIM_AGENT_DANCE7), + LLAnimStateEntry("Dance 8", "dance8", ANIM_AGENT_DANCE8), + LLAnimStateEntry("Disdain", "express_disdain", ANIM_AGENT_EXPRESS_DISDAIN), + LLAnimStateEntry("Drink", "drink", ANIM_AGENT_DRINK), + LLAnimStateEntry("Embarrassed", "express_embarrased", ANIM_AGENT_EMBARRASSED), + LLAnimStateEntry("Finger Wag", "angry_fingerwag", ANIM_AGENT_FINGER_WAG), + LLAnimStateEntry("Fist Pump", "fist_pump", ANIM_AGENT_FIST_PUMP), + LLAnimStateEntry("Floating Yoga", "yoga_float", ANIM_AGENT_YOGA_FLOAT), + LLAnimStateEntry("Frown", "express_frown", ANIM_AGENT_EXPRESS_FROWN), + LLAnimStateEntry("Impatient", "impatient", ANIM_AGENT_IMPATIENT), + LLAnimStateEntry("Jump For Joy", "jumpforjoy", ANIM_AGENT_JUMP_FOR_JOY), + LLAnimStateEntry("Kiss My Butt", "kissmybutt", ANIM_AGENT_KISS_MY_BUTT), + LLAnimStateEntry("Kiss", "express_kiss", ANIM_AGENT_EXPRESS_KISS), + LLAnimStateEntry("Laugh", "laugh_short", ANIM_AGENT_LAUGH_SHORT), + LLAnimStateEntry("Muscle Beach", "musclebeach", ANIM_AGENT_MUSCLE_BEACH), + LLAnimStateEntry("No (Unhappy)", "no_unhappy", ANIM_AGENT_NO_UNHAPPY), + LLAnimStateEntry("No", "no_head", ANIM_AGENT_NO), + LLAnimStateEntry("Nya-nya-nya", "nyanya", ANIM_AGENT_NYAH_NYAH), + LLAnimStateEntry("One-Two Punch", "punch_onetwo", ANIM_AGENT_ONETWO_PUNCH), + LLAnimStateEntry("Open Mouth", "express_open_mouth", ANIM_AGENT_EXPRESS_OPEN_MOUTH), + LLAnimStateEntry("Peace", "peace", ANIM_AGENT_PEACE), + LLAnimStateEntry("Point at Other", "point_you", ANIM_AGENT_POINT_YOU), + LLAnimStateEntry("Point at Self", "point_me", ANIM_AGENT_POINT_ME), + LLAnimStateEntry("Punch Left", "punch_l", ANIM_AGENT_PUNCH_LEFT), + LLAnimStateEntry("Punch Right", "punch_r", ANIM_AGENT_PUNCH_RIGHT), + LLAnimStateEntry("RPS count", "rps_countdown", ANIM_AGENT_RPS_COUNTDOWN), + LLAnimStateEntry("RPS paper", "rps_paper", ANIM_AGENT_RPS_PAPER), + LLAnimStateEntry("RPS rock", "rps_rock", ANIM_AGENT_RPS_ROCK), + LLAnimStateEntry("RPS scissors", "rps_scissors", ANIM_AGENT_RPS_SCISSORS), + LLAnimStateEntry("Repulsed", "express_repulsed", ANIM_AGENT_EXPRESS_REPULSED), + LLAnimStateEntry("Roundhouse Kick", "kick_roundhouse_r", ANIM_AGENT_ROUNDHOUSE_KICK), + LLAnimStateEntry("Sad", "express_sad", ANIM_AGENT_SAD), + LLAnimStateEntry("Salute", "salute", ANIM_AGENT_SALUTE), + LLAnimStateEntry("Shout", "shout", ANIM_AGENT_SHOUT), + LLAnimStateEntry("Shrug", "express_shrug", ANIM_AGENT_SHRUG), + LLAnimStateEntry("Smile", "express_smile", ANIM_AGENT_EXPRESS_SMILE), + LLAnimStateEntry("Smoke Idle", "smoke_idle", ANIM_AGENT_SMOKE_IDLE), + LLAnimStateEntry("Smoke Inhale", "smoke_inhale", ANIM_AGENT_SMOKE_INHALE), + LLAnimStateEntry("Smoke Throw Down","smoke_throw_down", ANIM_AGENT_SMOKE_THROW_DOWN), + LLAnimStateEntry("Surprise", "express_surprise", ANIM_AGENT_SURPRISE), + LLAnimStateEntry("Sword Strike", "sword_strike_r", ANIM_AGENT_SWORD_STRIKE), + LLAnimStateEntry("Tantrum", "angry_tantrum", ANIM_AGENT_TANTRUM), + LLAnimStateEntry("TongueOut", "express_tongue_out", ANIM_AGENT_EXPRESS_TONGUE_OUT), + LLAnimStateEntry("Wave", "hello", ANIM_AGENT_HELLO), + LLAnimStateEntry("Whisper", "whisper", ANIM_AGENT_WHISPER), + LLAnimStateEntry("Whistle", "whistle", ANIM_AGENT_WHISTLE), + LLAnimStateEntry("Wink", "express_wink", ANIM_AGENT_WINK), + LLAnimStateEntry("Wink (Hollywood)","wink_hollywood", ANIM_AGENT_WINK_HOLLYWOOD), + LLAnimStateEntry("Worry", "express_worry", ANIM_AGENT_EXPRESS_WORRY), + LLAnimStateEntry("Yes (Happy)", "yes_happy", ANIM_AGENT_YES_HAPPY), + LLAnimStateEntry("Yes", "yes_head", ANIM_AGENT_YES), +}; + +const S32 gUserAnimStatesCount = sizeof(gUserAnimStates) / sizeof(gUserAnimStates[0]); + + + +// End + diff --git a/indra/llcharacter/llanimationstates.h b/indra/llcharacter/llanimationstates.h new file mode 100644 index 0000000000..31fe0dfc11 --- /dev/null +++ b/indra/llcharacter/llanimationstates.h @@ -0,0 +1,227 @@ +/** + * @file llanimationstates.h + * @brief Implementation of animation state support. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLANIMATIONSTATES_H +#define LL_LLANIMATIONSTATES_H + +#include + +#include "string_table.h" +#include "lluuid.h" + +//----------------------------------------------------------------------------- +// These bit flags are generally used to track the animation state +// of characters. The simulator and viewer share these flags to interpret +// the Animation name/value attribute on agents. +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Agent Animation State +//----------------------------------------------------------------------------- +const S32 MAX_CONCURRENT_ANIMS = 16; + + +const LLUUID ANIM_AGENT_AFRAID = LLUUID("6b61c8e8-4747-0d75-12d7-e49ff207a4ca"); +const LLUUID ANIM_AGENT_AIM_BAZOOKA_R = LLUUID("b5b4a67d-0aee-30d2-72cd-77b333e932ef"); +const LLUUID ANIM_AGENT_AIM_BOW_L = LLUUID("46bb4359-de38-4ed8-6a22-f1f52fe8f506"); +const LLUUID ANIM_AGENT_AIM_HANDGUN_R = LLUUID("3147d815-6338-b932-f011-16b56d9ac18b"); +const LLUUID ANIM_AGENT_AIM_RIFLE_R = LLUUID("ea633413-8006-180a-c3ba-96dd1d756720"); +const LLUUID ANIM_AGENT_ANGRY = LLUUID("5747a48e-073e-c331-f6f3-7c2149613d3e"); +const LLUUID ANIM_AGENT_AWAY = LLUUID("fd037134-85d4-f241-72c6-4f42164fedee"); +const LLUUID ANIM_AGENT_BACKFLIP = LLUUID("c4ca6188-9127-4f31-0158-23c4e2f93304"); +const LLUUID ANIM_AGENT_BELLY_LAUGH = LLUUID("18b3a4b5-b463-bd48-e4b6-71eaac76c515"); +const LLUUID ANIM_AGENT_BLOW_KISS = LLUUID("db84829b-462c-ee83-1e27-9bbee66bd624"); +const LLUUID ANIM_AGENT_BORED = LLUUID("b906c4ba-703b-1940-32a3-0c7f7d791510"); +const LLUUID ANIM_AGENT_BOW = LLUUID("82e99230-c906-1403-4d9c-3889dd98daba"); +const LLUUID ANIM_AGENT_BRUSH = LLUUID("349a3801-54f9-bf2c-3bd0-1ac89772af01"); +const LLUUID ANIM_AGENT_BUSY = LLUUID("efcf670c-2d18-8128-973a-034ebc806b67"); +const LLUUID ANIM_AGENT_CLAP = LLUUID("9b0c1c4e-8ac7-7969-1494-28c874c4f668"); +const LLUUID ANIM_AGENT_COURTBOW = LLUUID("9ba1c942-08be-e43a-fb29-16ad440efc50"); +const LLUUID ANIM_AGENT_CROUCH = LLUUID("201f3fdf-cb1f-dbec-201f-7333e328ae7c"); +const LLUUID ANIM_AGENT_CROUCHWALK = LLUUID("47f5f6fb-22e5-ae44-f871-73aaaf4a6022"); +const LLUUID ANIM_AGENT_CRY = LLUUID("92624d3e-1068-f1aa-a5ec-8244585193ed"); +const LLUUID ANIM_AGENT_CUSTOMIZE = LLUUID("038fcec9-5ebd-8a8e-0e2e-6e71a0a1ac53"); +const LLUUID ANIM_AGENT_CUSTOMIZE_DONE = LLUUID("6883a61a-b27b-5914-a61e-dda118a9ee2c"); +const LLUUID ANIM_AGENT_DANCE1 = LLUUID("b68a3d7c-de9e-fc87-eec8-543d787e5b0d"); +const LLUUID ANIM_AGENT_DANCE2 = LLUUID("928cae18-e31d-76fd-9cc9-2f55160ff818"); +const LLUUID ANIM_AGENT_DANCE3 = LLUUID("30047778-10ea-1af7-6881-4db7a3a5a114"); +const LLUUID ANIM_AGENT_DANCE4 = LLUUID("951469f4-c7b2-c818-9dee-ad7eea8c30b7"); +const LLUUID ANIM_AGENT_DANCE5 = LLUUID("4bd69a1d-1114-a0b4-625f-84e0a5237155"); +const LLUUID ANIM_AGENT_DANCE6 = LLUUID("cd28b69b-9c95-bb78-3f94-8d605ff1bb12"); +const LLUUID ANIM_AGENT_DANCE7 = LLUUID("a54d8ee2-28bb-80a9-7f0c-7afbbe24a5d6"); +const LLUUID ANIM_AGENT_DANCE8 = LLUUID("b0dc417c-1f11-af36-2e80-7e7489fa7cdc"); +const LLUUID ANIM_AGENT_DEAD = LLUUID("57abaae6-1d17-7b1b-5f98-6d11a6411276"); +const LLUUID ANIM_AGENT_DRINK = LLUUID("0f86e355-dd31-a61c-fdb0-3a96b9aad05f"); +const LLUUID ANIM_AGENT_EMBARRASSED = LLUUID("514af488-9051-044a-b3fc-d4dbf76377c6"); +const LLUUID ANIM_AGENT_EXPRESS_AFRAID = LLUUID("aa2df84d-cf8f-7218-527b-424a52de766e"); +const LLUUID ANIM_AGENT_EXPRESS_ANGER = LLUUID("1a03b575-9634-b62a-5767-3a679e81f4de"); +const LLUUID ANIM_AGENT_EXPRESS_BORED = LLUUID("214aa6c1-ba6a-4578-f27c-ce7688f61d0d"); +const LLUUID ANIM_AGENT_EXPRESS_CRY = LLUUID("d535471b-85bf-3b4d-a542-93bea4f59d33"); +const LLUUID ANIM_AGENT_EXPRESS_DISDAIN = LLUUID("d4416ff1-09d3-300f-4183-1b68a19b9fc1"); +const LLUUID ANIM_AGENT_EXPRESS_EMBARRASSED = LLUUID("0b8c8211-d78c-33e8-fa28-c51a9594e424"); +const LLUUID ANIM_AGENT_EXPRESS_FROWN = LLUUID("fee3df48-fa3d-1015-1e26-a205810e3001"); +const LLUUID ANIM_AGENT_EXPRESS_KISS = LLUUID("1e8d90cc-a84e-e135-884c-7c82c8b03a14"); +const LLUUID ANIM_AGENT_EXPRESS_LAUGH = LLUUID("62570842-0950-96f8-341c-809e65110823"); +const LLUUID ANIM_AGENT_EXPRESS_OPEN_MOUTH = LLUUID("d63bc1f9-fc81-9625-a0c6-007176d82eb7"); +const LLUUID ANIM_AGENT_EXPRESS_REPULSED = LLUUID("f76cda94-41d4-a229-2872-e0296e58afe1"); +const LLUUID ANIM_AGENT_EXPRESS_SAD = LLUUID("eb6ebfb2-a4b3-a19c-d388-4dd5c03823f7"); +const LLUUID ANIM_AGENT_EXPRESS_SHRUG = LLUUID("a351b1bc-cc94-aac2-7bea-a7e6ebad15ef"); +const LLUUID ANIM_AGENT_EXPRESS_SMILE = LLUUID("b7c7c833-e3d3-c4e3-9fc0-131237446312"); +const LLUUID ANIM_AGENT_EXPRESS_SURPRISE = LLUUID("728646d9-cc79-08b2-32d6-937f0a835c24"); +const LLUUID ANIM_AGENT_EXPRESS_TONGUE_OUT = LLUUID("835965c6-7f2f-bda2-5deb-2478737f91bf"); +const LLUUID ANIM_AGENT_EXPRESS_TOOTHSMILE = LLUUID("b92ec1a5-e7ce-a76b-2b05-bcdb9311417e"); +const LLUUID ANIM_AGENT_EXPRESS_WINK = LLUUID("da020525-4d94-59d6-23d7-81fdebf33148"); +const LLUUID ANIM_AGENT_EXPRESS_WORRY = LLUUID("9c05e5c7-6f07-6ca4-ed5a-b230390c3950"); +const LLUUID ANIM_AGENT_FALLDOWN = LLUUID("666307d9-a860-572d-6fd4-c3ab8865c094"); +const LLUUID ANIM_AGENT_FEMALE_WALK = LLUUID("f5fc7433-043d-e819-8298-f519a119b688"); +const LLUUID ANIM_AGENT_FINGER_WAG = LLUUID("c1bc7f36-3ba0-d844-f93c-93be945d644f"); +const LLUUID ANIM_AGENT_FIST_PUMP = LLUUID("7db00ccd-f380-f3ee-439d-61968ec69c8a"); +const LLUUID ANIM_AGENT_FLY = LLUUID("aec4610c-757f-bc4e-c092-c6e9caf18daf"); +const LLUUID ANIM_AGENT_FLYSLOW = LLUUID("2b5a38b2-5e00-3a97-a495-4c826bc443e6"); +const LLUUID ANIM_AGENT_HELLO = LLUUID("9b29cd61-c45b-5689-ded2-91756b8d76a9"); +const LLUUID ANIM_AGENT_HOLD_BAZOOKA_R = LLUUID("ef62d355-c815-4816-2474-b1acc21094a6"); +const LLUUID ANIM_AGENT_HOLD_BOW_L = LLUUID("8b102617-bcba-037b-86c1-b76219f90c88"); +const LLUUID ANIM_AGENT_HOLD_HANDGUN_R = LLUUID("efdc1727-8b8a-c800-4077-975fc27ee2f2"); +const LLUUID ANIM_AGENT_HOLD_RIFLE_R = LLUUID("3d94bad0-c55b-7dcc-8763-033c59405d33"); +const LLUUID ANIM_AGENT_HOLD_THROW_R = LLUUID("7570c7b5-1f22-56dd-56ef-a9168241bbb6"); +const LLUUID ANIM_AGENT_HOVER = LLUUID("4ae8016b-31b9-03bb-c401-b1ea941db41d"); +const LLUUID ANIM_AGENT_HOVER_DOWN = LLUUID("20f063ea-8306-2562-0b07-5c853b37b31e"); +const LLUUID ANIM_AGENT_HOVER_UP = LLUUID("62c5de58-cb33-5743-3d07-9e4cd4352864"); +const LLUUID ANIM_AGENT_IMPATIENT = LLUUID("5ea3991f-c293-392e-6860-91dfa01278a3"); +const LLUUID ANIM_AGENT_JUMP = LLUUID("2305bd75-1ca9-b03b-1faa-b176b8a8c49e"); +const LLUUID ANIM_AGENT_JUMP_FOR_JOY = LLUUID("709ea28e-1573-c023-8bf8-520c8bc637fa"); +const LLUUID ANIM_AGENT_KISS_MY_BUTT = LLUUID("19999406-3a3a-d58c-a2ac-d72e555dcf51"); +const LLUUID ANIM_AGENT_LAND = LLUUID("7a17b059-12b2-41b1-570a-186368b6aa6f"); +const LLUUID ANIM_AGENT_LAUGH_SHORT = LLUUID("ca5b3f14-3194-7a2b-c894-aa699b718d1f"); +const LLUUID ANIM_AGENT_MEDIUM_LAND = LLUUID("f4f00d6e-b9fe-9292-f4cb-0ae06ea58d57"); +const LLUUID ANIM_AGENT_MOTORCYCLE_SIT = LLUUID("08464f78-3a8e-2944-cba5-0c94aff3af29"); +const LLUUID ANIM_AGENT_MUSCLE_BEACH = LLUUID("315c3a41-a5f3-0ba4-27da-f893f769e69b"); +const LLUUID ANIM_AGENT_NO = LLUUID("5a977ed9-7f72-44e9-4c4c-6e913df8ae74"); +const LLUUID ANIM_AGENT_NO_UNHAPPY = LLUUID("d83fa0e5-97ed-7eb2-e798-7bd006215cb4"); +const LLUUID ANIM_AGENT_NYAH_NYAH = LLUUID("f061723d-0a18-754f-66ee-29a44795a32f"); +const LLUUID ANIM_AGENT_ONETWO_PUNCH = LLUUID("eefc79be-daae-a239-8c04-890f5d23654a"); +const LLUUID ANIM_AGENT_PEACE = LLUUID("b312b10e-65ab-a0a4-8b3c-1326ea8e3ed9"); +const LLUUID ANIM_AGENT_POINT_ME = LLUUID("17c024cc-eef2-f6a0-3527-9869876d7752"); +const LLUUID ANIM_AGENT_POINT_YOU = LLUUID("ec952cca-61ef-aa3b-2789-4d1344f016de"); +const LLUUID ANIM_AGENT_PRE_JUMP = LLUUID("7a4e87fe-de39-6fcb-6223-024b00893244"); +const LLUUID ANIM_AGENT_PUNCH_LEFT = LLUUID("f3300ad9-3462-1d07-2044-0fef80062da0"); +const LLUUID ANIM_AGENT_PUNCH_RIGHT = LLUUID("c8e42d32-7310-6906-c903-cab5d4a34656"); +const LLUUID ANIM_AGENT_REPULSED = LLUUID("36f81a92-f076-5893-dc4b-7c3795e487cf"); +const LLUUID ANIM_AGENT_ROUNDHOUSE_KICK = LLUUID("49aea43b-5ac3-8a44-b595-96100af0beda"); +const LLUUID ANIM_AGENT_RPS_COUNTDOWN = LLUUID("35db4f7e-28c2-6679-cea9-3ee108f7fc7f"); +const LLUUID ANIM_AGENT_RPS_PAPER = LLUUID("0836b67f-7f7b-f37b-c00a-460dc1521f5a"); +const LLUUID ANIM_AGENT_RPS_ROCK = LLUUID("42dd95d5-0bc6-6392-f650-777304946c0f"); +const LLUUID ANIM_AGENT_RPS_SCISSORS = LLUUID("16803a9f-5140-e042-4d7b-d28ba247c325"); +const LLUUID ANIM_AGENT_RUN = LLUUID("05ddbff8-aaa9-92a1-2b74-8fe77a29b445"); +const LLUUID ANIM_AGENT_SAD = LLUUID("0eb702e2-cc5a-9a88-56a5-661a55c0676a"); +const LLUUID ANIM_AGENT_SALUTE = LLUUID("cd7668a6-7011-d7e2-ead8-fc69eff1a104"); +const LLUUID ANIM_AGENT_SHOOT_BOW_L = LLUUID("e04d450d-fdb5-0432-fd68-818aaf5935f8"); +const LLUUID ANIM_AGENT_SHOUT = LLUUID("6bd01860-4ebd-127a-bb3d-d1427e8e0c42"); +const LLUUID ANIM_AGENT_SHRUG = LLUUID("70ea714f-3a97-d742-1b01-590a8fcd1db5"); +const LLUUID ANIM_AGENT_SIT = LLUUID("1a5fe8ac-a804-8a5d-7cbd-56bd83184568"); +const LLUUID ANIM_AGENT_SIT_FEMALE = LLUUID("b1709c8d-ecd3-54a1-4f28-d55ac0840782"); +const LLUUID ANIM_AGENT_SIT_GENERIC = LLUUID("245f3c54-f1c0-bf2e-811f-46d8eeb386e7"); +const LLUUID ANIM_AGENT_SIT_GROUND = LLUUID("1c7600d6-661f-b87b-efe2-d7421eb93c86"); +const LLUUID ANIM_AGENT_SIT_GROUND_CONSTRAINED = LLUUID("1a2bd58e-87ff-0df8-0b4c-53e047b0bb6e"); +const LLUUID ANIM_AGENT_SIT_TO_STAND = LLUUID("a8dee56f-2eae-9e7a-05a2-6fb92b97e21e"); +const LLUUID ANIM_AGENT_SLEEP = LLUUID("f2bed5f9-9d44-39af-b0cd-257b2a17fe40"); +const LLUUID ANIM_AGENT_SMOKE_IDLE = LLUUID("d2f2ee58-8ad1-06c9-d8d3-3827ba31567a"); +const LLUUID ANIM_AGENT_SMOKE_INHALE = LLUUID("6802d553-49da-0778-9f85-1599a2266526"); +const LLUUID ANIM_AGENT_SMOKE_THROW_DOWN = LLUUID("0a9fb970-8b44-9114-d3a9-bf69cfe804d6"); +const LLUUID ANIM_AGENT_SNAPSHOT = LLUUID("eae8905b-271a-99e2-4c0e-31106afd100c"); +const LLUUID ANIM_AGENT_STAND = LLUUID("2408fe9e-df1d-1d7d-f4ff-1384fa7b350f"); +const LLUUID ANIM_AGENT_STANDUP = LLUUID("3da1d753-028a-5446-24f3-9c9b856d9422"); +const LLUUID ANIM_AGENT_STAND_1 = LLUUID("15468e00-3400-bb66-cecc-646d7c14458e"); +const LLUUID ANIM_AGENT_STAND_2 = LLUUID("370f3a20-6ca6-9971-848c-9a01bc42ae3c"); +const LLUUID ANIM_AGENT_STAND_3 = LLUUID("42b46214-4b44-79ae-deb8-0df61424ff4b"); +const LLUUID ANIM_AGENT_STAND_4 = LLUUID("f22fed8b-a5ed-2c93-64d5-bdd8b93c889f"); +const LLUUID ANIM_AGENT_STRETCH = LLUUID("80700431-74ec-a008-14f8-77575e73693f"); +const LLUUID ANIM_AGENT_STRIDE = LLUUID("1cb562b0-ba21-2202-efb3-30f82cdf9595"); +const LLUUID ANIM_AGENT_SURF = LLUUID("41426836-7437-7e89-025d-0aa4d10f1d69"); +const LLUUID ANIM_AGENT_SURPRISE = LLUUID("313b9881-4302-73c0-c7d0-0e7a36b6c224"); +const LLUUID ANIM_AGENT_SWORD_STRIKE = LLUUID("85428680-6bf9-3e64-b489-6f81087c24bd"); +const LLUUID ANIM_AGENT_TALK = LLUUID("5c682a95-6da4-a463-0bf6-0f5b7be129d1"); +const LLUUID ANIM_AGENT_TANTRUM = LLUUID("11000694-3f41-adc2-606b-eee1d66f3724"); +const LLUUID ANIM_AGENT_THROW_R = LLUUID("aa134404-7dac-7aca-2cba-435f9db875ca"); +const LLUUID ANIM_AGENT_TRYON_SHIRT = LLUUID("83ff59fe-2346-f236-9009-4e3608af64c1"); +const LLUUID ANIM_AGENT_TURNLEFT = LLUUID("56e0ba0d-4a9f-7f27-6117-32f2ebbf6135"); +const LLUUID ANIM_AGENT_TURNRIGHT = LLUUID("2d6daa51-3192-6794-8e2e-a15f8338ec30"); +const LLUUID ANIM_AGENT_TYPE = LLUUID("c541c47f-e0c0-058b-ad1a-d6ae3a4584d9"); +const LLUUID ANIM_AGENT_WALK = LLUUID("6ed24bd8-91aa-4b12-ccc7-c97c857ab4e0"); +const LLUUID ANIM_AGENT_WHISPER = LLUUID("7693f268-06c7-ea71-fa21-2b30d6533f8f"); +const LLUUID ANIM_AGENT_WHISTLE = LLUUID("b1ed7982-c68e-a982-7561-52a88a5298c0"); +const LLUUID ANIM_AGENT_WINK = LLUUID("869ecdad-a44b-671e-3266-56aef2e3ac2e"); +const LLUUID ANIM_AGENT_WINK_HOLLYWOOD = LLUUID("c0c4030f-c02b-49de-24ba-2331f43fe41c"); +const LLUUID ANIM_AGENT_WORRY = LLUUID("9f496bd2-589a-709f-16cc-69bf7df1d36c"); +const LLUUID ANIM_AGENT_YES = LLUUID("15dd911d-be82-2856-26db-27659b142875"); +const LLUUID ANIM_AGENT_YES_HAPPY = LLUUID("b8c8b2a3-9008-1771-3bfc-90924955ab2d"); +const LLUUID ANIM_AGENT_YOGA_FLOAT = LLUUID("42ecd00b-9947-a97c-400a-bbc9174c7aeb"); + +extern LLUUID AGENT_WALK_ANIMS[]; +extern S32 NUM_AGENT_WALK_ANIMS; + +extern LLUUID AGENT_GUN_HOLD_ANIMS[]; +extern S32 NUM_AGENT_GUN_HOLD_ANIMS; + +extern LLUUID AGENT_GUN_AIM_ANIMS[]; +extern S32 NUM_AGENT_GUN_AIM_ANIMS; + +extern LLUUID AGENT_NO_ROTATE_ANIMS[]; +extern S32 NUM_AGENT_NO_ROTATE_ANIMS; + +extern LLUUID AGENT_STAND_ANIMS[]; +extern S32 NUM_AGENT_STAND_ANIMS; + +class LLAnimationLibrary +{ +private: + LLStringTable mAnimStringTable; + + typedef std::map anim_map_t; + anim_map_t mAnimMap; + +public: + LLAnimationLibrary(); + ~LLAnimationLibrary(); + + //----------------------------------------------------------------------------- + // Return the text name of a single animation state, + // Return NULL if the state is invalid + //----------------------------------------------------------------------------- + const char *animStateToString( const LLUUID& state ); + + //----------------------------------------------------------------------------- + // Return the animation state for the given name. + // Retun NULL if the name is invalid. + //----------------------------------------------------------------------------- + LLUUID stringToAnimState( const char *name, BOOL allow_ids = TRUE ); +}; + +struct LLAnimStateEntry +{ + LLAnimStateEntry(const char* label, const char* name, const LLUUID& id) + : mLabel(label), + mName(name), + mID(id) + { } + + const char* mLabel; + const char* mName; + const LLUUID mID; +}; + +// Animation states that the user can trigger +extern const LLAnimStateEntry gUserAnimStates[]; +extern const S32 gUserAnimStatesCount; +extern LLAnimationLibrary gAnimLibrary; + + +#endif // LL_LLANIMATIONSTATES_H + + + diff --git a/indra/llcharacter/llbvhloader.cpp b/indra/llcharacter/llbvhloader.cpp new file mode 100644 index 0000000000..4d4ad39080 --- /dev/null +++ b/indra/llcharacter/llbvhloader.cpp @@ -0,0 +1,1489 @@ +/** + * @file llbvhloader.cpp + * @brief Translates a BVH files to LindenLabAnimation format. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llbvhloader.h" +#include "lldatapacker.h" +#include "lldir.h" +#include "llkeyframemotion.h" +#include "llquantize.h" +#include "llstl.h" +#include "llapr.h" + +#include +#include +#include +#include +#include + +using namespace std; + +#define INCHES_TO_METERS 0.02540005f + +const F32 POSITION_KEYFRAME_THRESHOLD = 0.03f; +const F32 ROTATION_KEYFRAME_THRESHOLD = 0.01f; + +const F32 POSITION_MOTION_THRESHOLD = 0.001f; +const F32 ROTATION_MOTION_THRESHOLD = 0.001f; + +char gInFile[1024]; /* Flawfinder: ignore */ +char gOutFile[1024]; /* Flawfinder: ignore */ + +//------------------------------------------------------------------------ +// Status Codes +//------------------------------------------------------------------------ +char *LLBVHLoader::ST_OK = "Ok"; +char *LLBVHLoader::ST_EOF = "Premature end of file."; +char *LLBVHLoader::ST_NO_CONSTRAINT = "Can't read constraint definition."; +char *LLBVHLoader::ST_NO_FILE = "Can't open BVH file."; +char *LLBVHLoader::ST_NO_HIER = "Invalid HIERARCHY header."; +char *LLBVHLoader::ST_NO_JOINT = "Can't find ROOT or JOINT."; +char *LLBVHLoader::ST_NO_NAME = "Can't get JOINT name."; +char *LLBVHLoader::ST_NO_OFFSET = "Can't find OFFSET."; +char *LLBVHLoader::ST_NO_CHANNELS = "Can't find CHANNELS."; +char *LLBVHLoader::ST_NO_ROTATION = "Can't get rotation order."; +char *LLBVHLoader::ST_NO_AXIS = "Can't get rotation axis."; +char *LLBVHLoader::ST_NO_MOTION = "Can't find MOTION."; +char *LLBVHLoader::ST_NO_FRAMES = "Can't get number of frames."; +char *LLBVHLoader::ST_NO_FRAME_TIME = "Can't get frame time."; +char *LLBVHLoader::ST_NO_POS = "Can't get position values."; +char *LLBVHLoader::ST_NO_ROT = "Can't get rotation values."; +char *LLBVHLoader::ST_NO_XLT_FILE = "Can't open translation file."; +char *LLBVHLoader::ST_NO_XLT_HEADER = "Can't read translation header."; +char *LLBVHLoader::ST_NO_XLT_NAME = "Can't read translation names."; +char *LLBVHLoader::ST_NO_XLT_IGNORE = "Can't read translation ignore value."; +char *LLBVHLoader::ST_NO_XLT_RELATIVE = "Can't read translation relative value."; +char *LLBVHLoader::ST_NO_XLT_OUTNAME = "Can't read translation outname value."; +char *LLBVHLoader::ST_NO_XLT_MATRIX = "Can't read translation matrix."; +char *LLBVHLoader::ST_NO_XLT_MERGECHILD = "Can't get mergechild name."; +char *LLBVHLoader::ST_NO_XLT_MERGEPARENT = "Can't get mergeparent name."; +char *LLBVHLoader::ST_NO_XLT_PRIORITY = "Can't get priority value."; +char *LLBVHLoader::ST_NO_XLT_LOOP = "Can't get loop value."; +char *LLBVHLoader::ST_NO_XLT_EASEIN = "Can't get easeIn values."; +char *LLBVHLoader::ST_NO_XLT_EASEOUT = "Can't get easeOut values."; +char *LLBVHLoader::ST_NO_XLT_HAND = "Can't get hand morph value."; +char *LLBVHLoader::ST_NO_XLT_EMOTE = "Can't read emote name."; + +//------------------------------------------------------------------------ +// find_next_whitespace() +//------------------------------------------------------------------------ +const char *find_next_whitespace(const char *p) +{ + while(*p && isspace(*p)) p++; + while(*p && !isspace(*p)) p++; + return p; +} + + +//------------------------------------------------------------------------ +// bvhStringToOrder() +// +// XYZ order in BVH files must be passed to mayaQ() as ZYX. +// This function reverses the input string before passing it on +// to StringToOrder(). +//------------------------------------------------------------------------ +LLQuaternion::Order bvhStringToOrder( char *str ) +{ + char order[4]; /* Flawfinder: ignore */ + order[0] = str[2]; + order[1] = str[1]; + order[2] = str[0]; + order[3] = 0; + LLQuaternion::Order retVal = StringToOrder( order ); + return retVal; +} + +//----------------------------------------------------------------------------- +// LLBVHLoader() +//----------------------------------------------------------------------------- +LLBVHLoader::LLBVHLoader(const char* buffer) +{ + reset(); + + mStatus = loadTranslationTable("anim.ini"); + + if (mStatus == LLBVHLoader::ST_NO_XLT_FILE) + { + llwarns << "NOTE: No translation table found." << llendl; + return; + } + else + { + if (mStatus != LLBVHLoader::ST_OK) + { + llwarns << "ERROR: [line: " << getLineNumber() << "] " << mStatus << llendl; + return; + } + } + + char error_text[128]; /* Flawfinder: ignore */ + S32 error_line; + mStatus = loadBVHFile(buffer, error_text, error_line); + if (mStatus != LLBVHLoader::ST_OK) + { + llwarns << "ERROR: [line: " << getLineNumber() << "] " << mStatus << llendl; + return; + } + + applyTranslations(); + optimize(); + + mInitialized = TRUE; +} + +LLBVHLoader::~LLBVHLoader() +{ + std::for_each(mJoints.begin(),mJoints.end(),DeletePointer()); +} + +//------------------------------------------------------------------------ +// LLBVHLoader::loadTranslationTable() +//------------------------------------------------------------------------ +LLBVHLoader::Status LLBVHLoader::loadTranslationTable(const char *fileName) +{ + mLineNumber = 0; + mTranslations.clear(); + mConstraints.clear(); + + //-------------------------------------------------------------------- + // open file + //-------------------------------------------------------------------- + char path[LL_MAX_PATH]; /* Flawfinder: ignore */ + + snprintf( path, sizeof(path), "%s", + gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,fileName).c_str()); /* Flawfinder: ignore */ + + + apr_file_t *fp = ll_apr_file_open(path, LL_APR_R); + if (!fp) + return ST_NO_XLT_FILE; + + llinfos << "NOTE: Loading translation table: " << fileName << llendl; + + //-------------------------------------------------------------------- + // register file to be closed on function exit + //-------------------------------------------------------------------- + FileCloser fileCloser(fp); + + //-------------------------------------------------------------------- + // load header + //-------------------------------------------------------------------- + if ( ! getLine(fp) ) + return ST_EOF; + if ( strncmp(mLine, "Translations 1.0", 16) ) + return ST_NO_XLT_HEADER; + + //-------------------------------------------------------------------- + // load data one line at a time + //-------------------------------------------------------------------- + BOOL loadingGlobals = FALSE; + Translation *trans = NULL; + while ( getLine(fp) ) + { + //---------------------------------------------------------------- + // check the 1st token on the line to determine if it's empty or a comment + //---------------------------------------------------------------- + char token[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %127s", token) != 1 ) + continue; + + if (token[0] == '#') + continue; + + //---------------------------------------------------------------- + // check if a [jointName] or [GLOBALS] was specified. + //---------------------------------------------------------------- + if (token[0] == '[') + { + char name[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " [%127[^]]", name) != 1 ) + return ST_NO_XLT_NAME; + + if (strcmp(name, "GLOBALS")==0) + { + loadingGlobals = TRUE; + continue; + } + else + { + loadingGlobals = FALSE; + Translation &newTrans = mTranslations[ name ]; + trans = &newTrans; + continue; + } + } + + //---------------------------------------------------------------- + // check for optional emote + //---------------------------------------------------------------- + if (loadingGlobals && LLString::compareInsensitive(token, "emote")==0) + { + char emote_str[1024]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %*s = %1023s", emote_str) != 1 ) + return ST_NO_XLT_EMOTE; + + mEmoteName.assign( emote_str ); +// llinfos << "NOTE: Emote: " << mEmoteName.c_str() << llendl; + continue; + } + + + //---------------------------------------------------------------- + // check for global priority setting + //---------------------------------------------------------------- + if (loadingGlobals && LLString::compareInsensitive(token, "priority")==0) + { + S32 priority; + if ( sscanf(mLine, " %*s = %d", &priority) != 1 ) + return ST_NO_XLT_PRIORITY; + + mPriority = priority; +// llinfos << "NOTE: Priority: " << mPriority << llendl; + continue; + } + + //---------------------------------------------------------------- + // check for global loop setting + //---------------------------------------------------------------- + if (loadingGlobals && LLString::compareInsensitive(token, "loop")==0) + { + char trueFalse[128]; /* Flawfinder: ignore */ + trueFalse[0] = '\0'; + + F32 loop_in = 0.f; + F32 loop_out = 1.f; + + if ( sscanf(mLine, " %*s = %f %f", &loop_in, &loop_out) == 2 ) + { + mLoop = TRUE; + } + else if ( sscanf(mLine, " %*s = %127s", trueFalse) == 1 ) + { + mLoop = (LLString::compareInsensitive(trueFalse, "true")==0); + } + else + { + return ST_NO_XLT_LOOP; + } + + mLoopInPoint = loop_in * mDuration; + mLoopOutPoint = loop_out * mDuration; + + continue; + } + + //---------------------------------------------------------------- + // check for global easeIn setting + //---------------------------------------------------------------- + if (loadingGlobals && LLString::compareInsensitive(token, "easein")==0) + { + F32 duration; + char type[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %*s = %f %127s", &duration, type) != 2 ) + return ST_NO_XLT_EASEIN; + + mEaseIn = duration; + continue; + } + + //---------------------------------------------------------------- + // check for global easeOut setting + //---------------------------------------------------------------- + if (loadingGlobals && LLString::compareInsensitive(token, "easeout")==0) + { + F32 duration; + char type[128]; + if ( sscanf(mLine, " %*s = %f %127s", &duration, type) != 2 ) + return ST_NO_XLT_EASEOUT; + + mEaseOut = duration; + continue; + } + + //---------------------------------------------------------------- + // check for global handMorph setting + //---------------------------------------------------------------- + if (loadingGlobals && LLString::compareInsensitive(token, "hand")==0) + { + S32 handMorph; + if (sscanf(mLine, " %*s = %d", &handMorph) != 1) + return ST_NO_XLT_HAND; + + mHand = handMorph; + continue; + } + + if (loadingGlobals && LLString::compareInsensitive(token, "constraint")==0) + { + Constraint constraint; + + // try reading optional target direction + if(sscanf( + mLine, + " %*s = %d %f %f %f %f %15s %f %f %f %15s %f %f %f %f %f %f", + &constraint.mChainLength, + &constraint.mEaseInStart, + &constraint.mEaseInStop, + &constraint.mEaseOutStart, + &constraint.mEaseOutStop, + constraint.mSourceJointName, + &constraint.mSourceOffset.mV[VX], + &constraint.mSourceOffset.mV[VY], + &constraint.mSourceOffset.mV[VZ], + constraint.mTargetJointName, + &constraint.mTargetOffset.mV[VX], + &constraint.mTargetOffset.mV[VY], + &constraint.mTargetOffset.mV[VZ], + &constraint.mTargetDir.mV[VX], + &constraint.mTargetDir.mV[VY], + &constraint.mTargetDir.mV[VZ]) != 16) + { + if(sscanf( + mLine, + " %*s = %d %f %f %f %f %15s %f %f %f %15s %f %f %f", + &constraint.mChainLength, + &constraint.mEaseInStart, + &constraint.mEaseInStop, + &constraint.mEaseOutStart, + &constraint.mEaseOutStop, + constraint.mSourceJointName, + &constraint.mSourceOffset.mV[VX], + &constraint.mSourceOffset.mV[VY], + &constraint.mSourceOffset.mV[VZ], + constraint.mTargetJointName, + &constraint.mTargetOffset.mV[VX], + &constraint.mTargetOffset.mV[VY], + &constraint.mTargetOffset.mV[VZ]) != 13) + { + return ST_NO_CONSTRAINT; + } + } + else + { + // normalize direction + if (!constraint.mTargetDir.isExactlyZero()) + { + constraint.mTargetDir.normVec(); + } + + } + + constraint.mConstraintType = CONSTRAINT_TYPE_POINT; + mConstraints.push_back(constraint); + continue; + } + + if (loadingGlobals && LLString::compareInsensitive(token, "planar_constraint")==0) + { + Constraint constraint; + + // try reading optional target direction + if(sscanf( + mLine, + " %*s = %d %f %f %f %f %15s %f %f %f %15s %f %f %f %f %f %f", + &constraint.mChainLength, + &constraint.mEaseInStart, + &constraint.mEaseInStop, + &constraint.mEaseOutStart, + &constraint.mEaseOutStop, + constraint.mSourceJointName, + &constraint.mSourceOffset.mV[VX], + &constraint.mSourceOffset.mV[VY], + &constraint.mSourceOffset.mV[VZ], + constraint.mTargetJointName, + &constraint.mTargetOffset.mV[VX], + &constraint.mTargetOffset.mV[VY], + &constraint.mTargetOffset.mV[VZ], + &constraint.mTargetDir.mV[VX], + &constraint.mTargetDir.mV[VY], + &constraint.mTargetDir.mV[VZ]) != 16) + { + if(sscanf( + mLine, + " %*s = %d %f %f %f %f %15s %f %f %f %15s %f %f %f", + &constraint.mChainLength, + &constraint.mEaseInStart, + &constraint.mEaseInStop, + &constraint.mEaseOutStart, + &constraint.mEaseOutStop, + constraint.mSourceJointName, + &constraint.mSourceOffset.mV[VX], + &constraint.mSourceOffset.mV[VY], + &constraint.mSourceOffset.mV[VZ], + constraint.mTargetJointName, + &constraint.mTargetOffset.mV[VX], + &constraint.mTargetOffset.mV[VY], + &constraint.mTargetOffset.mV[VZ]) != 13) + { + return ST_NO_CONSTRAINT; + } + } + else + { + // normalize direction + if (!constraint.mTargetDir.isExactlyZero()) + { + constraint.mTargetDir.normVec(); + } + + } + + constraint.mConstraintType = CONSTRAINT_TYPE_PLANE; + mConstraints.push_back(constraint); + continue; + } + + + //---------------------------------------------------------------- + // at this point there must be a valid trans pointer + //---------------------------------------------------------------- + if ( ! trans ) + return ST_NO_XLT_NAME; + + //---------------------------------------------------------------- + // check for ignore flag + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "ignore")==0 ) + { + char trueFalse[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %*s = %127s", trueFalse) != 1 ) + return ST_NO_XLT_IGNORE; + + trans->mIgnore = (LLString::compareInsensitive(trueFalse, "true")==0); + continue; + } + + //---------------------------------------------------------------- + // check for relativepos flag + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "relativepos")==0 ) + { + F32 x, y, z; + char relpos[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %*s = %f %f %f", &x, &y, &z) == 3 ) + { + trans->mRelativePosition.setVec( x, y, z ); + } + else if ( sscanf(mLine, " %*s = %127s", relpos) == 1 ) + { + if ( LLString::compareInsensitive(relpos, "firstkey")==0 ) + { + trans->mRelativePositionKey = TRUE; + } + else + { + return ST_NO_XLT_RELATIVE; + } + } + else + { + return ST_NO_XLT_RELATIVE; + } + + continue; + } + + //---------------------------------------------------------------- + // check for relativerot flag + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "relativerot")==0 ) + { + //F32 x, y, z; + char relpos[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %*s = %127s", relpos) == 1 ) + { + if ( LLString::compareInsensitive(relpos, "firstkey")==0 ) + { + trans->mRelativeRotationKey = TRUE; + } + else + { + return ST_NO_XLT_RELATIVE; + } + } + else + { + return ST_NO_XLT_RELATIVE; + } + + continue; + } + + //---------------------------------------------------------------- + // check for outname value + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "outname")==0 ) + { + char outName[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %*s = %127s", outName) != 1 ) + return ST_NO_XLT_OUTNAME; + + trans->mOutName = outName; + continue; + } + + //---------------------------------------------------------------- + // check for frame matrix value + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "frame")==0 ) + { + LLMatrix3 fm; + if ( sscanf(mLine, " %*s = %f %f %f, %f %f %f, %f %f %f", + &fm.mMatrix[0][0], &fm.mMatrix[0][1], &fm.mMatrix[0][2], + &fm.mMatrix[1][0], &fm.mMatrix[1][1], &fm.mMatrix[1][2], + &fm.mMatrix[2][0], &fm.mMatrix[2][1], &fm.mMatrix[2][2] ) != 9 ) + return ST_NO_XLT_MATRIX; + + trans->mFrameMatrix = fm; + continue; + } + + //---------------------------------------------------------------- + // check for offset matrix value + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "offset")==0 ) + { + LLMatrix3 om; + if ( sscanf(mLine, " %*s = %f %f %f, %f %f %f, %f %f %f", + &om.mMatrix[0][0], &om.mMatrix[0][1], &om.mMatrix[0][2], + &om.mMatrix[1][0], &om.mMatrix[1][1], &om.mMatrix[1][2], + &om.mMatrix[2][0], &om.mMatrix[2][1], &om.mMatrix[2][2] ) != 9 ) + return ST_NO_XLT_MATRIX; + + trans->mOffsetMatrix = om; + continue; + } + + //---------------------------------------------------------------- + // check for mergeparent value + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "mergeparent")==0 ) + { + char mergeParentName[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %*s = %127s", mergeParentName) != 1 ) + return ST_NO_XLT_MERGEPARENT; + + trans->mMergeParentName = mergeParentName; + continue; + } + + //---------------------------------------------------------------- + // check for mergechild value + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "mergechild")==0 ) + { + char mergeChildName[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %*s = %127s", mergeChildName) != 1 ) + return ST_NO_XLT_MERGECHILD; + + trans->mMergeChildName = mergeChildName; + continue; + } + + //---------------------------------------------------------------- + // check for per-joint priority + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "priority")==0 ) + { + S32 priority; + if ( sscanf(mLine, " %*s = %d", &priority) != 1 ) + return ST_NO_XLT_PRIORITY; + + trans->mPriorityModifier = priority; + continue; + } + + } + return ST_OK; +} + + +//------------------------------------------------------------------------ +// LLBVHLoader::loadBVHFile() +//------------------------------------------------------------------------ +LLBVHLoader::Status LLBVHLoader::loadBVHFile(const char *buffer, char* error_text, S32 &err_line) +{ + std::string line; + + err_line = 0; + error_text[127] = '\0'; + + std::string str(buffer); + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("\r\n"); + tokenizer tokens(str, sep); + tokenizer::iterator iter = tokens.begin(); + + mLineNumber = 0; + mJoints.clear(); + + std::vector parent_joints; + + //-------------------------------------------------------------------- + // consume hierarchy + //-------------------------------------------------------------------- + if (iter == tokens.end()) + return ST_EOF; + line = (*(iter++)); + err_line++; + + if ( !strstr(line.c_str(), "HIERARCHY") ) + { +// llinfos << line << llendl; + return ST_NO_HIER; + } + + //-------------------------------------------------------------------- + // consume joints + //-------------------------------------------------------------------- + while (TRUE) + { + //---------------------------------------------------------------- + // get next line + //---------------------------------------------------------------- + if (iter == tokens.end()) + return ST_EOF; + line = (*(iter++)); + err_line++; + + //---------------------------------------------------------------- + // consume } + //---------------------------------------------------------------- + if ( strstr(line.c_str(), "}") ) + { + if (parent_joints.size() > 0) + { + parent_joints.pop_back(); + } + continue; + } + + //---------------------------------------------------------------- + // if MOTION, break out + //---------------------------------------------------------------- + if ( strstr(line.c_str(), "MOTION") ) + break; + + //---------------------------------------------------------------- + // it must be either ROOT or JOINT or EndSite + //---------------------------------------------------------------- + if ( strstr(line.c_str(), "ROOT") ) + { + } + else if ( strstr(line.c_str(), "JOINT") ) + { + } + else if ( strstr(line.c_str(), "End Site") ) + { + iter++; // { + iter++; // OFFSET + S32 depth = 0; + for (S32 j = (S32)parent_joints.size() - 1; j >= 0; j--) + { + Joint *joint = mJoints[parent_joints[j]]; + if (depth > joint->mChildTreeMaxDepth) + { + joint->mChildTreeMaxDepth = depth; + } + depth++; + } + continue; + } + else + { + strncpy(error_text, line.c_str(), 127); /* Flawfinder: ignore */ + return ST_NO_JOINT; + } + + //---------------------------------------------------------------- + // get the joint name + //---------------------------------------------------------------- + char jointName[80]; /* Flawfinder: ignore */ + if ( sscanf(line.c_str(), "%*s %79s", jointName) != 1 ) + { + strncpy(error_text, line.c_str(), 127); /* Flawfinder: ignore */ + return ST_NO_NAME; + } + + //---------------------------------------------------------------- + // add a set of keyframes for this joint + //---------------------------------------------------------------- + mJoints.push_back( new Joint( jointName ) ); + Joint *joint = mJoints.back(); + + S32 depth = 1; + for (S32 j = (S32)parent_joints.size() - 1; j >= 0; j--) + { + Joint *pjoint = mJoints[parent_joints[j]]; + if (depth > pjoint->mChildTreeMaxDepth) + { + pjoint->mChildTreeMaxDepth = depth; + } + depth++; + } + + //---------------------------------------------------------------- + // get next line + //---------------------------------------------------------------- + if (iter == tokens.end()) + { + return ST_EOF; + } + line = (*(iter++)); + err_line++; + + //---------------------------------------------------------------- + // it must be { + //---------------------------------------------------------------- + if ( !strstr(line.c_str(), "{") ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_OFFSET; + } + else + { + parent_joints.push_back((S32)mJoints.size() - 1); + } + + //---------------------------------------------------------------- + // get next line + //---------------------------------------------------------------- + if (iter == tokens.end()) + { + return ST_EOF; + } + line = (*(iter++)); + err_line++; + + //---------------------------------------------------------------- + // it must be OFFSET + //---------------------------------------------------------------- + if ( !strstr(line.c_str(), "OFFSET") ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_OFFSET; + } + + //---------------------------------------------------------------- + // get next line + //---------------------------------------------------------------- + if (iter == tokens.end()) + { + return ST_EOF; + } + line = (*(iter++)); + err_line++; + + //---------------------------------------------------------------- + // it must be CHANNELS + //---------------------------------------------------------------- + if ( !strstr(line.c_str(), "CHANNELS") ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_CHANNELS; + } + + //---------------------------------------------------------------- + // get rotation order + //---------------------------------------------------------------- + const char *p = line.c_str(); + for (S32 i=0; i<3; i++) + { + p = strstr(p, "rotation"); + if (!p) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_ROTATION; + } + + const char axis = *(p - 1); + if ((axis != 'X') && (axis != 'Y') && (axis != 'Z')) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_AXIS; + } + + joint->mOrder[i] = axis; + + p++; + } + } + + //-------------------------------------------------------------------- + // consume motion + //-------------------------------------------------------------------- + if ( !strstr(line.c_str(), "MOTION") ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_MOTION; + } + + //-------------------------------------------------------------------- + // get number of frames + //-------------------------------------------------------------------- + if (iter == tokens.end()) + { + return ST_EOF; + } + line = (*(iter++)); + err_line++; + + if ( !strstr(line.c_str(), "Frames:") ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_FRAMES; + } + + if ( sscanf(line.c_str(), "Frames: %d", &mNumFrames) != 1 ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_FRAMES; + } + + //-------------------------------------------------------------------- + // get frame time + //-------------------------------------------------------------------- + if (iter == tokens.end()) + { + return ST_EOF; + } + line = (*(iter++)); + err_line++; + + if ( !strstr(line.c_str(), "Frame Time:") ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_FRAME_TIME; + } + + if ( sscanf(line.c_str(), "Frame Time: %f", &mFrameTime) != 1 ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_FRAME_TIME; + } + + mDuration = (F32)mNumFrames * mFrameTime; + if (!mLoop) + { + mLoopOutPoint = mDuration; + } + + //-------------------------------------------------------------------- + // load frames + //-------------------------------------------------------------------- + for (S32 i=0; imKeys.push_back( Key() ); + Key &key = joint->mKeys.back(); + + // get 3 pos values for root joint only + if (j==0) + { + if ( sscanf(p, "%f %f %f", key.mPos, key.mPos+1, key.mPos+2) != 3 ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_POS; + } + } + + // skip to next 3 values in the line + p = find_next_whitespace(p); + if (!p) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_ROT; + } + p = find_next_whitespace(++p); + if (!p) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_ROT; + } + p = find_next_whitespace(++p); + if (!p) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_ROT; + } + + // get 3 rot values for joint + F32 rot[3]; + if ( sscanf(p, " %f %f %f", rot, rot+1, rot+2) != 3 ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_ROT; + } + + p++; + + key.mRot[ joint->mOrder[0]-'X' ] = rot[0]; + key.mRot[ joint->mOrder[1]-'X' ] = rot[1]; + key.mRot[ joint->mOrder[2]-'X' ] = rot[2]; + } + } + + return ST_OK; +} + + +//------------------------------------------------------------------------ +// LLBVHLoader::applyTranslation() +//------------------------------------------------------------------------ +void LLBVHLoader::applyTranslations() +{ + JointVector::iterator ji; + for (ji = mJoints.begin(); ji != mJoints.end(); ++ji ) + { + Joint *joint = *ji; + //---------------------------------------------------------------- + // Look for a translation for this joint. + // If none, skip to next joint + //---------------------------------------------------------------- + TranslationMap::iterator ti = mTranslations.find( joint->mName ); + if ( ti == mTranslations.end() ) + { + continue; + } + + Translation &trans = ti->second; + + //---------------------------------------------------------------- + // Set the ignore flag if necessary + //---------------------------------------------------------------- + if ( trans.mIgnore ) + { + //llinfos << "NOTE: Ignoring " << joint->mName.c_str() << llendl; + joint->mIgnore = TRUE; + continue; + } + + //---------------------------------------------------------------- + // Set the output name + //---------------------------------------------------------------- + if ( ! trans.mOutName.empty() ) + { + //llinfos << "NOTE: Changing " << joint->mName.c_str() << " to " << trans.mOutName.c_str() << llendl; + joint->mOutName = trans.mOutName; + } + + //---------------------------------------------------------------- + // Set the ignorepos flag if necessary + //---------------------------------------------------------------- + if ( joint->mOutName == std::string("mPelvis") ) + { + joint->mIgnorePositions = FALSE; + } + + //---------------------------------------------------------------- + // Set the relativepos flags if necessary + //---------------------------------------------------------------- + if ( trans.mRelativePositionKey ) + { +// llinfos << "NOTE: Removing 1st position offset from all keys for " << joint->mOutName.c_str() << llendl; + joint->mRelativePositionKey = TRUE; + } + + if ( trans.mRelativeRotationKey ) + { +// llinfos << "NOTE: Removing 1st rotation from all keys for " << joint->mOutName.c_str() << llendl; + joint->mRelativeRotationKey = TRUE; + } + + if ( trans.mRelativePosition.magVec() > 0.0f ) + { + joint->mRelativePosition = trans.mRelativePosition; +// llinfos << "NOTE: Removing " << +// joint->mRelativePosition.mV[0] << " " << +// joint->mRelativePosition.mV[1] << " " << +// joint->mRelativePosition.mV[2] << +// " from all position keys in " << +// joint->mOutName.c_str() << llendl; + } + + //---------------------------------------------------------------- + // Set change of coordinate frame + //---------------------------------------------------------------- + joint->mFrameMatrix = trans.mFrameMatrix; + joint->mOffsetMatrix = trans.mOffsetMatrix; + + //---------------------------------------------------------------- + // Set mergeparent name + //---------------------------------------------------------------- + if ( ! trans.mMergeParentName.empty() ) + { +// llinfos << "NOTE: Merging " << joint->mOutName.c_str() << +// " with parent " << +// trans.mMergeParentName.c_str() << llendl; + joint->mMergeParentName = trans.mMergeParentName; + } + + //---------------------------------------------------------------- + // Set mergechild name + //---------------------------------------------------------------- + if ( ! trans.mMergeChildName.empty() ) + { +// llinfos << "NOTE: Merging " << joint->mName.c_str() << +// " with child " << trans.mMergeChildName.c_str() << llendl; + joint->mMergeChildName = trans.mMergeChildName; + } + + //---------------------------------------------------------------- + // Set joint priority + //---------------------------------------------------------------- + joint->mPriority = mPriority + trans.mPriorityModifier; + + } +} + +//----------------------------------------------------------------------------- +// LLBVHLoader::optimize() +//----------------------------------------------------------------------------- +void LLBVHLoader::optimize() +{ + //RN: assumes motion blend, which is the default now + if (!mLoop && mEaseIn + mEaseOut > mDuration && mDuration != 0.f) + { + F32 factor = mDuration / (mEaseIn + mEaseOut); + mEaseIn *= factor; + mEaseOut *= factor; + } + + JointVector::iterator ji; + for (ji = mJoints.begin(); ji != mJoints.end(); ++ji) + { + Joint *joint = *ji; + BOOL pos_changed = FALSE; + BOOL rot_changed = FALSE; + + if ( ! joint->mIgnore ) + { + joint->mNumPosKeys = 0; + joint->mNumRotKeys = 0; + LLQuaternion::Order order = bvhStringToOrder( joint->mOrder ); + + KeyVector::iterator first_key = joint->mKeys.begin(); + + // no keys? + if (first_key == joint->mKeys.end()) + { + joint->mIgnore = TRUE; + continue; + } + + LLVector3 first_frame_pos(first_key->mPos); + LLQuaternion first_frame_rot = mayaQ( first_key->mRot[0], first_key->mRot[1], first_key->mRot[2], order); + + // skip first key + KeyVector::iterator ki = joint->mKeys.begin(); + if (joint->mKeys.size() == 1) + { + //FIXME: use single frame to move pelvis + // if only one keyframe force output for this joint + rot_changed = TRUE; + } + else + { + // if more than one keyframe, use first frame as reference and skip to second + first_key->mIgnorePos = TRUE; + first_key->mIgnoreRot = TRUE; + ++ki; + } + + KeyVector::iterator ki_prev = ki; + KeyVector::iterator ki_last_good_pos = ki; + KeyVector::iterator ki_last_good_rot = ki; + S32 numPosFramesConsidered = 2; + S32 numRotFramesConsidered = 2; + + F32 rot_threshold = ROTATION_KEYFRAME_THRESHOLD / llmax((F32)joint->mChildTreeMaxDepth * 0.33f, 1.f); + + for (; ki != joint->mKeys.end(); ++ki) + { + if (ki_prev == ki_last_good_pos) + { + joint->mNumPosKeys++; + if (dist_vec(LLVector3(ki_prev->mPos), first_frame_pos) > POSITION_MOTION_THRESHOLD) + { + pos_changed = TRUE; + } + } + else + { + //check position for noticeable effect + LLVector3 test_pos(ki_prev->mPos); + LLVector3 last_good_pos(ki_last_good_pos->mPos); + LLVector3 current_pos(ki->mPos); + LLVector3 interp_pos = lerp(current_pos, last_good_pos, 1.f / (F32)numPosFramesConsidered); + + if (dist_vec(current_pos, first_frame_pos) > POSITION_MOTION_THRESHOLD) + { + pos_changed = TRUE; + } + + if (dist_vec(interp_pos, test_pos) < POSITION_KEYFRAME_THRESHOLD) + { + ki_prev->mIgnorePos = TRUE; + numPosFramesConsidered++; + } + else + { + numPosFramesConsidered = 2; + ki_last_good_pos = ki_prev; + joint->mNumPosKeys++; + } + } + + if (ki_prev == ki_last_good_rot) + { + joint->mNumRotKeys++; + LLQuaternion test_rot = mayaQ( ki_prev->mRot[0], ki_prev->mRot[1], ki_prev->mRot[2], order); + F32 x_delta = dist_vec(LLVector3::x_axis * first_frame_rot, LLVector3::x_axis * test_rot); + F32 y_delta = dist_vec(LLVector3::y_axis * first_frame_rot, LLVector3::y_axis * test_rot); + F32 rot_test = x_delta + y_delta; + + if (rot_test > ROTATION_MOTION_THRESHOLD) + { + rot_changed = TRUE; + } + } + else + { + //check rotation for noticeable effect + LLQuaternion test_rot = mayaQ( ki_prev->mRot[0], ki_prev->mRot[1], ki_prev->mRot[2], order); + LLQuaternion last_good_rot = mayaQ( ki_last_good_rot->mRot[0], ki_last_good_rot->mRot[1], ki_last_good_rot->mRot[2], order); + LLQuaternion current_rot = mayaQ( ki->mRot[0], ki->mRot[1], ki->mRot[2], order); + LLQuaternion interp_rot = lerp(1.f / (F32)numRotFramesConsidered, current_rot, last_good_rot); + + F32 x_delta; + F32 y_delta; + F32 rot_test; + + x_delta = dist_vec(LLVector3::x_axis * first_frame_rot, LLVector3::x_axis * test_rot); + y_delta = dist_vec(LLVector3::y_axis * first_frame_rot, LLVector3::y_axis * test_rot); + rot_test = x_delta + y_delta; + + if (rot_test > ROTATION_MOTION_THRESHOLD) + { + rot_changed = TRUE; + } + + x_delta = dist_vec(LLVector3::x_axis * interp_rot, LLVector3::x_axis * test_rot); + y_delta = dist_vec(LLVector3::y_axis * interp_rot, LLVector3::y_axis * test_rot); + rot_test = x_delta + y_delta; + + if (rot_test < rot_threshold) + { + ki_prev->mIgnoreRot = TRUE; + numRotFramesConsidered++; + } + else + { + numRotFramesConsidered = 2; + ki_last_good_rot = ki_prev; + joint->mNumRotKeys++; + } + } + + ki_prev = ki; + } + } + + // don't output joints with no motion + if (!(pos_changed || rot_changed)) + { + //llinfos << "Ignoring joint " << joint->mName << llendl; + joint->mIgnore = TRUE; + } + } +} + +void LLBVHLoader::reset() +{ + mLineNumber = 0; + mNumFrames = 0; + mFrameTime = 0.0f; + mDuration = 0.0f; + + mPriority = 2; + mLoop = FALSE; + mLoopInPoint = 0.f; + mLoopOutPoint = 0.f; + mEaseIn = 0.3f; + mEaseOut = 0.3f; + mHand = 1; + mInitialized = FALSE; + + mEmoteName = ""; +} + +//------------------------------------------------------------------------ +// LLBVHLoader::getLine() +//------------------------------------------------------------------------ +BOOL LLBVHLoader::getLine(apr_file_t* fp) +{ + if (apr_file_eof(fp) == APR_EOF) + { + return FALSE; + } + if ( apr_file_gets(mLine, BVH_PARSER_LINE_SIZE, fp) == APR_SUCCESS) + { + mLineNumber++; + return TRUE; + } + + return FALSE; +} + +// returns required size of output buffer +U32 LLBVHLoader::getOutputSize() +{ + LLDataPackerBinaryBuffer dp; + serialize(dp); + + return dp.getCurrentSize(); +} + +// writes contents to datapacker +BOOL LLBVHLoader::serialize(LLDataPacker& dp) +{ + JointVector::iterator ji; + KeyVector::iterator ki; + F32 time; + + // count number of non-ignored joints + S32 numJoints = 0; + for (ji=mJoints.begin(); ji!=mJoints.end(); ++ji) + { + Joint *joint = *ji; + if ( ! joint->mIgnore ) + numJoints++; + } + + // print header + dp.packU16(KEYFRAME_MOTION_VERSION, "version"); + dp.packU16(KEYFRAME_MOTION_SUBVERSION, "sub_version"); + dp.packS32(mPriority, "base_priority"); + dp.packF32(mDuration, "duration"); + dp.packString(mEmoteName.c_str(), "emote_name"); + dp.packF32(mLoopInPoint, "loop_in_point"); + dp.packF32(mLoopOutPoint, "loop_out_point"); + dp.packS32(mLoop, "loop"); + dp.packF32(mEaseIn, "ease_in_duration"); + dp.packF32(mEaseOut, "ease_out_duration"); + dp.packU32(mHand, "hand_pose"); + dp.packU32(numJoints, "num_joints"); + + for ( ji = mJoints.begin(); + ji != mJoints.end(); + ++ji ) + { + Joint *joint = *ji; + // if ignored, skip it + if ( joint->mIgnore ) + continue; + + LLQuaternion first_frame_rot; + LLQuaternion fixup_rot; + + dp.packString(joint->mOutName.c_str(), "joint_name"); + dp.packS32(joint->mPriority, "joint_priority"); + + // compute coordinate frame rotation + LLQuaternion frameRot( joint->mFrameMatrix ); + LLQuaternion frameRotInv = ~frameRot; + + LLQuaternion offsetRot( joint->mOffsetMatrix ); + + // find mergechild and mergeparent joints, if specified + LLQuaternion mergeParentRot; + LLQuaternion mergeChildRot; + Joint *mergeParent = NULL; + Joint *mergeChild = NULL; + + JointVector::iterator mji; + for (mji=mJoints.begin(); mji!=mJoints.end(); ++mji) + { + Joint *mjoint = *mji; + if ( !joint->mMergeParentName.empty() && (mjoint->mName == joint->mMergeParentName) ) + { + mergeParent = *mji; + } + if ( !joint->mMergeChildName.empty() && (mjoint->mName == joint->mMergeChildName) ) + { + mergeChild = *mji; + } + } + + dp.packS32(joint->mNumRotKeys, "num_rot_keys"); + + LLQuaternion::Order order = bvhStringToOrder( joint->mOrder ); + S32 outcount = 0; + S32 frame = 1; + for ( ki = joint->mKeys.begin(); + ki != joint->mKeys.end(); + ++ki ) + { + if ((frame == 1) && joint->mRelativeRotationKey) + { + first_frame_rot = mayaQ( ki->mRot[0], ki->mRot[1], ki->mRot[2], order); + + fixup_rot.shortestArc(LLVector3::z_axis * first_frame_rot * frameRot, LLVector3::z_axis); + } + + if (ki->mIgnoreRot) + { + frame++; + continue; + } + + time = (F32)frame * mFrameTime; + + if (mergeParent) + { + mergeParentRot = mayaQ( mergeParent->mKeys[frame-1].mRot[0], + mergeParent->mKeys[frame-1].mRot[1], + mergeParent->mKeys[frame-1].mRot[2], + bvhStringToOrder(mergeParent->mOrder) ); + LLQuaternion parentFrameRot( mergeParent->mFrameMatrix ); + LLQuaternion parentOffsetRot( mergeParent->mOffsetMatrix ); + mergeParentRot = ~parentFrameRot * mergeParentRot * parentFrameRot * parentOffsetRot; + } + else + { + mergeParentRot.loadIdentity(); + } + + if (mergeChild) + { + mergeChildRot = mayaQ( mergeChild->mKeys[frame-1].mRot[0], + mergeChild->mKeys[frame-1].mRot[1], + mergeChild->mKeys[frame-1].mRot[2], + bvhStringToOrder(mergeChild->mOrder) ); + LLQuaternion childFrameRot( mergeChild->mFrameMatrix ); + LLQuaternion childOffsetRot( mergeChild->mOffsetMatrix ); + mergeChildRot = ~childFrameRot * mergeChildRot * childFrameRot * childOffsetRot; + + } + else + { + mergeChildRot.loadIdentity(); + } + + LLQuaternion inRot = mayaQ( ki->mRot[0], ki->mRot[1], ki->mRot[2], order); + + LLQuaternion outRot = frameRotInv* mergeChildRot * inRot * mergeParentRot * ~first_frame_rot * frameRot * offsetRot; + + U16 time_short = F32_to_U16(time, 0.f, mDuration); + dp.packU16(time_short, "time"); + U16 x, y, z; + LLVector3 rot_vec = outRot.packToVector3(); + rot_vec.quantize16(-1.f, 1.f, -1.f, 1.f); + x = F32_to_U16(rot_vec.mV[VX], -1.f, 1.f); + y = F32_to_U16(rot_vec.mV[VY], -1.f, 1.f); + z = F32_to_U16(rot_vec.mV[VZ], -1.f, 1.f); + dp.packU16(x, "rot_angle_x"); + dp.packU16(y, "rot_angle_y"); + dp.packU16(z, "rot_angle_z"); + outcount++; + frame++; + } + + // output position keys (only for 1st joint) + if ( ji == mJoints.begin() && !joint->mIgnorePositions ) + { + dp.packS32(joint->mNumPosKeys, "num_pos_keys"); + + LLVector3 relPos = joint->mRelativePosition; + LLVector3 relKey; + + frame = 1; + for ( ki = joint->mKeys.begin(); + ki != joint->mKeys.end(); + ++ki ) + { + if ((frame == 1) && joint->mRelativePositionKey) + { + relKey.setVec(ki->mPos); + } + + if (ki->mIgnorePos) + { + frame++; + continue; + } + + time = (F32)frame * mFrameTime; + + LLVector3 inPos = (LLVector3(ki->mPos) - relKey) * ~first_frame_rot;// * fixup_rot; + LLVector3 outPos = inPos * frameRot * offsetRot; + + outPos *= INCHES_TO_METERS; + + outPos -= relPos; + outPos.clamp(-LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + + U16 time_short = F32_to_U16(time, 0.f, mDuration); + dp.packU16(time_short, "time"); + + U16 x, y, z; + outPos.quantize16(-LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET, -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + x = F32_to_U16(outPos.mV[VX], -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + y = F32_to_U16(outPos.mV[VY], -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + z = F32_to_U16(outPos.mV[VZ], -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + dp.packU16(x, "pos_x"); + dp.packU16(y, "pos_y"); + dp.packU16(z, "pos_z"); + + frame++; + } + } + else + { + dp.packS32(0, "num_pos_keys"); + } + } + + S32 num_constraints = (S32)mConstraints.size(); + dp.packS32(num_constraints, "num_constraints"); + + for (ConstraintVector::iterator constraint_it = mConstraints.begin(); + constraint_it != mConstraints.end(); + constraint_it++) + { + U8 byte = constraint_it->mChainLength; + dp.packU8(byte, "chain_lenght"); + + byte = constraint_it->mConstraintType; + dp.packU8(byte, "constraint_type"); + dp.packBinaryDataFixed((U8*)constraint_it->mSourceJointName, 16, "source_volume"); + dp.packVector3(constraint_it->mSourceOffset, "source_offset"); + dp.packBinaryDataFixed((U8*)constraint_it->mTargetJointName, 16, "target_volume"); + dp.packVector3(constraint_it->mTargetOffset, "target_offset"); + dp.packVector3(constraint_it->mTargetDir, "target_dir"); + dp.packF32(constraint_it->mEaseInStart, "ease_in_start"); + dp.packF32(constraint_it->mEaseInStop, "ease_in_stop"); + dp.packF32(constraint_it->mEaseOutStart, "ease_out_start"); + dp.packF32(constraint_it->mEaseOutStop, "ease_out_stop"); + } + + return TRUE; +} diff --git a/indra/llcharacter/llbvhloader.h b/indra/llcharacter/llbvhloader.h new file mode 100644 index 0000000000..7e00e1d5f4 --- /dev/null +++ b/indra/llcharacter/llbvhloader.h @@ -0,0 +1,282 @@ +/** + * @file llbvhloader.h + * @brief Translates a BVH files to LindenLabAnimation format. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLBVHLOADER_H +#define LL_LLBVHLOADER_H + +#include +#include +#include +#include +#include +#include "v3math.h" +#include "m3math.h" +#include "llmath.h" +#include "llapr.h" + +const S32 BVH_PARSER_LINE_SIZE = 2048; +const F32 MAX_ANIM_DURATION = 30.f; +class LLDataPacker; + +//------------------------------------------------------------------------ +// FileCloser +//------------------------------------------------------------------------ +class FileCloser +{ +public: + FileCloser( apr_file_t *file ) + { + mFile = file; + } + + ~FileCloser() + { + apr_file_close(mFile); + } +protected: + apr_file_t* mFile; +}; + + +//------------------------------------------------------------------------ +// Key +//------------------------------------------------------------------------ +struct Key +{ + Key() + { + mPos[0] = mPos[1] = mPos[2] = 0.0f; + mRot[0] = mRot[1] = mRot[2] = 0.0f; + mIgnorePos = false; + mIgnoreRot = false; + } + + F32 mPos[3]; + F32 mRot[3]; + BOOL mIgnorePos; + BOOL mIgnoreRot; +}; + + +//------------------------------------------------------------------------ +// KeyVector +//------------------------------------------------------------------------ +typedef std::vector KeyVector; + +//------------------------------------------------------------------------ +// Joint +//------------------------------------------------------------------------ +struct Joint +{ + Joint(const char *name) + { + mName = name; + mIgnore = FALSE; + mIgnorePositions = FALSE; + mRelativePositionKey = FALSE; + mRelativeRotationKey = FALSE; + mOutName = name; + mOrder[0] = 'X'; + mOrder[1] = 'Y'; + mOrder[2] = 'Z'; + mOrder[3] = 0; + mNumPosKeys = 0; + mNumRotKeys = 0; + mChildTreeMaxDepth = 0; + mPriority = 0; + } + + // Include aligned members first + LLMatrix3 mFrameMatrix; + LLMatrix3 mOffsetMatrix; + LLVector3 mRelativePosition; + // + std::string mName; + BOOL mIgnore; + BOOL mIgnorePositions; + BOOL mRelativePositionKey; + BOOL mRelativeRotationKey; + std::string mOutName; + std::string mMergeParentName; + std::string mMergeChildName; + char mOrder[4]; /* Flawfinder: ignore */ + KeyVector mKeys; + S32 mNumPosKeys; + S32 mNumRotKeys; + S32 mChildTreeMaxDepth; + S32 mPriority; +}; + + +typedef enum e_constraint_type +{ + CONSTRAINT_TYPE_POINT, + CONSTRAINT_TYPE_PLANE +} EConstraintType; + +struct Constraint +{ + char mSourceJointName[16]; /* Flawfinder: ignore */ + char mTargetJointName[16]; /* Flawfinder: ignore */ + S32 mChainLength; + LLVector3 mSourceOffset; + LLVector3 mTargetOffset; + LLVector3 mTargetDir; + F32 mEaseInStart; + F32 mEaseInStop; + F32 mEaseOutStart; + F32 mEaseOutStop; + EConstraintType mConstraintType; +}; + +//------------------------------------------------------------------------ +// JointVector +//------------------------------------------------------------------------ +typedef std::vector JointVector; + +//------------------------------------------------------------------------ +// ConstraintVector +//------------------------------------------------------------------------ +typedef std::vector ConstraintVector; + +//------------------------------------------------------------------------ +// Translation +//------------------------------------------------------------------------ +class Translation +{ +public: + Translation() + { + mIgnore = FALSE; + mRelativePositionKey = FALSE; + mRelativeRotationKey = FALSE; + mPriorityModifier = 0; + } + + std::string mOutName; + BOOL mIgnore; + BOOL mIgnorePositions; + BOOL mRelativePositionKey; + BOOL mRelativeRotationKey; + LLMatrix3 mFrameMatrix; + LLMatrix3 mOffsetMatrix; + LLVector3 mRelativePosition; + std::string mMergeParentName; + std::string mMergeChildName; + S32 mPriorityModifier; +}; + +//------------------------------------------------------------------------ +// TranslationMap +//------------------------------------------------------------------------ +typedef std::map TranslationMap; + +class LLBVHLoader +{ + friend class LLKeyframeMotion; +public: + // Constructor + LLBVHLoader(const char* buffer); + ~LLBVHLoader(); + + // Status Codes + typedef char *Status; + static char *ST_OK; + static char *ST_EOF; + static char *ST_NO_CONSTRAINT; + static char *ST_NO_FILE; + static char *ST_NO_HIER; + static char *ST_NO_JOINT; + static char *ST_NO_NAME; + static char *ST_NO_OFFSET; + static char *ST_NO_CHANNELS; + static char *ST_NO_ROTATION; + static char *ST_NO_AXIS; + static char *ST_NO_MOTION; + static char *ST_NO_FRAMES; + static char *ST_NO_FRAME_TIME; + static char *ST_NO_POS; + static char *ST_NO_ROT; + static char *ST_NO_XLT_FILE; + static char *ST_NO_XLT_HEADER; + static char *ST_NO_XLT_NAME; + static char *ST_NO_XLT_IGNORE; + static char *ST_NO_XLT_RELATIVE; + static char *ST_NO_XLT_OUTNAME; + static char *ST_NO_XLT_MATRIX; + static char *ST_NO_XLT_MERGECHILD; + static char *ST_NO_XLT_MERGEPARENT; + static char *ST_NO_XLT_PRIORITY; + static char *ST_NO_XLT_LOOP; + static char *ST_NO_XLT_EASEIN; + static char *ST_NO_XLT_EASEOUT; + static char *ST_NO_XLT_HAND; + static char *ST_NO_XLT_EMOTE; + + // Loads the specified translation table. + Status loadTranslationTable(const char *fileName); + + // Load the specified BVH file. + // Returns status code. + Status loadBVHFile(const char *buffer, char *error_text, S32 &error_line); + + // Applies translations to BVH data loaded. + void applyTranslations(); + + // Returns the number of lines scanned. + // Useful for error reporting. + S32 getLineNumber() { return mLineNumber; } + + // returns required size of output buffer + U32 getOutputSize(); + + // writes contents to datapacker + BOOL serialize(LLDataPacker& dp); + + // flags redundant keyframe data + void optimize(); + + void reset(); + + F32 getDuration() { return mDuration; } + + BOOL isInitialized() { return mInitialized; } + + Status getStatus() { return mStatus; } + +protected: + // Consumes one line of input from file. + BOOL getLine(apr_file_t *fp); + + // parser state + char mLine[BVH_PARSER_LINE_SIZE]; /* Flawfinder: ignore */ + S32 mLineNumber; + + // parsed values + S32 mNumFrames; + F32 mFrameTime; + JointVector mJoints; + ConstraintVector mConstraints; + TranslationMap mTranslations; + + S32 mPriority; + BOOL mLoop; + F32 mLoopInPoint; + F32 mLoopOutPoint; + F32 mEaseIn; + F32 mEaseOut; + S32 mHand; + std::string mEmoteName; + + BOOL mInitialized; + Status mStatus; + // computed values + F32 mDuration; +}; + +#endif // LL_LLBVHLOADER_H diff --git a/indra/llcharacter/llcharacter.cpp b/indra/llcharacter/llcharacter.cpp new file mode 100644 index 0000000000..ecca9a2526 --- /dev/null +++ b/indra/llcharacter/llcharacter.cpp @@ -0,0 +1,472 @@ +/** + * @file llcharacter.cpp + * @brief Implementation of LLCharacter class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- + +#include "linden_common.h" + +#include "llcharacter.h" +#include "llstring.h" + +#define SKEL_HEADER "Linden Skeleton 1.0" + +LLStringTable LLCharacter::sVisualParamNames(1024); + +// helper functions +BOOL larger_screen_area( LLCharacter* data_new, LLCharacter* data_tested ) +{ + return data_new->getPixelArea() > data_tested->getPixelArea(); +} + +LLLinkedList< LLCharacter > LLCharacter::sInstances( larger_screen_area ); + + +//----------------------------------------------------------------------------- +// LLCharacter() +// Class Constructor +//----------------------------------------------------------------------------- +LLCharacter::LLCharacter() + : + mPreferredPelvisHeight( 0.f ), + mSex( SEX_FEMALE ), + mAppearanceSerialNum( 0 ), + mSkeletonSerialNum( 0 ) +{ + mMotionController.setCharacter( this ); + sInstances.addData(this); + mPauseRequest = new LLPauseRequestHandle(); +} + + +//----------------------------------------------------------------------------- +// ~LLCharacter() +// Class Destructor +//----------------------------------------------------------------------------- +LLCharacter::~LLCharacter() +{ + for (LLVisualParam *param = getFirstVisualParam(); + param; + param = getNextVisualParam()) + { + delete param; + } + sInstances.removeData(this); +} + + +//----------------------------------------------------------------------------- +// getJoint() +//----------------------------------------------------------------------------- +LLJoint *LLCharacter::getJoint( const std::string &name ) +{ + LLJoint *joint = NULL; + + LLJoint *root = getRootJoint(); + if (root) + { + joint = root->findJoint(name); + } + + if (!joint) + { + llwarns << "Failed to find joint." << llendl; + } + return joint; +} + +//----------------------------------------------------------------------------- +// addMotion() +//----------------------------------------------------------------------------- +BOOL LLCharacter::addMotion( const LLUUID& id, LLMotionConstructor create ) +{ + return mMotionController.addMotion(id, create); +} + +//----------------------------------------------------------------------------- +// removeMotion() +//----------------------------------------------------------------------------- +void LLCharacter::removeMotion( const LLUUID& id ) +{ + mMotionController.removeMotion(id); +} + +//----------------------------------------------------------------------------- +// getMotion() +//----------------------------------------------------------------------------- +LLMotion* LLCharacter::createMotion( const LLUUID &id ) +{ + return mMotionController.createMotion( id ); +} + +//----------------------------------------------------------------------------- +// startMotion() +//----------------------------------------------------------------------------- +BOOL LLCharacter::startMotion(const LLUUID &id, F32 start_offset) +{ + return mMotionController.startMotion(id, start_offset); +} + + +//----------------------------------------------------------------------------- +// stopMotion() +//----------------------------------------------------------------------------- +BOOL LLCharacter::stopMotion(const LLUUID& id, BOOL stop_immediate) +{ + return mMotionController.stopMotionLocally(id, stop_immediate); +} + +//----------------------------------------------------------------------------- +// isMotionActive() +//----------------------------------------------------------------------------- +BOOL LLCharacter::isMotionActive(const LLUUID& id) +{ + LLMotion *motionp = mMotionController.findMotion(id); + if (motionp) + { + return mMotionController.isMotionActive(motionp); + } + + return FALSE; +} + + +//----------------------------------------------------------------------------- +// onDeactivateMotion() +//----------------------------------------------------------------------------- +void LLCharacter::requestStopMotion( LLMotion* motion) +{ +// llinfos << "DEBUG: Char::onDeactivateMotion( " << motionName << " )" << llendl; +} + + +//----------------------------------------------------------------------------- +// updateMotion() +//----------------------------------------------------------------------------- +void LLCharacter::updateMotion(BOOL force_update) +{ + // unpause if we're forcing an update or + // number of outstanding pause requests has dropped + // to the initial one + if (mMotionController.isPaused() && + (force_update || mPauseRequest->getNumRefs() == 1)) + { + mMotionController.unpause(); + } + + mMotionController.updateMotion(); + + // pause once again, after forced update, if there are outstanding + // pause requests + if (force_update && mPauseRequest->getNumRefs() > 1) + { + mMotionController.pause(); + } +} + + +//----------------------------------------------------------------------------- +// flushAllMotions() +//----------------------------------------------------------------------------- +void LLCharacter::flushAllMotions() +{ + mMotionController.flushAllMotions(); +} + + +//----------------------------------------------------------------------------- +// dumpCharacter() +//----------------------------------------------------------------------------- +void LLCharacter::dumpCharacter( LLJoint *joint ) +{ + // handle top level entry into recursion + if (joint == NULL) + { + llinfos << "DEBUG: Dumping Character @" << this << llendl; + dumpCharacter( getRootJoint() ); + llinfos << "DEBUG: Done." << llendl; + return; + } + + // print joint info + llinfos << "DEBUG: " << joint->getName() << " (" << (joint->getParent()?joint->getParent()->getName():std::string("ROOT")) << ")" << llendl; + + // recurse + for ( LLJoint *j = joint->mChildren.getFirstData(); + j != NULL; + j = joint->mChildren.getNextData() ) + { + dumpCharacter(j); + } +} + +//----------------------------------------------------------------------------- +// setAnimationData() +//----------------------------------------------------------------------------- +void LLCharacter::setAnimationData(std::string name, void *data) +{ + if(mAnimationData.getValue(name)) + { + *mAnimationData[name] = data; + } + else + { + mAnimationData.addToHead(name, data); + } +} + +//----------------------------------------------------------------------------- +// getAnimationData() +//----------------------------------------------------------------------------- +void * LLCharacter::getAnimationData(std::string name) +{ + void **result = mAnimationData.getValue(name); + void *return_value; // Necessary to suppress VC6 warning. JC + if (!result) + { + return_value = NULL; + } + else + { + return_value = *result; + } + + return return_value; +} + +//----------------------------------------------------------------------------- +// removeAnimationData() +//----------------------------------------------------------------------------- +void LLCharacter::removeAnimationData(std::string name) +{ + mAnimationData.remove(name); +} + +//----------------------------------------------------------------------------- +// setVisualParamWeight() +//----------------------------------------------------------------------------- +BOOL LLCharacter::setVisualParamWeight(LLVisualParam* which_param, F32 weight, BOOL set_by_user) +{ + S32 index = which_param->getID(); + VisualParamIndexMap_t::iterator index_iter = mVisualParamIndexMap.find(index); + if (index_iter != mVisualParamIndexMap.end()) + { + index_iter->second->setWeight(weight, set_by_user); + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// setVisualParamWeight() +//----------------------------------------------------------------------------- +BOOL LLCharacter::setVisualParamWeight(const char* param_name, F32 weight, BOOL set_by_user) +{ + LLString tname(param_name); + LLString::toLower(tname); + char *tableptr = sVisualParamNames.checkString(tname); + VisualParamNameMap_t::iterator name_iter = mVisualParamNameMap.find(tableptr); + if (name_iter != mVisualParamNameMap.end()) + { + name_iter->second->setWeight(weight, set_by_user); + return TRUE; + } + llwarns << "LLCharacter::setVisualParamWeight() Invalid visual parameter: " << param_name << llendl; + return FALSE; +} + +//----------------------------------------------------------------------------- +// setVisualParamWeight() +//----------------------------------------------------------------------------- +BOOL LLCharacter::setVisualParamWeight(S32 index, F32 weight, BOOL set_by_user) +{ + VisualParamIndexMap_t::iterator index_iter = mVisualParamIndexMap.find(index); + if (index_iter != mVisualParamIndexMap.end()) + { + index_iter->second->setWeight(weight, set_by_user); + return TRUE; + } + llwarns << "LLCharacter::setVisualParamWeight() Invalid visual parameter index: " << index << llendl; + return FALSE; +} + +//----------------------------------------------------------------------------- +// getVisualParamWeight() +//----------------------------------------------------------------------------- +F32 LLCharacter::getVisualParamWeight(LLVisualParam *which_param) +{ + S32 index = which_param->getID(); + VisualParamIndexMap_t::iterator index_iter = mVisualParamIndexMap.find(index); + if (index_iter != mVisualParamIndexMap.end()) + { + return index_iter->second->getWeight(); + } + else + { + llwarns << "LLCharacter::getVisualParamWeight() Invalid visual parameter*, index= " << index << llendl; + return 0.f; + } +} + +//----------------------------------------------------------------------------- +// getVisualParamWeight() +//----------------------------------------------------------------------------- +F32 LLCharacter::getVisualParamWeight(const char* param_name) +{ + LLString tname(param_name); + LLString::toLower(tname); + char *tableptr = sVisualParamNames.checkString(tname); + VisualParamNameMap_t::iterator name_iter = mVisualParamNameMap.find(tableptr); + if (name_iter != mVisualParamNameMap.end()) + { + return name_iter->second->getWeight(); + } + llwarns << "LLCharacter::getVisualParamWeight() Invalid visual parameter: " << param_name << llendl; + return 0.f; +} + +//----------------------------------------------------------------------------- +// getVisualParamWeight() +//----------------------------------------------------------------------------- +F32 LLCharacter::getVisualParamWeight(S32 index) +{ + VisualParamIndexMap_t::iterator index_iter = mVisualParamIndexMap.find(index); + if (index_iter != mVisualParamIndexMap.end()) + { + return index_iter->second->getWeight(); + } + else + { + llwarns << "LLCharacter::getVisualParamWeight() Invalid visual parameter index: " << index << llendl; + return 0.f; + } +} + +//----------------------------------------------------------------------------- +// clearVisualParamWeights() +//----------------------------------------------------------------------------- +void LLCharacter::clearVisualParamWeights() +{ + for (LLVisualParam *param = getFirstVisualParam(); + param; + param = getNextVisualParam()) + { + if (param->getGroup() == VISUAL_PARAM_GROUP_TWEAKABLE) + { + param->setWeight( param->getDefaultWeight(), FALSE ); + } + } +} + +//----------------------------------------------------------------------------- +// getVisualParam() +//----------------------------------------------------------------------------- +LLVisualParam* LLCharacter::getVisualParam(const char *param_name) +{ + LLString tname(param_name); + LLString::toLower(tname); + char *tableptr = sVisualParamNames.checkString(tname); + VisualParamNameMap_t::iterator name_iter = mVisualParamNameMap.find(tableptr); + if (name_iter != mVisualParamNameMap.end()) + { + return name_iter->second; + } + llwarns << "LLCharacter::getVisualParam() Invalid visual parameter: " << param_name << llendl; + return NULL; +} + +//----------------------------------------------------------------------------- +// addSharedVisualParam() +//----------------------------------------------------------------------------- +void LLCharacter::addSharedVisualParam(LLVisualParam *param) +{ + S32 index = param->getID(); + VisualParamIndexMap_t::iterator index_iter = mVisualParamIndexMap.find(index); + LLVisualParam* current_param = 0; + if (index_iter != mVisualParamIndexMap.end()) + current_param = index_iter->second; + if( current_param ) + { + LLVisualParam* next_param = current_param; + while(next_param->getNextParam()) + { + next_param = next_param->getNextParam(); + } + next_param->setNextParam(param); + } + else + { + llwarns << "Shared visual parameter " << param->getName() << " does not already exist with ID " << + param->getID() << llendl; + } +} + +//----------------------------------------------------------------------------- +// addVisualParam() +//----------------------------------------------------------------------------- +void LLCharacter::addVisualParam(LLVisualParam *param) +{ + S32 index = param->getID(); + // Add Index map + std::pair idxres; + idxres = mVisualParamIndexMap.insert(VisualParamIndexMap_t::value_type(index, param)); + if (!idxres.second) + { + llwarns << "Visual parameter " << param->getName() << " already exists with same ID as " << + param->getName() << llendl; + VisualParamIndexMap_t::iterator index_iter = idxres.first; + index_iter->second = param; + } + + if (param->getInfo()) + { + // Add name map + LLString tname(param->getName()); + LLString::toLower(tname); + char *tableptr = sVisualParamNames.addString(tname); + std::pair nameres; + nameres = mVisualParamNameMap.insert(VisualParamNameMap_t::value_type(tableptr, param)); + if (!nameres.second) + { + // Already exists, copy param + VisualParamNameMap_t::iterator name_iter = nameres.first; + name_iter->second = param; + } + } + //llinfos << "Adding Visual Param '" << param->getName() << "' ( " << index << " )" << llendl; +} + +//----------------------------------------------------------------------------- +// updateVisualParams() +//----------------------------------------------------------------------------- +void LLCharacter::updateVisualParams() +{ + for (LLVisualParam *param = getFirstVisualParam(); + param; + param = getNextVisualParam()) + { + if (param->isAnimating()) + { + continue; + } + // only apply parameters whose effective weight has changed + F32 effective_weight = ( param->getSex() & mSex ) ? param->getWeight() : param->getDefaultWeight(); + if (effective_weight != param->getLastWeight()) + { + param->apply( mSex ); + } + } +} + +LLAnimPauseRequest LLCharacter::requestPause() +{ + mMotionController.pause(); + return mPauseRequest; +} + diff --git a/indra/llcharacter/llcharacter.h b/indra/llcharacter/llcharacter.h new file mode 100644 index 0000000000..f2f106f360 --- /dev/null +++ b/indra/llcharacter/llcharacter.h @@ -0,0 +1,252 @@ +/** + * @file llcharacter.h + * @brief Implementation of LLCharacter class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLCHARACTER_H +#define LL_LLCHARACTER_H + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include + +#include "lljoint.h" +#include "llmotioncontroller.h" +#include "llassoclist.h" +#include "llvisualparam.h" +#include "linked_lists.h" +#include "string_table.h" +#include "llmemory.h" + +class LLPolyMesh; + +class LLPauseRequestHandle : public LLThreadSafeRefCount +{ +public: + LLPauseRequestHandle() {}; +}; + +typedef LLPointer LLAnimPauseRequest; + +//----------------------------------------------------------------------------- +// class LLCharacter +//----------------------------------------------------------------------------- +class LLCharacter +{ +public: + // Constructor + LLCharacter(); + + // Destructor + virtual ~LLCharacter(); + + //------------------------------------------------------------------------- + // LLCharacter Interface + // These functions must be implemented by subclasses. + //------------------------------------------------------------------------- + + // get the prefix to be used to lookup motion data files + // from the viewer data directory + virtual const char *getAnimationPrefix() = 0; + + // get the root joint of the character + virtual LLJoint *getRootJoint() = 0; + + // get the specified joint + // default implementation does recursive search, + // subclasses may optimize/cache results. + virtual LLJoint *getJoint( const std::string &name ); + + // get the position of the character + virtual LLVector3 getCharacterPosition() = 0; + + // get the rotation of the character + virtual LLQuaternion getCharacterRotation() = 0; + + // get the velocity of the character + virtual LLVector3 getCharacterVelocity() = 0; + + // get the angular velocity of the character + virtual LLVector3 getCharacterAngularVelocity() = 0; + + // get the height & normal of the ground under a point + virtual void getGround(const LLVector3 &inPos, LLVector3 &outPos, LLVector3 &outNorm) = 0; + + // allocate an array of joints for the character skeleton + // this must be overloaded to support joint subclasses, + // and is called implicitly from buildSkeleton(). + // Note this must handle reallocation as it will be called + // each time buildSkeleton() is called. + virtual BOOL allocateCharacterJoints( U32 num ) = 0; + + // skeleton joint accessor to support joint subclasses + virtual LLJoint *getCharacterJoint( U32 i ) = 0; + + // get the physics time dilation for the simulator + virtual F32 getTimeDilation() = 0; + + // gets current pixel area of this character + virtual F32 getPixelArea() = 0; + + // gets the head mesh of the character + virtual LLPolyMesh* getHeadMesh() = 0; + + // gets the upper body mesh of the character + virtual LLPolyMesh* getUpperBodyMesh() = 0; + + // gets global coordinates from agent local coordinates + virtual LLVector3d getPosGlobalFromAgent(const LLVector3 &position) = 0; + + // gets agent local coordinates from global coordinates + virtual LLVector3 getPosAgentFromGlobal(const LLVector3d &position) = 0; + + // updates all visual parameters for this character + virtual void updateVisualParams(); + + virtual void addDebugText( const char* text ) = 0; + + virtual const LLUUID& getID() = 0; + //------------------------------------------------------------------------- + // End Interface + //------------------------------------------------------------------------- + // registers a motion with the character + // returns true if successfull + BOOL addMotion( const LLUUID& id, LLMotionConstructor create ); + + void removeMotion( const LLUUID& id ); + + // returns an instance of a registered motion + LLMotion* createMotion( const LLUUID &id ); + + // start a motion + // returns true if successful, false if an error occurred + virtual BOOL startMotion( const LLUUID& id, F32 start_offset = 0.f); + + // stop a motion + virtual BOOL stopMotion( const LLUUID& id, BOOL stop_immediate = FALSE ); + + // is this motion active? + BOOL isMotionActive( const LLUUID& id ); + + // Event handler for motion deactivation. + // Called when a motion has completely stopped and has been deactivated. + // Subclasses may optionally override this. + // The default implementation does nothing. + virtual void requestStopMotion( LLMotion* motion ); + + // periodic update function, steps the motion controller + void updateMotion(BOOL force_update = FALSE); + + LLAnimPauseRequest requestPause(); + BOOL areAnimationsPaused() { return mMotionController.isPaused(); } + void setAnimTimeFactor(F32 factor) { mMotionController.setTimeFactor(factor); } + void setTimeStep(F32 time_step) { mMotionController.setTimeStep(time_step); } + // Releases all motion instances which should result in + // no cached references to character joint data. This is + // useful if a character wants to rebuild it's skeleton. + virtual void flushAllMotions(); + + // dumps information for debugging + virtual void dumpCharacter( LLJoint *joint = NULL ); + + virtual F32 getPreferredPelvisHeight() { return mPreferredPelvisHeight; } + + virtual LLVector3 getVolumePos(S32 joint_index, LLVector3& volume_offset) { return LLVector3::zero; } + + virtual LLJoint* findCollisionVolume(U32 volume_id) { return NULL; } + + virtual S32 getCollisionVolumeID(std::string &name) { return -1; } + + void setAnimationData(std::string name, void *data); + + void *getAnimationData(std::string name); + + void removeAnimationData(std::string name); + + void addVisualParam(LLVisualParam *param); + void addSharedVisualParam(LLVisualParam *param); + + BOOL setVisualParamWeight(LLVisualParam *which_param, F32 weight, BOOL set_by_user = FALSE ); + BOOL setVisualParamWeight(const char* param_name, F32 weight, BOOL set_by_user = FALSE ); + BOOL setVisualParamWeight(S32 index, F32 weight, BOOL set_by_user = FALSE ); + + // get visual param weight by param or name + F32 getVisualParamWeight(LLVisualParam *distortion); + F32 getVisualParamWeight(const char* param_name); + F32 getVisualParamWeight(S32 index); + + // set all morph weights to 0 + void clearVisualParamWeights(); + + // visual parameter accessors + LLVisualParam* getFirstVisualParam() + { + mCurIterator = mVisualParamIndexMap.begin(); + return getNextVisualParam(); + } + LLVisualParam* getNextVisualParam() + { + if (mCurIterator == mVisualParamIndexMap.end()) + return 0; + return (mCurIterator++)->second; + } + + LLVisualParam* getVisualParam(S32 id) + { + VisualParamIndexMap_t::iterator iter = mVisualParamIndexMap.find(id); + return (iter == mVisualParamIndexMap.end()) ? 0 : iter->second; + } + S32 getVisualParamID(LLVisualParam *id) + { + VisualParamIndexMap_t::iterator iter; + for (iter = mVisualParamIndexMap.begin(); iter != mVisualParamIndexMap.end(); iter++) + { + if (iter->second == id) + return iter->first; + } + return 0; + } + S32 getVisualParamCount() { return (S32)mVisualParamIndexMap.size(); } + LLVisualParam* getVisualParam(const char *name); + + + ESex getSex() { return mSex; } + void setSex( ESex sex ) { mSex = sex; } + + U32 getAppearanceSerialNum() const { return mAppearanceSerialNum; } + void setAppearanceSerialNum( U32 num ) { mAppearanceSerialNum = num; } + + U32 getSkeletonSerialNum() const { return mSkeletonSerialNum; } + void setSkeletonSerialNum( U32 num ) { mSkeletonSerialNum = num; } + + static LLLinkedList< LLCharacter > sInstances; + +protected: + LLMotionController mMotionController; + + LLAssocList mAnimationData; + + F32 mPreferredPelvisHeight; + ESex mSex; + U32 mAppearanceSerialNum; + U32 mSkeletonSerialNum; + LLAnimPauseRequest mPauseRequest; + + +private: + // visual parameter stuff + typedef std::map VisualParamIndexMap_t; + VisualParamIndexMap_t mVisualParamIndexMap; + VisualParamIndexMap_t::iterator mCurIterator; + typedef std::map VisualParamNameMap_t; + VisualParamNameMap_t mVisualParamNameMap; + + static LLStringTable sVisualParamNames; +}; + +#endif // LL_LLCHARACTER_H + diff --git a/indra/llcharacter/lleditingmotion.cpp b/indra/llcharacter/lleditingmotion.cpp new file mode 100644 index 0000000000..f6cfc0ab80 --- /dev/null +++ b/indra/llcharacter/lleditingmotion.cpp @@ -0,0 +1,234 @@ +/** + * @file lleditingmotion.cpp + * @brief Implementation of LLEditingMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "lleditingmotion.h" +#include "llcharacter.h" +#include "llhandmotion.h" +#include "llcriticaldamp.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +const LLQuaternion EDIT_MOTION_WRIST_ROTATION(F_PI_BY_TWO * 0.7f, LLVector3(1.0f, 0.0f, 0.0f)); +const F32 TARGET_LAG_HALF_LIFE = 0.1f; // half-life of IK targeting +const F32 TORSO_LAG_HALF_LIFE = 0.2f; +const F32 MAX_TIME_DELTA = 2.f; //max two seconds a frame for calculating interpolation + +S32 LLEditingMotion::sHandPose = LLHandMotion::HAND_POSE_RELAXED_R; +S32 LLEditingMotion::sHandPosePriority = 3; + +//----------------------------------------------------------------------------- +// LLEditingMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLEditingMotion::LLEditingMotion( const LLUUID &id) : LLMotion(id) +{ + mCharacter = NULL; + + // create kinematic chain + mParentJoint.addChild( &mShoulderJoint ); + mShoulderJoint.addChild( &mElbowJoint ); + mElbowJoint.addChild( &mWristJoint ); + + mName = "editing"; +} + + +//----------------------------------------------------------------------------- +// ~LLEditingMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLEditingMotion::~LLEditingMotion() +{ +} + +//----------------------------------------------------------------------------- +// LLEditingMotion::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLEditingMotion::onInitialize(LLCharacter *character) +{ + // save character for future use + mCharacter = character; + + // make sure character skeleton is copacetic + if (!mCharacter->getJoint("mShoulderLeft") || + !mCharacter->getJoint("mElbowLeft") || + !mCharacter->getJoint("mWristLeft")) + { + llwarns << "Invalid skeleton for editing motion!" << llendl; + return STATUS_FAILURE; + } + + // get the shoulder, elbow, wrist joints from the character + mParentState.setJoint( mCharacter->getJoint("mShoulderLeft")->getParent() ); + mShoulderState.setJoint( mCharacter->getJoint("mShoulderLeft") ); + mElbowState.setJoint( mCharacter->getJoint("mElbowLeft") ); + mWristState.setJoint( mCharacter->getJoint("mWristLeft") ); + mTorsoState.setJoint( mCharacter->getJoint("mTorso")); + + if ( ! mParentState.getJoint() ) + { + llinfos << getName() << ": Can't get parent joint." << llendl; + return STATUS_FAILURE; + } + + mWristOffset = LLVector3(0.0f, 0.2f, 0.0f); + + // add joint states to the pose + mShoulderState.setUsage(LLJointState::ROT); + mElbowState.setUsage(LLJointState::ROT); + mTorsoState.setUsage(LLJointState::ROT); + mWristState.setUsage(LLJointState::ROT); + addJointState( &mShoulderState ); + addJointState( &mElbowState ); + addJointState( &mTorsoState ); + addJointState( &mWristState ); + + // propagate joint positions to kinematic chain + mParentJoint.setPosition( mParentState.getJoint()->getWorldPosition() ); + mShoulderJoint.setPosition( mShoulderState.getJoint()->getPosition() ); + mElbowJoint.setPosition( mElbowState.getJoint()->getPosition() ); + mWristJoint.setPosition( mWristState.getJoint()->getPosition() + mWristOffset ); + + // propagate current joint rotations to kinematic chain + mParentJoint.setRotation( mParentState.getJoint()->getWorldRotation() ); + mShoulderJoint.setRotation( mShoulderState.getJoint()->getRotation() ); + mElbowJoint.setRotation( mElbowState.getJoint()->getRotation() ); + + // connect the ikSolver to the chain + mIKSolver.setPoleVector( LLVector3( -1.0f, 1.0f, 0.0f ) ); + // specifying the elbow's axis will prevent bad IK for the more + // singular configurations, but the axis is limb-specific -- Leviathan + mIKSolver.setBAxis( LLVector3( -0.682683f, 0.0f, -0.730714f ) ); + mIKSolver.setupJoints( &mShoulderJoint, &mElbowJoint, &mWristJoint, &mTarget ); + + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// LLEditingMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLEditingMotion::onActivate() +{ + // propagate joint positions to kinematic chain + mParentJoint.setPosition( mParentState.getJoint()->getWorldPosition() ); + mShoulderJoint.setPosition( mShoulderState.getJoint()->getPosition() ); + mElbowJoint.setPosition( mElbowState.getJoint()->getPosition() ); + mWristJoint.setPosition( mWristState.getJoint()->getPosition() + mWristOffset ); + + // propagate current joint rotations to kinematic chain + mParentJoint.setRotation( mParentState.getJoint()->getWorldRotation() ); + mShoulderJoint.setRotation( mShoulderState.getJoint()->getRotation() ); + mElbowJoint.setRotation( mElbowState.getJoint()->getRotation() ); + + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLEditingMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLEditingMotion::onUpdate(F32 time, U8* joint_mask) +{ + LLVector3 focus_pt; + LLVector3* pointAtPt = (LLVector3*)mCharacter->getAnimationData("PointAtPoint"); + + + BOOL result = TRUE; + + if (!pointAtPt) + { + focus_pt = mLastSelectPt; + result = FALSE; + } + else + { + focus_pt = *pointAtPt; + mLastSelectPt = focus_pt; + } + + focus_pt += mCharacter->getCharacterPosition(); + + // propagate joint positions to kinematic chain + mParentJoint.setPosition( mParentState.getJoint()->getWorldPosition() ); + mShoulderJoint.setPosition( mShoulderState.getJoint()->getPosition() ); + mElbowJoint.setPosition( mElbowState.getJoint()->getPosition() ); + mWristJoint.setPosition( mWristState.getJoint()->getPosition() + mWristOffset ); + + // propagate current joint rotations to kinematic chain + mParentJoint.setRotation( mParentState.getJoint()->getWorldRotation() ); + mShoulderJoint.setRotation( mShoulderState.getJoint()->getRotation() ); + mElbowJoint.setRotation( mElbowState.getJoint()->getRotation() ); + + // update target position from character + LLVector3 target = focus_pt - mParentJoint.getPosition(); + F32 target_dist = target.normVec(); + + LLVector3 edit_plane_normal(1.f / F_SQRT2, 1.f / F_SQRT2, 0.f); + edit_plane_normal.normVec(); + + edit_plane_normal.rotVec(mTorsoState.getJoint()->getWorldRotation()); + + F32 dot = edit_plane_normal * target; + + if (dot < 0.f) + { + target = target + (edit_plane_normal * (dot * 2.f)); + target.mV[VZ] += clamp_rescale(dot, 0.f, -1.f, 0.f, 5.f); + target.normVec(); + } + + target = target * target_dist; + if (!target.isFinite()) + { + llerrs << "Non finite target in editing motion with target distance of " << target_dist << + " and focus point " << focus_pt << llendl; + } + + mTarget.setPosition( target + mParentJoint.getPosition()); + +// llinfos << "Point At: " << mTarget.getPosition() << llendl; + + // update the ikSolver + if (!mTarget.getPosition().isExactlyZero()) + { + LLQuaternion shoulderRot = mShoulderJoint.getRotation(); + LLQuaternion elbowRot = mElbowJoint.getRotation(); + mIKSolver.solve(); + + // use blending... + F32 slerp_amt = LLCriticalDamp::getInterpolant(TARGET_LAG_HALF_LIFE); + shoulderRot = slerp(slerp_amt, mShoulderJoint.getRotation(), shoulderRot); + elbowRot = slerp(slerp_amt, mElbowJoint.getRotation(), elbowRot); + + // now put blended values back into joints + llassert(shoulderRot.isFinite()); + llassert(elbowRot.isFinite()); + mShoulderState.setRotation(shoulderRot); + mElbowState.setRotation(elbowRot); + mWristState.setRotation(LLQuaternion::DEFAULT); + } + + mCharacter->setAnimationData("Hand Pose", &sHandPose); + mCharacter->setAnimationData("Hand Pose Priority", &sHandPosePriority); + return result; +} + +//----------------------------------------------------------------------------- +// LLEditingMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLEditingMotion::onDeactivate() +{ +} + + +// End diff --git a/indra/llcharacter/lleditingmotion.h b/indra/llcharacter/lleditingmotion.h new file mode 100644 index 0000000000..b817a765b2 --- /dev/null +++ b/indra/llcharacter/lleditingmotion.h @@ -0,0 +1,115 @@ +/** + * @file lleditingmotion.h + * @brief Implementation of LLEditingMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLEDITINGMOTION_H +#define LL_LLEDITINGMOTION_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include "llmotion.h" +#include "lljointsolverrp3.h" +#include "v3dmath.h" + +#define EDITING_EASEIN_DURATION 0.0f +#define EDITING_EASEOUT_DURATION 0.5f +#define EDITING_PRIORITY LLJoint::HIGH_PRIORITY +#define MIN_REQUIRED_PIXEL_AREA_EDITING 500.f + +//----------------------------------------------------------------------------- +// class LLEditingMotion +//----------------------------------------------------------------------------- +class LLEditingMotion : + public LLMotion +{ +public: + // Constructor + LLEditingMotion(const LLUUID &id); + + // Destructor + virtual ~LLEditingMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLEditingMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual BOOL getLoop() { return TRUE; } + + // motions must report their total duration + virtual F32 getDuration() { return 0.0; } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { return EDITING_EASEIN_DURATION; } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { return EDITING_EASEOUT_DURATION; } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { return EDITING_PRIORITY; } + + virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_EDITING; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + virtual BOOL onActivate(); + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + virtual BOOL onUpdate(F32 time, U8* joint_mask); + + // called when a motion is deactivated + virtual void onDeactivate(); + +public: + //------------------------------------------------------------------------- + // joint states to be animated + //------------------------------------------------------------------------- + LLCharacter *mCharacter; + LLVector3 mWristOffset; + + LLJointState mParentState; + LLJointState mShoulderState; + LLJointState mElbowState; + LLJointState mWristState; + LLJointState mTorsoState; + + LLJoint mParentJoint; + LLJoint mShoulderJoint; + LLJoint mElbowJoint; + LLJoint mWristJoint; + LLJoint mTarget; + LLJointSolverRP3 mIKSolver; + + static S32 sHandPose; + static S32 sHandPosePriority; + LLVector3 mLastSelectPt; +}; + +#endif // LL_LLKEYFRAMEMOTION_H + diff --git a/indra/llcharacter/llgesture.cpp b/indra/llcharacter/llgesture.cpp new file mode 100644 index 0000000000..028c2f7fdf --- /dev/null +++ b/indra/llcharacter/llgesture.cpp @@ -0,0 +1,356 @@ +/** + * @file llgesture.cpp + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "indra_constants.h" + +#include "llgesture.h" +#include "llendianswizzle.h" +#include "message.h" +#include + +// for allocating serialization buffers - these need to be updated when members change +const S32 LLGestureList::SERIAL_HEADER_SIZE = sizeof(S32); +const S32 LLGesture::MAX_SERIAL_SIZE = sizeof(KEY) + sizeof(MASK) + 16 + 26 + 41 + 41; + +LLGesture::LLGesture() +: mKey(KEY_NONE), + mMask(MASK_NONE), + mTrigger(), + mTriggerLower(), + mSoundItemID(), + mAnimation(), + mOutputString() +{ } + +LLGesture::LLGesture(KEY key, MASK mask, const std::string &trigger, + const LLUUID &sound_item_id, + const std::string &animation, + const std::string &output_string) +: + mKey(key), + mMask(mask), + mTrigger(trigger), + mTriggerLower(trigger), + mSoundItemID(sound_item_id), + mAnimation(animation), + mOutputString(output_string) +{ + mTriggerLower = utf8str_tolower(mTriggerLower); +} + +LLGesture::LLGesture(U8 **buffer, S32 max_size) +{ + *buffer = deserialize(*buffer, max_size); +} + +LLGesture::LLGesture(const LLGesture &rhs) +{ + mKey = rhs.mKey; + mMask = rhs.mMask; + mTrigger = rhs.mTrigger; + mTriggerLower = rhs.mTriggerLower; + mSoundItemID = rhs.mSoundItemID; + mAnimation = rhs.mAnimation; + mOutputString = rhs.mOutputString; +} + +const LLGesture &LLGesture::operator =(const LLGesture &rhs) +{ + mKey = rhs.mKey; + mMask = rhs.mMask; + mTrigger = rhs.mTrigger; + mTriggerLower = rhs.mTriggerLower; + mSoundItemID = rhs.mSoundItemID; + mAnimation = rhs.mAnimation; + mOutputString = rhs.mOutputString; + return (*this); +} + + +BOOL LLGesture::trigger(KEY key, MASK mask) +{ + llwarns << "Parent class trigger called: you probably didn't mean this." << llendl; + return FALSE; +} + + +BOOL LLGesture::trigger(const LLString &trigger_string) +{ + llwarns << "Parent class trigger called: you probably didn't mean this." << llendl; + return FALSE; +} + +// NOT endian-neutral +U8 *LLGesture::serialize(U8 *buffer) const +{ + htonmemcpy(buffer, &mKey, MVT_S8, 1); + buffer += sizeof(mKey); + htonmemcpy(buffer, &mMask, MVT_U32, 4); + buffer += sizeof(mMask); + htonmemcpy(buffer, mSoundItemID.mData, MVT_LLUUID, 16); + buffer += 16; + + memcpy(buffer, mTrigger.c_str(), mTrigger.length() + 1); /* Flawfinder: ignore */ + buffer += mTrigger.length() + 1; + memcpy(buffer, mAnimation.c_str(), mAnimation.length() + 1); /* Flawfinder: ignore */ + buffer += mAnimation.length() + 1; + memcpy(buffer, mOutputString.c_str(), mOutputString.length() + 1); /* Flawfinder: ignore */ + buffer += mOutputString.length() + 1; + + return buffer; +} + +U8 *LLGesture::deserialize(U8 *buffer, S32 max_size) +{ + U8 *tmp = buffer; + + if (tmp + sizeof(mKey) + sizeof(mMask) + 16 > buffer + max_size) + { + llwarns << "Attempt to read past end of buffer, bad data!!!!" << llendl; + return buffer; + } + + htonmemcpy(&mKey, tmp, MVT_S8, 1); + tmp += sizeof(mKey); + htonmemcpy(&mMask, tmp, MVT_U32, 4); + tmp += sizeof(mMask); + htonmemcpy(mSoundItemID.mData, tmp, MVT_LLUUID, 16); + tmp += 16; + + mTrigger.assign((char *)tmp); + mTriggerLower = mTrigger; + mTriggerLower = utf8str_tolower(mTriggerLower); + tmp += mTrigger.length() + 1; + mAnimation.assign((char *)tmp); + //RN: force animation names to lower case + // must do this for backwards compatibility + mAnimation = utf8str_tolower(mAnimation); + tmp += mAnimation.length() + 1; + mOutputString.assign((char *)tmp); + tmp += mOutputString.length() + 1; + + if (tmp > buffer + max_size) + { + llwarns << "Read past end of buffer, bad data!!!!" << llendl; + return tmp; + } + + return tmp; +} + +S32 LLGesture::getMaxSerialSize() +{ + return MAX_SERIAL_SIZE; +} + +//--------------------------------------------------------------------- +// LLGestureList +//--------------------------------------------------------------------- + +LLGestureList::LLGestureList() +: mList(0) +{ + // add some gestures for debugging +// LLGesture *gesture = NULL; +/* + gesture = new LLGesture(KEY_F2, MASK_NONE, ":-)", + SND_CHIRP, "dance2", ":-)" ); + mList.put(gesture); + + gesture = new LLGesture(KEY_F3, MASK_NONE, "/dance", + SND_OBJECT_CREATE, "dance3", "(dances)" ); + mList.put(gesture); + + gesture = new LLGesture(KEY_F4, MASK_NONE, "/boogie", + LLUUID::null, "dance4", LLString::null ); + mList.put(gesture); + + gesture = new LLGesture(KEY_F5, MASK_SHIFT, "/tongue", + LLUUID::null, "Express_Tongue_Out", LLString::null ); + mList.put(gesture); + */ +} + +LLGestureList::~LLGestureList() +{ + deleteAll(); +} + + +void LLGestureList::deleteAll() +{ + S32 count = mList.count(); + for (S32 i = 0; i < count; i++) + { + delete mList.get(i); + } + mList.reset(); +} + +// Iterates through space delimited tokens in string, triggering any gestures found. +// Generates a revised string that has the found tokens replaced by their replacement strings +// and (as a minor side effect) has multiple spaces in a row replaced by single spaces. +BOOL LLGestureList::triggerAndReviseString(const LLString &string, LLString* revised_string) +{ + LLString tokenized = string; + + BOOL found_gestures = FALSE; + BOOL first_token = TRUE; + + typedef boost::tokenizer > tokenizer; + boost::char_separator sep(" "); + tokenizer tokens(string, sep); + tokenizer::iterator token_iter; + + for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + LLGesture* gesture = NULL; + + if( !found_gestures ) // Only pay attention to the first gesture in the string. + { + LLString cur_token_lower = *token_iter; + LLString::toLower(cur_token_lower); + + for (S32 i = 0; i < mList.count(); i++) + { + gesture = mList.get(i); + if (gesture->trigger(cur_token_lower)) + { + if( !gesture->getOutputString().empty() ) + { + if( !first_token ) + { + revised_string->append( " " ); + } + + // Don't muck with the user's capitalization if we don't have to. + const std::string& output = gesture->getOutputString(); + LLString output_lower = LLString(output.c_str()); + LLString::toLower(output_lower); + if( cur_token_lower == output_lower ) + { + revised_string->append(*token_iter); + } + else + { + revised_string->append(output.c_str()); + } + + } + found_gestures = TRUE; + break; + } + gesture = NULL; + } + } + + if( !gesture ) + { + if( !first_token ) + { + revised_string->append( " " ); + } + revised_string->append( *token_iter ); + } + + first_token = FALSE; + } + return found_gestures; +} + + + +BOOL LLGestureList::trigger(KEY key, MASK mask) +{ + for (S32 i = 0; i < mList.count(); i++) + { + LLGesture* gesture = mList.get(i); + if( gesture ) + { + if (gesture->trigger(key, mask)) + { + return TRUE; + } + } + else + { + llwarns << "NULL gesture in gesture list (" << i << ")" << llendl + } + } + return FALSE; +} + +// NOT endian-neutral +U8 *LLGestureList::serialize(U8 *buffer) const +{ + // a single S32 serves as the header that tells us how many to read + S32 count = mList.count(); + htonmemcpy(buffer, &count, MVT_S32, 4); + buffer += sizeof(count); + + for (S32 i = 0; i < count; i++) + { + buffer = mList[i]->serialize(buffer); + } + + return buffer; +} + +const S32 MAX_GESTURES = 4096; + +U8 *LLGestureList::deserialize(U8 *buffer, S32 max_size) +{ + deleteAll(); + + S32 count; + U8 *tmp = buffer; + + if (tmp + sizeof(count) > buffer + max_size) + { + llwarns << "Invalid max_size" << llendl; + return buffer; + } + + htonmemcpy(&count, tmp, MVT_S32, 4); + + if (count > MAX_GESTURES) + { + llwarns << "Unreasonably large gesture list count in deserialize: " << count << llendl; + return tmp; + } + + tmp += sizeof(count); + + mList.reserve_block(count); + + for (S32 i = 0; i < count; i++) + { + mList[i] = create_gesture(&tmp, max_size - (S32)(tmp - buffer)); + if (tmp - buffer > max_size) + { + llwarns << "Deserialization read past end of buffer, bad data!!!!" << llendl; + return tmp; + } + } + + return tmp; +} + +// this is a helper for deserialize +// it gets overridden by LLViewerGestureList to create LLViewerGestures +// overridden by child class to use local LLGesture implementation +LLGesture *LLGestureList::create_gesture(U8 **buffer, S32 max_size) +{ + return new LLGesture(buffer, max_size); +} + +S32 LLGestureList::getMaxSerialSize() +{ + return SERIAL_HEADER_SIZE + (count() * LLGesture::getMaxSerialSize()); +} diff --git a/indra/llcharacter/llgesture.h b/indra/llcharacter/llgesture.h new file mode 100644 index 0000000000..64c752769b --- /dev/null +++ b/indra/llcharacter/llgesture.h @@ -0,0 +1,96 @@ +/** + * @file llgesture.h + * @brief A gesture is a combination of a triggering chat phrase or + * key, a sound, an animation, and a chat string. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLGESTURE_H +#define LL_LLGESTURE_H + +#include "llanimationstates.h" +#include "lluuid.h" +#include "llstring.h" +#include "lldarray.h" + +class LLGesture +{ +public: + LLGesture(); + LLGesture(KEY key, MASK mask, const std::string &trigger, + const LLUUID &sound_item_id, const std::string &animation, + const std::string &output_string); + + LLGesture(U8 **buffer, S32 max_size); // deserializes, advances buffer + LLGesture(const LLGesture &gesture); + const LLGesture &operator=(const LLGesture &rhs); + + virtual ~LLGesture() {}; + + // Accessors + KEY getKey() const { return mKey; } + MASK getMask() const { return mMask; } + const std::string& getTrigger() const { return mTrigger; } + const LLUUID& getSound() const { return mSoundItemID; } + const std::string& getAnimation() const { return mAnimation; } + const std::string& getOutputString() const { return mOutputString; } + + // Triggers if a key/mask matches it + virtual BOOL trigger(KEY key, MASK mask); + + // Triggers if case-insensitive substring matches (assumes string is lowercase) + virtual BOOL trigger(const LLString &string); + + // non-endian-neutral serialization + U8 *serialize(U8 *buffer) const; + U8 *deserialize(U8 *buffer, S32 max_size); + static S32 getMaxSerialSize(); + +protected: + KEY mKey; // usually a function key + MASK mMask; // usually MASK_NONE, or MASK_SHIFT + std::string mTrigger; // string, no whitespace allowed + std::string mTriggerLower; // lowercase version of mTrigger + LLUUID mSoundItemID; // ItemID of sound to play, LLUUID::null if none + std::string mAnimation; // canonical name of animation or face animation + std::string mOutputString; // string to say + + static const S32 MAX_SERIAL_SIZE; +}; + +class LLGestureList +{ +public: + LLGestureList(); + virtual ~LLGestureList(); + + // Triggers if a key/mask matches one in the list + BOOL trigger(KEY key, MASK mask); + + // Triggers if substring matches and generates revised string. + BOOL triggerAndReviseString(const LLString &string, LLString* revised_string); + + // Used for construction from UI + S32 count() const { return mList.count(); } + virtual LLGesture* get(S32 i) const { return mList.get(i); } + virtual void put(LLGesture* gesture) { mList.put( gesture ); } + void deleteAll(); + + // non-endian-neutral serialization + U8 *serialize(U8 *buffer) const; + U8 *deserialize(U8 *buffer, S32 max_size); + S32 getMaxSerialSize(); + +protected: + // overridden by child class to use local LLGesture implementation + virtual LLGesture *create_gesture(U8 **buffer, S32 max_size); + +protected: + LLDynamicArray mList; + + static const S32 SERIAL_HEADER_SIZE; +}; + +#endif diff --git a/indra/llcharacter/llhandmotion.cpp b/indra/llcharacter/llhandmotion.cpp new file mode 100644 index 0000000000..d574363f83 --- /dev/null +++ b/indra/llcharacter/llhandmotion.cpp @@ -0,0 +1,202 @@ +/** + * @file llhandmotion.cpp + * @brief Implementation of LLHandMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llhandmotion.h" +#include "llcharacter.h" +#include "m3math.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- + +const char *gHandPoseNames[LLHandMotion::NUM_HAND_POSES] = /* Flawfinder: ignore */ +{ + "", + "Hands_Relaxed", + "Hands_Point", + "Hands_Fist", + "Hands_Relaxed_L", + "Hands_Point_L", + "Hands_Fist_L", + "Hands_Relaxed_R", + "Hands_Point_R", + "Hands_Fist_R", + "Hands_Salute_R", + "Hands_Typing", + "Hands_Peace_R", + "Hands_Spread_R" +}; + +const F32 HAND_MORPH_BLEND_TIME = 0.2f; + +//----------------------------------------------------------------------------- +// LLHandMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLHandMotion::LLHandMotion(const LLUUID &id) : LLMotion(id) +{ + mCharacter = NULL; + mLastTime = 0.f; + mCurrentPose = HAND_POSE_RELAXED; + mNewPose = HAND_POSE_RELAXED; + mName = "hand_motion"; + + //RN: flag hand joint as highest priority for now, until we implement a proper animation track + mJointSignature[0][LL_HAND_JOINT_NUM] = 0xff; + mJointSignature[1][LL_HAND_JOINT_NUM] = 0xff; + mJointSignature[2][LL_HAND_JOINT_NUM] = 0xff; +} + + +//----------------------------------------------------------------------------- +// ~LLHandMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLHandMotion::~LLHandMotion() +{ +} + +//----------------------------------------------------------------------------- +// LLHandMotion::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLHandMotion::onInitialize(LLCharacter *character) +{ + mCharacter = character; + + return STATUS_SUCCESS; +} + + +//----------------------------------------------------------------------------- +// LLHandMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLHandMotion::onActivate() +{ + LLPolyMesh *upperBodyMesh = mCharacter->getUpperBodyMesh(); + + if (upperBodyMesh) + { + // Note: 0 is the default + for (S32 i = 1; i < LLHandMotion::NUM_HAND_POSES; i++) + { + mCharacter->setVisualParamWeight(gHandPoseNames[i], 0.f); + } + mCharacter->setVisualParamWeight(gHandPoseNames[mCurrentPose], 1.f); + mCharacter->updateVisualParams(); + } + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLHandMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLHandMotion::onUpdate(F32 time, U8* joint_mask) +{ + eHandPose *requestedHandPose; + + F32 timeDelta = time - mLastTime; + mLastTime = time; + + requestedHandPose = (eHandPose *)mCharacter->getAnimationData("Hand Pose"); + // check to see if requested pose has changed + if (!requestedHandPose) + { + if (mNewPose != HAND_POSE_RELAXED && mNewPose != mCurrentPose) + { + mCharacter->setVisualParamWeight(gHandPoseNames[mNewPose], 0.f); + } + mNewPose = HAND_POSE_RELAXED; + } + else + { + // this is a new morph we didn't know about before + if (*requestedHandPose != mNewPose && mNewPose != mCurrentPose && mNewPose != HAND_POSE_SPREAD) + { + mCharacter->setVisualParamWeight(gHandPoseNames[mNewPose], 0.f); + } + mNewPose = *requestedHandPose; + } + + mCharacter->removeAnimationData("Hand Pose"); + mCharacter->removeAnimationData("Hand Pose Priority"); + +// if (requestedHandPose) +// llinfos << "Hand Pose " << *requestedHandPose << llendl; + + // if we are still blending... + if (mCurrentPose != mNewPose) + { + F32 incomingWeight = 1.f; + F32 outgoingWeight = 0.f; + + if (mNewPose != HAND_POSE_SPREAD) + { + incomingWeight = mCharacter->getVisualParamWeight(gHandPoseNames[mNewPose]); + incomingWeight += (timeDelta / HAND_MORPH_BLEND_TIME); + incomingWeight = llclamp(incomingWeight, 0.f, 1.f); + mCharacter->setVisualParamWeight(gHandPoseNames[mNewPose], incomingWeight); + } + + if (mCurrentPose != HAND_POSE_SPREAD) + { + outgoingWeight = mCharacter->getVisualParamWeight(gHandPoseNames[mCurrentPose]); + outgoingWeight -= (timeDelta / HAND_MORPH_BLEND_TIME); + outgoingWeight = llclamp(outgoingWeight, 0.f, 1.f); + mCharacter->setVisualParamWeight(gHandPoseNames[mCurrentPose], outgoingWeight); + } + + mCharacter->updateVisualParams(); + + if (incomingWeight == 1.f && outgoingWeight == 0.f) + { + mCurrentPose = mNewPose; + } + } + + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLHandMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLHandMotion::onDeactivate() +{ +} + +//----------------------------------------------------------------------------- +// LLHandMotion::getHandPoseName() +//----------------------------------------------------------------------------- +LLString LLHandMotion::getHandPoseName(eHandPose pose) +{ + if ((S32)pose < LLHandMotion::NUM_HAND_POSES && (S32)pose >= 0) + { + return gHandPoseNames[pose]; + } + return ""; +} + +LLHandMotion::eHandPose LLHandMotion::getHandPose(LLString posename) +{ + for (S32 pose = 0; pose < LLHandMotion::NUM_HAND_POSES; ++pose) + { + if (gHandPoseNames[pose] == posename) + { + return (eHandPose)pose; + } + } + return (eHandPose)0; +} + +// End diff --git a/indra/llcharacter/llhandmotion.h b/indra/llcharacter/llhandmotion.h new file mode 100644 index 0000000000..cef7361633 --- /dev/null +++ b/indra/llcharacter/llhandmotion.h @@ -0,0 +1,119 @@ +/** + * @file llhandmotion.h + * @brief Implementation of LLHandMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLHANDMOTION_H +#define LL_LLHANDMOTION_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include "llmotion.h" +#include "lltimer.h" + +#define MIN_REQUIRED_PIXEL_AREA_HAND 10000.f; + +//----------------------------------------------------------------------------- +// class LLHandMotion +//----------------------------------------------------------------------------- +class LLHandMotion : + public LLMotion +{ +public: + typedef enum e_hand_pose + { + HAND_POSE_SPREAD, + HAND_POSE_RELAXED, + HAND_POSE_POINT, + HAND_POSE_FIST, + HAND_POSE_RELAXED_L, + HAND_POSE_POINT_L, + HAND_POSE_FIST_L, + HAND_POSE_RELAXED_R, + HAND_POSE_POINT_R, + HAND_POSE_FIST_R, + HAND_POSE_SALUTE_R, + HAND_POSE_TYPING, + HAND_POSE_PEACE_R, + HAND_POSE_PALM_R, + NUM_HAND_POSES + } eHandPose; + + // Constructor + LLHandMotion(const LLUUID &id); + + // Destructor + virtual ~LLHandMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLHandMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual BOOL getLoop() { return TRUE; } + + // motions must report their total duration + virtual F32 getDuration() { return 0.0; } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { return 0.0; } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { return 0.0; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_HAND; } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { return LLJoint::MEDIUM_PRIORITY; } + + virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + virtual BOOL onActivate(); + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + virtual BOOL onUpdate(F32 time, U8* joint_mask); + + // called when a motion is deactivated + virtual void onDeactivate(); + + static LLString getHandPoseName(eHandPose pose); + static eHandPose getHandPose(LLString posename); + +public: + //------------------------------------------------------------------------- + // joint states to be animated + //------------------------------------------------------------------------- + + LLCharacter *mCharacter; + + F32 mLastTime; + eHandPose mCurrentPose; + eHandPose mNewPose; +}; +#endif // LL_LLHANDMOTION_H + diff --git a/indra/llcharacter/llheadrotmotion.cpp b/indra/llcharacter/llheadrotmotion.cpp new file mode 100644 index 0000000000..4ff7e6b582 --- /dev/null +++ b/indra/llcharacter/llheadrotmotion.cpp @@ -0,0 +1,505 @@ +/** + * @file llheadrotmotion.cpp + * @brief Implementation of LLHeadRotMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llheadrotmotion.h" +#include "llcharacter.h" +#include "llrand.h" +#include "m3math.h" +#include "v3dmath.h" +#include "llcriticaldamp.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +const F32 TORSO_LAG = 0.35f; // torso rotation factor +const F32 NECK_LAG = 0.5f; // neck rotation factor +const F32 HEAD_LOOKAT_LAG_HALF_LIFE = 0.15f; // half-life of lookat targeting for head +const F32 TORSO_LOOKAT_LAG_HALF_LIFE = 0.27f; // half-life of lookat targeting for torso +const F32 EYE_LOOKAT_LAG_HALF_LIFE = 0.06f; // half-life of lookat targeting for eye +const F32 HEAD_ROTATION_CONSTRAINT = F_PI_BY_TWO * 0.8f; // limit angle for head rotation + +const F32 MIN_HEAD_LOOKAT_DISTANCE = 0.3f; // minimum distance from head before we turn to look at it +const F32 MAX_TIME_DELTA = 2.f; //max two seconds a frame for calculating interpolation +const F32 EYE_JITTER_MIN_TIME = 0.3f; // min amount of time between eye "jitter" motions +const F32 EYE_JITTER_MAX_TIME = 2.5f; // max amount of time between eye "jitter" motions +const F32 EYE_JITTER_MAX_YAW = 0.08f; // max yaw of eye jitter motion +const F32 EYE_JITTER_MAX_PITCH = 0.015f; // max pitch of eye jitter motion +const F32 EYE_LOOK_AWAY_MIN_TIME = 5.f; // min amount of time between eye "look away" motions +const F32 EYE_LOOK_AWAY_MAX_TIME = 15.f; // max amount of time between eye "look away" motions +const F32 EYE_LOOK_BACK_MIN_TIME = 1.f; // min amount of time before looking back after looking away +const F32 EYE_LOOK_BACK_MAX_TIME = 5.f; // max amount of time before looking back after looking away +const F32 EYE_LOOK_AWAY_MAX_YAW = 0.15f; // max yaw of eye look away motion +const F32 EYE_LOOK_AWAY_MAX_PITCH = 0.12f; // max pitch of look away motion +const F32 EYE_ROT_LIMIT_ANGLE = F_PI_BY_TWO * 0.3f; //max angle in radians for eye rotation + +const F32 EYE_BLINK_MIN_TIME = 0.5f; // minimum amount of time between blinks +const F32 EYE_BLINK_MAX_TIME = 8.f; // maximum amount of time between blinks +const F32 EYE_BLINK_CLOSE_TIME = 0.03f; // how long the eye stays closed in a blink +const F32 EYE_BLINK_SPEED = 0.015f; // seconds it takes for a eye open/close movement +const F32 EYE_BLINK_TIME_DELTA = 0.005f; // time between one eye starting a blink and the other following + +//----------------------------------------------------------------------------- +// LLHeadRotMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLHeadRotMotion::LLHeadRotMotion(const LLUUID &id) : + LLMotion(id), + mCharacter(NULL), + mTorsoJoint(NULL), + mHeadJoint(NULL) +{ + mName = "head_rot"; +} + + +//----------------------------------------------------------------------------- +// ~LLHeadRotMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLHeadRotMotion::~LLHeadRotMotion() +{ +} + +//----------------------------------------------------------------------------- +// LLHeadRotMotion::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLHeadRotMotion::onInitialize(LLCharacter *character) +{ + if (!character) + return STATUS_FAILURE; + mCharacter = character; + + mPelvisJoint = character->getJoint("mPelvis"); + if ( ! mPelvisJoint ) + { + llinfos << getName() << ": Can't get pelvis joint." << llendl; + return STATUS_FAILURE; + } + + mRootJoint = character->getJoint("mRoot"); + if ( ! mRootJoint ) + { + llinfos << getName() << ": Can't get root joint." << llendl; + return STATUS_FAILURE; + } + + mTorsoJoint = character->getJoint("mTorso"); + if ( ! mTorsoJoint ) + { + llinfos << getName() << ": Can't get torso joint." << llendl; + return STATUS_FAILURE; + } + + mHeadJoint = character->getJoint("mHead"); + if ( ! mHeadJoint ) + { + llinfos << getName() << ": Can't get head joint." << llendl; + return STATUS_FAILURE; + } + + mTorsoState.setJoint( character->getJoint("mTorso") ); + if ( ! mTorsoState.getJoint() ) + { + llinfos << getName() << ": Can't get torso joint." << llendl; + return STATUS_FAILURE; + } + + mNeckState.setJoint( character->getJoint("mNeck") ); + if ( ! mNeckState.getJoint() ) + { + llinfos << getName() << ": Can't get neck joint." << llendl; + return STATUS_FAILURE; + } + + mHeadState.setJoint( character->getJoint("mHead") ); + if ( ! mHeadState.getJoint() ) + { + llinfos << getName() << ": Can't get head joint." << llendl; + return STATUS_FAILURE; + } + + mTorsoState.setUsage(LLJointState::ROT); + mNeckState.setUsage(LLJointState::ROT); + mHeadState.setUsage(LLJointState::ROT); + + addJointState( &mTorsoState ); + addJointState( &mNeckState ); + addJointState( &mHeadState ); + + mLastHeadRot.loadIdentity(); + + return STATUS_SUCCESS; +} + + +//----------------------------------------------------------------------------- +// LLHeadRotMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLHeadRotMotion::onActivate() +{ + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLHeadRotMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLHeadRotMotion::onUpdate(F32 time, U8* joint_mask) +{ + LLQuaternion targetHeadRotWorld; + LLQuaternion currentRootRotWorld = mRootJoint->getWorldRotation(); + LLQuaternion currentInvRootRotWorld = ~currentRootRotWorld; + + F32 head_slerp_amt = LLCriticalDamp::getInterpolant(HEAD_LOOKAT_LAG_HALF_LIFE); + F32 torso_slerp_amt = LLCriticalDamp::getInterpolant(TORSO_LOOKAT_LAG_HALF_LIFE); + + LLVector3* targetPos = (LLVector3*)mCharacter->getAnimationData("LookAtPoint"); + + if (targetPos) + { + LLVector3 headLookAt = *targetPos; + +// llinfos << "Look At: " << headLookAt + mHeadJoint->getWorldPosition() << llendl; + + F32 lookatDistance = headLookAt.normVec(); + + if (lookatDistance < MIN_HEAD_LOOKAT_DISTANCE) + { + targetHeadRotWorld = mPelvisJoint->getWorldRotation(); + } + else + { + LLVector3 root_up = LLVector3(0.f, 0.f, 1.f) * currentRootRotWorld; + LLVector3 left(root_up % headLookAt); + // if look_at has zero length, fail + // if look_at and skyward are parallel, fail + // + // Test both of these conditions with a cross product. + + if (left.magVecSquared() < 0.15f) + { + LLVector3 root_at = LLVector3(1.f, 0.f, 0.f) * currentRootRotWorld; + root_at.mV[VZ] = 0.f; + root_at.normVec(); + + headLookAt = lerp(headLookAt, root_at, 0.4f); + headLookAt.normVec(); + + left = root_up % headLookAt; + } + + // Make sure look_at and skyward and not parallel + // and neither are zero length + LLVector3 up(headLookAt % left); + + targetHeadRotWorld = LLQuaternion(headLookAt, left, up); + } + } + else + { + targetHeadRotWorld = currentRootRotWorld; + } + + LLQuaternion head_rot_local = targetHeadRotWorld * currentInvRootRotWorld; + head_rot_local.constrain(HEAD_ROTATION_CONSTRAINT); + + // set final torso rotation + // Set torso target rotation such that it lags behind the head rotation + // by a fixed amount. + LLQuaternion torso_rot_local = nlerp(TORSO_LAG, LLQuaternion::DEFAULT, head_rot_local ); + mTorsoState.setRotation( nlerp(torso_slerp_amt, mTorsoState.getRotation(), torso_rot_local) ); + + head_rot_local = nlerp(head_slerp_amt, mLastHeadRot, head_rot_local); + mLastHeadRot = head_rot_local; + + // Set the head rotation. + LLQuaternion torsoRotLocal = mNeckState.getJoint()->getParent()->getWorldRotation() * currentInvRootRotWorld; + head_rot_local = head_rot_local * ~torsoRotLocal; + mNeckState.setRotation( nlerp(NECK_LAG, LLQuaternion::DEFAULT, head_rot_local) ); + mHeadState.setRotation( nlerp(1.f - NECK_LAG, LLQuaternion::DEFAULT, head_rot_local)); + + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLHeadRotMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLHeadRotMotion::onDeactivate() +{ +} + + +//----------------------------------------------------------------------------- +// LLEyeMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLEyeMotion::LLEyeMotion(const LLUUID &id) : LLMotion(id) +{ + mCharacter = NULL; + mEyeJitterTime = 0.f; + mEyeJitterYaw = 0.f; + mEyeJitterPitch = 0.f; + + mEyeLookAwayTime = 0.f; + mEyeLookAwayYaw = 0.f; + mEyeLookAwayPitch = 0.f; + + mEyeBlinkTime = 0.f; + mEyesClosed = FALSE; + + mHeadJoint = NULL; + + mName = "eye_rot"; +} + + +//----------------------------------------------------------------------------- +// ~LLEyeMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLEyeMotion::~LLEyeMotion() +{ +} + +//----------------------------------------------------------------------------- +// LLEyeMotion::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLEyeMotion::onInitialize(LLCharacter *character) +{ + mCharacter = character; + + mHeadJoint = character->getJoint("mHead"); + if ( ! mHeadJoint ) + { + llinfos << getName() << ": Can't get head joint." << llendl; + return STATUS_FAILURE; + } + + mLeftEyeState.setJoint( character->getJoint("mEyeLeft") ); + if ( ! mLeftEyeState.getJoint() ) + { + llinfos << getName() << ": Can't get left eyeball joint." << llendl; + return STATUS_FAILURE; + } + + mRightEyeState.setJoint( character->getJoint("mEyeRight") ); + if ( ! mRightEyeState.getJoint() ) + { + llinfos << getName() << ": Can't get Right eyeball joint." << llendl; + return STATUS_FAILURE; + } + + mLeftEyeState.setUsage(LLJointState::ROT); + mRightEyeState.setUsage(LLJointState::ROT); + + addJointState( &mLeftEyeState ); + addJointState( &mRightEyeState ); + + return STATUS_SUCCESS; +} + + +//----------------------------------------------------------------------------- +// LLEyeMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLEyeMotion::onActivate() +{ + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLEyeMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLEyeMotion::onUpdate(F32 time, U8* joint_mask) +{ + // Compute eye rotation. + LLQuaternion target_eye_rot; + LLVector3 eye_look_at; + F32 vergence; + + //calculate jitter + if (mEyeJitterTimer.getElapsedTimeF32() > mEyeJitterTime) + { + mEyeJitterTime = EYE_JITTER_MIN_TIME + frand(EYE_JITTER_MAX_TIME - EYE_JITTER_MIN_TIME); + mEyeJitterYaw = (frand(2.f) - 1.f) * EYE_JITTER_MAX_YAW; + mEyeJitterPitch = (frand(2.f) - 1.f) * EYE_JITTER_MAX_PITCH; + // make sure lookaway time count gets updated, because we're resetting the timer + mEyeLookAwayTime -= llmax(0.f, mEyeJitterTimer.getElapsedTimeF32()); + mEyeJitterTimer.reset(); + } + else if (mEyeJitterTimer.getElapsedTimeF32() > mEyeLookAwayTime) + { + if (frand(1.f) > 0.1f) + { + // blink while moving eyes some percentage of the time + mEyeBlinkTime = mEyeBlinkTimer.getElapsedTimeF32(); + } + if (mEyeLookAwayYaw == 0.f && mEyeLookAwayPitch == 0.f) + { + mEyeLookAwayYaw = (frand(2.f) - 1.f) * EYE_LOOK_AWAY_MAX_YAW; + mEyeLookAwayPitch = (frand(2.f) - 1.f) * EYE_LOOK_AWAY_MAX_PITCH; + mEyeLookAwayTime = EYE_LOOK_BACK_MIN_TIME + frand(EYE_LOOK_BACK_MAX_TIME - EYE_LOOK_BACK_MIN_TIME); + } + else + { + mEyeLookAwayYaw = 0.f; + mEyeLookAwayPitch = 0.f; + mEyeLookAwayTime = EYE_LOOK_AWAY_MIN_TIME + frand(EYE_LOOK_AWAY_MAX_TIME - EYE_LOOK_AWAY_MIN_TIME); + } + } + + // do blinking + if (!mEyesClosed && mEyeBlinkTimer.getElapsedTimeF32() >= mEyeBlinkTime) + { + F32 leftEyeBlinkMorph = mEyeBlinkTimer.getElapsedTimeF32() - mEyeBlinkTime; + F32 rightEyeBlinkMorph = leftEyeBlinkMorph - EYE_BLINK_TIME_DELTA; + + leftEyeBlinkMorph = llclamp(leftEyeBlinkMorph / EYE_BLINK_SPEED, 0.f, 1.f); + rightEyeBlinkMorph = llclamp(rightEyeBlinkMorph / EYE_BLINK_SPEED, 0.f, 1.f); + mCharacter->setVisualParamWeight("Blink_Left", leftEyeBlinkMorph); + mCharacter->setVisualParamWeight("Blink_Right", rightEyeBlinkMorph); + mCharacter->updateVisualParams(); + + if (rightEyeBlinkMorph == 1.f) + { + mEyesClosed = TRUE; + mEyeBlinkTime = EYE_BLINK_CLOSE_TIME; + mEyeBlinkTimer.reset(); + } + } + else if (mEyesClosed) + { + if (mEyeBlinkTimer.getElapsedTimeF32() >= mEyeBlinkTime) + { + F32 leftEyeBlinkMorph = mEyeBlinkTimer.getElapsedTimeF32() - mEyeBlinkTime; + F32 rightEyeBlinkMorph = leftEyeBlinkMorph - EYE_BLINK_TIME_DELTA; + + leftEyeBlinkMorph = 1.f - llclamp(leftEyeBlinkMorph / EYE_BLINK_SPEED, 0.f, 1.f); + rightEyeBlinkMorph = 1.f - llclamp(rightEyeBlinkMorph / EYE_BLINK_SPEED, 0.f, 1.f); + mCharacter->setVisualParamWeight("Blink_Left", leftEyeBlinkMorph); + mCharacter->setVisualParamWeight("Blink_Right", rightEyeBlinkMorph); + mCharacter->updateVisualParams(); + + if (rightEyeBlinkMorph == 0.f) + { + mEyesClosed = FALSE; + mEyeBlinkTime = EYE_BLINK_MIN_TIME + frand(EYE_BLINK_MAX_TIME - EYE_BLINK_MIN_TIME); + mEyeBlinkTimer.reset(); + } + } + } + + BOOL has_eye_target = FALSE; + LLVector3* targetPos = (LLVector3*)mCharacter->getAnimationData("LookAtPoint"); + + if (targetPos) + { + LLVector3 skyward(0.f, 0.f, 1.f); + LLVector3 left; + LLVector3 up; + + eye_look_at = *targetPos; + F32 lookAtDistance = eye_look_at.normVec(); + + left.setVec(skyward % eye_look_at); + up.setVec(eye_look_at % left); + + target_eye_rot = LLQuaternion(eye_look_at, left, up); + + // calculate vergence + F32 interocular_dist = (mLeftEyeState.getJoint()->getWorldPosition() - mRightEyeState.getJoint()->getWorldPosition()).magVec(); + vergence = -atan2((interocular_dist / 2.f), lookAtDistance); + llclamp(vergence, -F_PI_BY_TWO, 0.f); + } + else + { + target_eye_rot = mHeadJoint->getWorldRotation(); + vergence = 0.f; + } + + //RN: subtract 4 degrees to account for foveal angular offset relative to pupil + vergence += 4.f * DEG_TO_RAD; + + // calculate eye jitter + LLQuaternion eye_jitter_rot; + + // vergence not too high... + if (vergence > -0.05f) + { + //...go ahead and jitter + eye_jitter_rot.setQuat(0.f, mEyeJitterPitch + mEyeLookAwayPitch, mEyeJitterYaw + mEyeLookAwayYaw); + } + else + { + //...or don't + eye_jitter_rot.loadIdentity(); + } + + // calculate vergence of eyes as an object gets closer to the avatar's head + LLQuaternion vergence_quat; + + if (has_eye_target) + { + vergence_quat.setQuat(vergence, LLVector3(0.f, 0.f, 1.f)); + } + else + { + vergence_quat.loadIdentity(); + } + + // calculate eye rotations + LLQuaternion left_eye_rot = target_eye_rot; + left_eye_rot = vergence_quat * eye_jitter_rot * left_eye_rot; + + LLQuaternion right_eye_rot = target_eye_rot; + vergence_quat.transQuat(); + right_eye_rot = vergence_quat * eye_jitter_rot * right_eye_rot; + + //set final eye rotations + // start with left + LLQuaternion tQw = mLeftEyeState.getJoint()->getParent()->getWorldRotation(); + LLQuaternion tQh = left_eye_rot * ~tQw; + tQh.constrain(EYE_ROT_LIMIT_ANGLE); + mLeftEyeState.setRotation( tQh ); + + // now do right eye + tQw = mRightEyeState.getJoint()->getParent()->getWorldRotation(); + tQh = right_eye_rot * ~tQw; + tQh.constrain(EYE_ROT_LIMIT_ANGLE); + mRightEyeState.setRotation( tQh ); + + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLEyeMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLEyeMotion::onDeactivate() +{ + LLJoint* joint = mLeftEyeState.getJoint(); + if (joint) + { + joint->setRotation(LLQuaternion::DEFAULT); + } + + joint = mRightEyeState.getJoint(); + if (joint) + { + joint->setRotation(LLQuaternion::DEFAULT); + } +} + +// End diff --git a/indra/llcharacter/llheadrotmotion.h b/indra/llcharacter/llheadrotmotion.h new file mode 100644 index 0000000000..dc327cf44b --- /dev/null +++ b/indra/llcharacter/llheadrotmotion.h @@ -0,0 +1,194 @@ +/** + * @file llheadrotmotion.h + * @brief Implementation of LLHeadRotMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLHEADROTMOTION_H +#define LL_LLHEADROTMOTION_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include "llmotion.h" +#include "llframetimer.h" + +#define MIN_REQUIRED_PIXEL_AREA_HEAD_ROT 500.f; +#define MIN_REQUIRED_PIXEL_AREA_EYE 25000.f; + +//----------------------------------------------------------------------------- +// class LLHeadRotMotion +//----------------------------------------------------------------------------- +class LLHeadRotMotion : + public LLMotion +{ +public: + // Constructor + LLHeadRotMotion(const LLUUID &id); + + // Destructor + virtual ~LLHeadRotMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLHeadRotMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual BOOL getLoop() { return TRUE; } + + // motions must report their total duration + virtual F32 getDuration() { return 0.0; } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { return 1.f; } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { return 1.f; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_HEAD_ROT; } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { return LLJoint::MEDIUM_PRIORITY; } + + virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + virtual BOOL onActivate(); + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + virtual BOOL onUpdate(F32 time, U8* joint_mask); + + // called when a motion is deactivated + virtual void onDeactivate(); + +public: + //------------------------------------------------------------------------- + // joint states to be animated + //------------------------------------------------------------------------- + LLCharacter *mCharacter; + + LLJoint *mTorsoJoint; + LLJoint *mHeadJoint; + LLJoint *mRootJoint; + LLJoint *mPelvisJoint; + + LLJointState mTorsoState; + LLJointState mNeckState; + LLJointState mHeadState; + + LLQuaternion mLastHeadRot; +}; + +//----------------------------------------------------------------------------- +// class LLEyeMotion +//----------------------------------------------------------------------------- +class LLEyeMotion : + public LLMotion +{ +public: + // Constructor + LLEyeMotion(const LLUUID &id); + + // Destructor + virtual ~LLEyeMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create( const LLUUID &id) { return new LLEyeMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual BOOL getLoop() { return TRUE; } + + // motions must report their total duration + virtual F32 getDuration() { return 0.0; } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { return 0.5f; } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { return 0.5f; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_EYE; } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { return LLJoint::MEDIUM_PRIORITY; } + + virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + virtual BOOL onActivate(); + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + virtual BOOL onUpdate(F32 time, U8* joint_mask); + + // called when a motion is deactivated + virtual void onDeactivate(); + +public: + //------------------------------------------------------------------------- + // joint states to be animated + //------------------------------------------------------------------------- + LLCharacter *mCharacter; + + LLJoint *mHeadJoint; + LLJointState mLeftEyeState; + LLJointState mRightEyeState; + + LLFrameTimer mEyeJitterTimer; + F32 mEyeJitterTime; + F32 mEyeJitterYaw; + F32 mEyeJitterPitch; + F32 mEyeLookAwayTime; + F32 mEyeLookAwayYaw; + F32 mEyeLookAwayPitch; + + // eye blinking + LLFrameTimer mEyeBlinkTimer; + F32 mEyeBlinkTime; + BOOL mEyesClosed; +}; + +#endif // LL_LLHEADROTMOTION_H + diff --git a/indra/llcharacter/lljoint.cpp b/indra/llcharacter/lljoint.cpp new file mode 100644 index 0000000000..3924c06adc --- /dev/null +++ b/indra/llcharacter/lljoint.cpp @@ -0,0 +1,504 @@ +/** + * @file lljoint.cpp + * @brief Implementation of LLJoint class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "lljoint.h" + +#include "llmath.h" + +S32 LLJoint::sNumUpdates = 0; +S32 LLJoint::sNumTouches = 0; + +//----------------------------------------------------------------------------- +// LLJoint() +// Class Constructor +//----------------------------------------------------------------------------- +LLJoint::LLJoint() +{ + mName = "unnamed"; + mParent = NULL; + mXform.setScaleChildOffset(TRUE); + mXform.setScale(LLVector3(1.0f, 1.0f, 1.0f)); + mDirtyFlags = MATRIX_DIRTY | ROTATION_DIRTY | POSITION_DIRTY; + mUpdateXform = TRUE; + mJointNum = -1; + touch(); +} + + +//----------------------------------------------------------------------------- +// LLJoint() +// Class Constructor +//----------------------------------------------------------------------------- +LLJoint::LLJoint(const std::string &name, LLJoint *parent) +{ + mName = "unnamed"; + mParent = NULL; + mXform.setScaleChildOffset(TRUE); + mXform.setScale(LLVector3(1.0f, 1.0f, 1.0f)); + mDirtyFlags = MATRIX_DIRTY | ROTATION_DIRTY | POSITION_DIRTY; + mJointNum = 0; + + setName(name); + if (parent) + parent->addChild( this ); + + touch(); +} + +//----------------------------------------------------------------------------- +// ~LLJoint() +// Class Destructor +//----------------------------------------------------------------------------- +LLJoint::~LLJoint() +{ + removeAllChildren(); +} + + +//----------------------------------------------------------------------------- +// setup() +//----------------------------------------------------------------------------- +void LLJoint::setup(const std::string &name, LLJoint *parent) +{ + setName(name); + if (parent) + parent->addChild( this ); +} + +//----------------------------------------------------------------------------- +// touch() +// Sets all dirty flags for all children, recursively. +//----------------------------------------------------------------------------- +void LLJoint::touch(U32 flags) +{ + if ((flags | mDirtyFlags) != mDirtyFlags) + { + sNumTouches++; + mDirtyFlags |= flags; + U32 child_flags = flags; + if (flags & ROTATION_DIRTY) + { + child_flags |= POSITION_DIRTY; + } + + for ( LLJoint *joint = mChildren.getFirstData(); + joint != NULL; + joint = mChildren.getNextData() ) + { + joint->touch(child_flags); + } + } +} + +//----------------------------------------------------------------------------- +// getRoot() +//----------------------------------------------------------------------------- +LLJoint *LLJoint::getRoot() +{ + if ( getParent() == NULL ) + { + return this; + } + return getParent()->getRoot(); +} + + +//----------------------------------------------------------------------------- +// findJoint() +//----------------------------------------------------------------------------- +LLJoint *LLJoint::findJoint( const std::string &name ) +{ + if (name == getName()) + return this; + + for ( LLJoint *j = mChildren.getFirstData(); + j != NULL; + j = mChildren.getNextData() ) + { + LLJoint *found = j->findJoint(name); + if (found) + return found; + } + + return NULL; +} + + +//-------------------------------------------------------------------- +// addChild() +//-------------------------------------------------------------------- +void LLJoint::addChild(LLJoint *joint) +{ + if (joint->mParent) + joint->mParent->removeChild(joint); + + mChildren.addDataAtEnd(joint); + joint->mXform.setParent(&mXform); + joint->mParent = this; + joint->touch(); +} + + +//-------------------------------------------------------------------- +// removeChild() +//-------------------------------------------------------------------- +void LLJoint::removeChild(LLJoint *joint) +{ + this->mChildren.removeData(joint); + joint->mXform.setParent(NULL); + joint->mParent = NULL; + joint->touch(); +} + + +//-------------------------------------------------------------------- +// removeAllChildren() +//-------------------------------------------------------------------- +void LLJoint::removeAllChildren() +{ + for ( LLJoint *joint = mChildren.getFirstData(); + joint != NULL; + joint = mChildren.getNextData() ) + { + removeChild(joint); + } +} + + +//-------------------------------------------------------------------- +// getPosition() +//-------------------------------------------------------------------- +const LLVector3& LLJoint::getPosition() +{ + return mXform.getPosition(); +} + + +//-------------------------------------------------------------------- +// setPosition() +//-------------------------------------------------------------------- +void LLJoint::setPosition( const LLVector3& pos ) +{ + mXform.setPosition(pos); + touch(MATRIX_DIRTY | POSITION_DIRTY); +} + + +//-------------------------------------------------------------------- +// getWorldPosition() +//-------------------------------------------------------------------- +LLVector3 LLJoint::getWorldPosition() +{ + updateWorldPRSParent(); + return mXform.getWorldPosition(); +} + +//----------------------------------------------------------------------------- +// getLastWorldPosition() +//----------------------------------------------------------------------------- +LLVector3 LLJoint::getLastWorldPosition() +{ + return mXform.getWorldPosition(); +} + + +//-------------------------------------------------------------------- +// setWorldPosition() +//-------------------------------------------------------------------- +void LLJoint::setWorldPosition( const LLVector3& pos ) +{ + if (mParent == NULL) + { + this->setPosition( pos ); + return; + } + + LLMatrix4 temp_matrix = getWorldMatrix(); + temp_matrix.mMatrix[VW][VX] = pos.mV[VX]; + temp_matrix.mMatrix[VW][VY] = pos.mV[VY]; + temp_matrix.mMatrix[VW][VZ] = pos.mV[VZ]; + + LLMatrix4 parentWorldMatrix = mParent->getWorldMatrix(); + LLMatrix4 invParentWorldMatrix = parentWorldMatrix.invert(); + + temp_matrix *= invParentWorldMatrix; + + LLVector3 localPos( temp_matrix.mMatrix[VW][VX], + temp_matrix.mMatrix[VW][VY], + temp_matrix.mMatrix[VW][VZ] ); + + setPosition( localPos ); +} + + +//-------------------------------------------------------------------- +// mXform.getRotation() +//-------------------------------------------------------------------- +const LLQuaternion& LLJoint::getRotation() +{ + return mXform.getRotation(); +} + + +//-------------------------------------------------------------------- +// setRotation() +//-------------------------------------------------------------------- +void LLJoint::setRotation( const LLQuaternion& rot ) +{ + if (rot.isFinite()) + { + mXform.setRotation(rot); + touch(MATRIX_DIRTY | ROTATION_DIRTY); + } +} + + +//-------------------------------------------------------------------- +// getWorldRotation() +//-------------------------------------------------------------------- +LLQuaternion LLJoint::getWorldRotation() +{ + updateWorldPRSParent(); + + return mXform.getWorldRotation(); +} + +//----------------------------------------------------------------------------- +// getLastWorldRotation() +//----------------------------------------------------------------------------- +LLQuaternion LLJoint::getLastWorldRotation() +{ + return mXform.getWorldRotation(); +} + +//-------------------------------------------------------------------- +// setWorldRotation() +//-------------------------------------------------------------------- +void LLJoint::setWorldRotation( const LLQuaternion& rot ) +{ + if (mParent == NULL) + { + this->setRotation( rot ); + return; + } + + LLMatrix4 temp_mat(rot); + + LLMatrix4 parentWorldMatrix = mParent->getWorldMatrix(); + parentWorldMatrix.mMatrix[VW][VX] = 0; + parentWorldMatrix.mMatrix[VW][VY] = 0; + parentWorldMatrix.mMatrix[VW][VZ] = 0; + + LLMatrix4 invParentWorldMatrix = parentWorldMatrix.invert(); + + temp_mat *= invParentWorldMatrix; + + setRotation(LLQuaternion(temp_mat)); +} + + +//-------------------------------------------------------------------- +// getScale() +//-------------------------------------------------------------------- +const LLVector3& LLJoint::getScale() +{ + return mXform.getScale(); +} + +//-------------------------------------------------------------------- +// setScale() +//-------------------------------------------------------------------- +void LLJoint::setScale( const LLVector3& scale ) +{ + mXform.setScale(scale); + touch(); +} + + + +//-------------------------------------------------------------------- +// getWorldMatrix() +//-------------------------------------------------------------------- +const LLMatrix4 &LLJoint::getWorldMatrix() +{ + updateWorldMatrixParent(); + + return mXform.getWorldMatrix(); +} + + +//-------------------------------------------------------------------- +// setWorldMatrix() +//-------------------------------------------------------------------- +void LLJoint::setWorldMatrix( const LLMatrix4& mat ) +{ +llinfos << "WARNING: LLJoint::setWorldMatrix() not correctly implemented yet" << llendl; + // extract global translation + LLVector3 trans( mat.mMatrix[VW][VX], + mat.mMatrix[VW][VY], + mat.mMatrix[VW][VZ] ); + + // extract global rotation + LLQuaternion rot( mat ); + + setWorldPosition( trans ); + setWorldRotation( rot ); +} + +//----------------------------------------------------------------------------- +// updateWorldMatrixParent() +//----------------------------------------------------------------------------- +void LLJoint::updateWorldMatrixParent() +{ + if (mDirtyFlags & MATRIX_DIRTY) + { + LLJoint *parent = getParent(); + if (parent) + { + parent->updateWorldMatrixParent(); + } + updateWorldMatrix(); + } +} + +//----------------------------------------------------------------------------- +// updateWorldPRSParent() +//----------------------------------------------------------------------------- +void LLJoint::updateWorldPRSParent() +{ + if (mDirtyFlags & (ROTATION_DIRTY | POSITION_DIRTY)) + { + LLJoint *parent = getParent(); + if (parent) + { + parent->updateWorldPRSParent(); + } + + mXform.update(); + mDirtyFlags &= ~(ROTATION_DIRTY | POSITION_DIRTY); + } +} + +//----------------------------------------------------------------------------- +// updateWorldMatrixChildren() +//----------------------------------------------------------------------------- +void LLJoint::updateWorldMatrixChildren() +{ + if (mDirtyFlags & MATRIX_DIRTY) + { + updateWorldMatrix(); + } + for (LLJoint *child = mChildren.getFirstData(); child; child = mChildren.getNextData()) + { + child->updateWorldMatrixChildren(); + } +} + +//----------------------------------------------------------------------------- +// updateWorldMatrix() +//----------------------------------------------------------------------------- +void LLJoint::updateWorldMatrix() +{ + if (mDirtyFlags & MATRIX_DIRTY) + { + sNumUpdates++; + mXform.updateMatrix(FALSE); + mDirtyFlags = 0x0; + } +} + +//-------------------------------------------------------------------- +// getSkinOffset() +//-------------------------------------------------------------------- +const LLVector3 &LLJoint::getSkinOffset() +{ + return mSkinOffset; +} + + +//-------------------------------------------------------------------- +// setSkinOffset() +//-------------------------------------------------------------------- +void LLJoint::setSkinOffset( const LLVector3& offset ) +{ + mSkinOffset = offset; +} + +//----------------------------------------------------------------------------- +// setConstraintSilhouette() +//----------------------------------------------------------------------------- +void LLJoint::setConstraintSilhouette(LLDynamicArray& silhouette) +{ + S32 i; + + mConstraintSilhouette.reset(); + for (i = 0; i < silhouette.count(); i++) + { + if (i % 2 == 1) + { + // skip normals + continue; + } + mConstraintSilhouette[i / 2] = silhouette[i]; + } + LLQuaternion inv_parent_rotation = LLQuaternion::DEFAULT; + + if (getParent()) + { + inv_parent_rotation = ~getParent()->getWorldRotation(); + } + + for (i = 0; i < mConstraintSilhouette.count(); i++) + { + LLVector3 vert = mConstraintSilhouette[i]; + + vert -= getWorldPosition(); + vert.normVec(); + vert = vert * inv_parent_rotation; + } +} + +//----------------------------------------------------------------------------- +// clampRotation() +//----------------------------------------------------------------------------- +void LLJoint::clampRotation(LLQuaternion old_rot, LLQuaternion new_rot) +{ + LLVector3 main_axis(1.f, 0.f, 0.f); + + for (LLJoint* joint = mChildren.getFirstData(); joint; joint = mChildren.getNextData()) + { + if (joint->isAnimatable()) + { + main_axis = joint->getPosition(); + main_axis.normVec(); + // only care about first animatable child + break; + } + } + + // 2003.03.26 - This code was just using up cpu cycles. AB + +// LLVector3 old_axis = main_axis * old_rot; +// LLVector3 new_axis = main_axis * new_rot; + +// for (S32 i = 0; i < mConstraintSilhouette.count() - 1; i++) +// { +// LLVector3 vert1 = mConstraintSilhouette[i]; +// LLVector3 vert2 = mConstraintSilhouette[i + 1]; + + // figure out how to clamp rotation to line on 3-sphere + +// } +} + +// End diff --git a/indra/llcharacter/lljoint.h b/indra/llcharacter/lljoint.h new file mode 100644 index 0000000000..2fc86e87df --- /dev/null +++ b/indra/llcharacter/lljoint.h @@ -0,0 +1,163 @@ +/** + * @file lljoint.h + * @brief Implementation of LLJoint class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLJOINT_H +#define LL_LLJOINT_H + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include + +#include "linked_lists.h" +#include "v3math.h" +#include "v4math.h" +#include "m4math.h" +#include "llquaternion.h" +#include "xform.h" +#include "lldarray.h" + +const S32 LL_CHARACTER_MAX_JOINTS_PER_MESH = 15; +const U32 LL_CHARACTER_MAX_JOINTS = 32; // must be divisible by 4! +const U32 LL_HAND_JOINT_NUM = 31; +const U32 LL_FACE_JOINT_NUM = 30; +const S32 LL_CHARACTER_MAX_PRIORITY = 7; +const F32 LL_MAX_PELVIS_OFFSET = 5.f; + +//----------------------------------------------------------------------------- +// class LLJoint +//----------------------------------------------------------------------------- +class LLJoint +{ +public: + // priority levels, from highest to lowest + enum JointPriority + { + USE_MOTION_PRIORITY = -1, + LOW_PRIORITY = 0, + MEDIUM_PRIORITY, + HIGH_PRIORITY, + HIGHER_PRIORITY, + HIGHEST_PRIORITY, + ADDITIVE_PRIORITY = LL_CHARACTER_MAX_PRIORITY + }; + + enum DirtyFlags + { + MATRIX_DIRTY = 0x1 << 0, + ROTATION_DIRTY = 0x1 << 1, + POSITION_DIRTY = 0x1 << 2, + ALL_DIRTY = 0x7 + }; +protected: + std::string mName; + + // parent joint + LLJoint *mParent; + + // explicit transformation members + LLXformMatrix mXform; + +public: + U32 mDirtyFlags; + BOOL mWorldRotationDirty; + BOOL mUpdateXform; + + // describes the skin binding pose + LLVector3 mSkinOffset; + + S32 mJointNum; + + LLDynamicArray mConstraintSilhouette; + + // child joints + LLLinkedList mChildren; + + // debug statics + static S32 sNumTouches; + static S32 sNumUpdates; + +public: + LLJoint(); + LLJoint( const std::string &name, LLJoint *parent=NULL ); + + virtual ~LLJoint(); + + // set name and parent + void setup( const std::string &name, LLJoint *parent=NULL ); + + void touch(U32 flags = ALL_DIRTY); + + // get/set name + const std::string &getName() { return mName; } + void setName( const std::string &name ) { mName = name; } + + // getParent + LLJoint *getParent() { return mParent; } + + // getRoot + LLJoint *getRoot(); + + // search for child joints by name + LLJoint *findJoint( const std::string &name ); + + // add/remove children + void addChild( LLJoint *joint ); + void removeChild( LLJoint *joint ); + void removeAllChildren(); + + // get/set local position + const LLVector3& getPosition(); + void setPosition( const LLVector3& pos ); + + // get/set world position + LLVector3 getWorldPosition(); + LLVector3 getLastWorldPosition(); + void setWorldPosition( const LLVector3& pos ); + + // get/set local rotation + const LLQuaternion& getRotation(); + void setRotation( const LLQuaternion& rot ); + + // get/set world rotation + LLQuaternion getWorldRotation(); + LLQuaternion getLastWorldRotation(); + void setWorldRotation( const LLQuaternion& rot ); + + // get/set local scale + const LLVector3& getScale(); + void setScale( const LLVector3& scale ); + + // get/set world matrix + const LLMatrix4 &getWorldMatrix(); + void setWorldMatrix( const LLMatrix4& mat ); + + void updateWorldMatrixChildren(); + void updateWorldMatrixParent(); + + void updateWorldPRSParent(); + + void updateWorldMatrix(); + + // get/set skin offset + const LLVector3 &getSkinOffset(); + void setSkinOffset( const LLVector3 &offset); + + LLXformMatrix *getXform() { return &mXform; } + + void setConstraintSilhouette(LLDynamicArray& silhouette); + + void clampRotation(LLQuaternion old_rot, LLQuaternion new_rot); + + virtual BOOL isAnimatable() { return TRUE; } + + S32 getJointNum() { return mJointNum; } + void setJointNum(S32 joint_num) { mJointNum = joint_num; } +}; +#endif // LL_LLJOINT_H + diff --git a/indra/llcharacter/lljointsolverrp3.cpp b/indra/llcharacter/lljointsolverrp3.cpp new file mode 100644 index 0000000000..60b836734a --- /dev/null +++ b/indra/llcharacter/lljointsolverrp3.cpp @@ -0,0 +1,366 @@ +/** + * @file lljointsolverrp3.cpp + * @brief Implementation of LLJointSolverRP3 class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "lljointsolverrp3.h" + +#include + +#include "llmath.h" + +#define F_EPSILON 0.00001f + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +LLJointSolverRP3::LLJointSolverRP3() +{ + mJointA = NULL; + mJointB = NULL; + mJointC = NULL; + mJointGoal = NULL; + mLengthAB = 1.0f; + mLengthBC = 1.0f; + mPoleVector.setVec( 1.0f, 0.0f, 0.0f ); + mbUseBAxis = FALSE; + mTwist = 0.0f; + mFirstTime = TRUE; +} + + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +/*virtual*/ LLJointSolverRP3::~LLJointSolverRP3() +{ +} + + +//----------------------------------------------------------------------------- +// setupJoints() +//----------------------------------------------------------------------------- +void LLJointSolverRP3::setupJoints( LLJoint* jointA, + LLJoint* jointB, + LLJoint* jointC, + LLJoint* jointGoal ) +{ + mJointA = jointA; + mJointB = jointB; + mJointC = jointC; + mJointGoal = jointGoal; + + mLengthAB = mJointB->getPosition().magVec(); + mLengthBC = mJointC->getPosition().magVec(); + + mJointABaseRotation = jointA->getRotation(); + mJointBBaseRotation = jointB->getRotation(); +} + + +//----------------------------------------------------------------------------- +// getPoleVector() +//----------------------------------------------------------------------------- +const LLVector3& LLJointSolverRP3::getPoleVector() +{ + return mPoleVector; +} + + +//----------------------------------------------------------------------------- +// setPoleVector() +//----------------------------------------------------------------------------- +void LLJointSolverRP3::setPoleVector( const LLVector3& poleVector ) +{ + mPoleVector = poleVector; + mPoleVector.normVec(); +} + + +//----------------------------------------------------------------------------- +// setPoleVector() +//----------------------------------------------------------------------------- +void LLJointSolverRP3::setBAxis( const LLVector3& bAxis ) +{ + mBAxis = bAxis; + mBAxis.normVec(); + mbUseBAxis = TRUE; +} + +//----------------------------------------------------------------------------- +// getTwist() +//----------------------------------------------------------------------------- +F32 LLJointSolverRP3::getTwist() +{ + return mTwist; +} + + +//----------------------------------------------------------------------------- +// setTwist() +//----------------------------------------------------------------------------- +void LLJointSolverRP3::setTwist( F32 twist ) +{ + mTwist = twist; +} + + +//----------------------------------------------------------------------------- +// solve() +//----------------------------------------------------------------------------- +void LLJointSolverRP3::solve() +{ +// llinfos << llendl; +// llinfos << "LLJointSolverRP3::solve()" << llendl; + + //------------------------------------------------------------------------- + // setup joints in their base rotations + //------------------------------------------------------------------------- + mJointA->setRotation( mJointABaseRotation ); + mJointB->setRotation( mJointBBaseRotation ); + + //------------------------------------------------------------------------- + // get joint positions in world space + //------------------------------------------------------------------------- + LLVector3 aPos = mJointA->getWorldPosition(); + LLVector3 bPos = mJointB->getWorldPosition(); + LLVector3 cPos = mJointC->getWorldPosition(); + LLVector3 gPos = mJointGoal->getWorldPosition(); + +// llinfos << "bPosLocal = " << mJointB->getPosition() << llendl; +// llinfos << "cPosLocal = " << mJointC->getPosition() << llendl; +// llinfos << "bRotLocal = " << mJointB->getRotation() << llendl; +// llinfos << "cRotLocal = " << mJointC->getRotation() << llendl; + +// llinfos << "aPos : " << aPos << llendl; +// llinfos << "bPos : " << bPos << llendl; +// llinfos << "cPos : " << cPos << llendl; +// llinfos << "gPos : " << gPos << llendl; + + //------------------------------------------------------------------------- + // get the poleVector in world space + //------------------------------------------------------------------------- + LLMatrix4 worldJointAParentMat; + if ( mJointA->getParent() ) + { + worldJointAParentMat = mJointA->getParent()->getWorldMatrix(); + } + LLVector3 poleVec = rotate_vector( mPoleVector, worldJointAParentMat ); + + //------------------------------------------------------------------------- + // compute the following: + // vector from A to B + // vector from B to C + // vector from A to C + // vector from A to G (goal) + //------------------------------------------------------------------------- + LLVector3 abVec = bPos - aPos; + LLVector3 bcVec = cPos - bPos; + LLVector3 acVec = cPos - aPos; + LLVector3 agVec = gPos - aPos; + +// llinfos << "abVec : " << abVec << llendl; +// llinfos << "bcVec : " << bcVec << llendl; +// llinfos << "acVec : " << acVec << llendl; +// llinfos << "agVec : " << agVec << llendl; + + //------------------------------------------------------------------------- + // compute needed lengths of those vectors + //------------------------------------------------------------------------- + F32 abLen = abVec.magVec(); + F32 bcLen = bcVec.magVec(); + F32 agLen = agVec.magVec(); + +// llinfos << "abLen : " << abLen << llendl; +// llinfos << "bcLen : " << bcLen << llendl; +// llinfos << "agLen : " << agLen << llendl; + + //------------------------------------------------------------------------- + // compute component vector of (A->B) orthogonal to (A->C) + //------------------------------------------------------------------------- + LLVector3 abacCompOrthoVec = abVec - acVec * ((abVec * acVec)/(acVec * acVec)); + +// llinfos << "abacCompOrthoVec : " << abacCompOrthoVec << llendl + + //------------------------------------------------------------------------- + // compute the normal of the original ABC plane (and store for later) + //------------------------------------------------------------------------- + LLVector3 abcNorm; + if (!mbUseBAxis) + { + if( are_parallel(abVec, bcVec, 0.001f) ) + { + // the current solution is maxed out, so we use the axis that is + // orthogonal to both poleVec and A->B + if ( are_parallel(poleVec, abVec, 0.001f) ) + { + // ACK! the problem is singular + if ( are_parallel(poleVec, agVec, 0.001f) ) + { + // the solutions is also singular + return; + } + else + { + abcNorm = poleVec % agVec; + } + } + else + { + abcNorm = poleVec % abVec; + } + } + else + { + abcNorm = abVec % bcVec; + } + } + else + { + abcNorm = mBAxis * mJointB->getWorldRotation(); + } + + //------------------------------------------------------------------------- + // compute rotation of B + //------------------------------------------------------------------------- + // angle between A->B and B->C + F32 abbcAng = angle_between(abVec, bcVec); + + // vector orthogonal to A->B and B->C + LLVector3 abbcOrthoVec = abVec % bcVec; + if (abbcOrthoVec.magVecSquared() < 0.001f) + { + abbcOrthoVec = poleVec % abVec; + abacCompOrthoVec = poleVec; + } + abbcOrthoVec.normVec(); + + F32 agLenSq = agLen * agLen; + + // angle arm for extension + F32 cosTheta = (agLenSq - abLen*abLen - bcLen*bcLen) / (2.0f * abLen * bcLen); + if (cosTheta > 1.0f) + cosTheta = 1.0f; + else if (cosTheta < -1.0f) + cosTheta = -1.0f; + + F32 theta = acos(cosTheta); + + LLQuaternion bRot(theta - abbcAng, abbcOrthoVec); + +// llinfos << "abbcAng : " << abbcAng << llendl; +// llinfos << "abbcOrthoVec : " << abbcOrthoVec << llendl; +// llinfos << "agLenSq : " << agLenSq << llendl; +// llinfos << "cosTheta : " << cosTheta << llendl; +// llinfos << "theta : " << theta << llendl; +// llinfos << "bRot : " << bRot << llendl; +// llinfos << "theta abbcAng theta-abbcAng: " << theta*180.0/F_PI << " " << abbcAng*180.0f/F_PI << " " << (theta - abbcAng)*180.0f/F_PI << llendl; + + //------------------------------------------------------------------------- + // compute rotation that rotates new A->C to A->G + //------------------------------------------------------------------------- + // rotate B->C by bRot + bcVec = bcVec * bRot; + + // update A->C + acVec = abVec + bcVec; + + LLQuaternion cgRot; + cgRot.shortestArc( acVec, agVec ); + +// llinfos << "bcVec : " << bcVec << llendl; +// llinfos << "acVec : " << acVec << llendl; +// llinfos << "cgRot : " << cgRot << llendl; + + // update A->B and B->C with rotation from C to G + abVec = abVec * cgRot; + bcVec = bcVec * cgRot; + abcNorm = abcNorm * cgRot; + acVec = abVec + bcVec; + + //------------------------------------------------------------------------- + // compute the normal of the APG plane + //------------------------------------------------------------------------- + if (are_parallel(agVec, poleVec, 0.001f)) + { + // the solution plane is undefined ==> we're done + return; + } + LLVector3 apgNorm = poleVec % agVec; + apgNorm.normVec(); + + if (!mbUseBAxis) + { + //--------------------------------------------------------------------- + // compute the normal of the new ABC plane + // (only necessary if we're NOT using mBAxis) + //--------------------------------------------------------------------- + if( are_parallel(abVec, bcVec, 0.001f) ) + { + // G is either too close or too far away + // we'll use the old ABCnormal + } + else + { + abcNorm = abVec % bcVec; + } + abcNorm.normVec(); + } + + //------------------------------------------------------------------------- + // calcuate plane rotation + //------------------------------------------------------------------------- + LLQuaternion pRot; + if ( are_parallel( abcNorm, apgNorm, 0.001f) ) + { + if (abcNorm * apgNorm < 0.0f) + { + // we must be PI radians off ==> rotate by PI around agVec + pRot.setQuat(F_PI, agVec); + } + else + { + // we're done + } + } + else + { + pRot.shortestArc( abcNorm, apgNorm ); + } + +// llinfos << "abcNorm = " << abcNorm << llendl; +// llinfos << "apgNorm = " << apgNorm << llendl; +// llinfos << "pRot = " << pRot << llendl; + + //------------------------------------------------------------------------- + // compute twist rotation + //------------------------------------------------------------------------- + LLQuaternion twistRot( mTwist, agVec ); + +// llinfos << "twist : " << mTwist*180.0/F_PI << llendl; +// llinfos << "agNormVec: " << agNormVec << llendl; +// llinfos << "twistRot : " << twistRot << llendl; + + //------------------------------------------------------------------------- + // compute rotation of A + //------------------------------------------------------------------------- + LLQuaternion aRot = cgRot * pRot * twistRot; + + //------------------------------------------------------------------------- + // apply the rotations + //------------------------------------------------------------------------- + mJointB->setWorldRotation( mJointB->getWorldRotation() * bRot ); + mJointA->setWorldRotation( mJointA->getWorldRotation() * aRot ); +} + + +// End diff --git a/indra/llcharacter/lljointsolverrp3.h b/indra/llcharacter/lljointsolverrp3.h new file mode 100644 index 0000000000..d1507351b6 --- /dev/null +++ b/indra/llcharacter/lljointsolverrp3.h @@ -0,0 +1,158 @@ +/** + * @file lljointsolverrp3.h + * @brief Implementation of LLJointSolverRP3 class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLJOINTSOLVERRP3_H +#define LL_LLJOINTSOLVERRP3_H + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "lljoint.h" + +/* -some compilers don't like line continuation chars- +//----------------------------------------------------------------------------- +// class LLJointSolverRP3 +// +// This class is a "poor man's" IK for simple 3 joint kinematic chains. +// It is modeled after the 'ikRPSolver' in Maya. +// This class takes 4 LLJoints: +// jointA +// jointB +// jointC +// jointGoal +// +// Such that jointA is the parent of jointB, jointB is the parent of jointC. +// When invoked, this class modifies the rotations of jointA and jointB such +// that the position of the jointC attempts to reach the position of jointGoal. +// +// At object initialization time, the distances between jointA - jointB and +// jointB - jointC are cached. During evaluation these bone lengths are +// preserved. +// +// A A +// | | +// | | +// B B---CG A---B---C...G +// \ +// \ +// CG +// +// +// In addition a "poleVector" is specified that does two things: +// +// a) defines the plane in which the solution occurs, thus +// reducing an infinite number of solutions, down to 2. +// +// b) disambiguates the resulting two solutions as follows: +// +// A A A--->poleVector +// | \ \ +// | \ \ +// B vs. B ==> B +// \ | | +// \ | | +// CG CG CG +// +// A "twist" setting allows the solution plane to be rotated about the +// line between A and C. A handy animation feature. +// +// For "smarter" results for non-coplanar limbs, specify the joints axis +// of bend in the B's local frame (see setBAxis()) +//----------------------------------------------------------------------------- +*/ + +class LLJointSolverRP3 +{ +protected: + LLJoint *mJointA; + LLJoint *mJointB; + LLJoint *mJointC; + LLJoint *mJointGoal; + + F32 mLengthAB; + F32 mLengthBC; + + LLVector3 mPoleVector; + LLVector3 mBAxis; + BOOL mbUseBAxis; + + F32 mTwist; + + BOOL mFirstTime; + LLMatrix4 mSavedJointAMat; + LLMatrix4 mSavedInvPlaneMat; + + LLQuaternion mJointABaseRotation; + LLQuaternion mJointBBaseRotation; + +public: + //------------------------------------------------------------------------- + // Constructor/Destructor + //------------------------------------------------------------------------- + LLJointSolverRP3(); + virtual ~LLJointSolverRP3(); + + //------------------------------------------------------------------------- + // setupJoints() + // This must be called one time to setup the solver. + // This must be called AFTER the skeleton has been created, all parent/child + // relationships are established, and after the joints are placed in + // a valid configuration (as distances between them will be cached). + //------------------------------------------------------------------------- + void setupJoints( LLJoint* jointA, + LLJoint* jointB, + LLJoint* jointC, + LLJoint* jointGoal ); + + //------------------------------------------------------------------------- + // getPoleVector() + // Returns the current pole vector. + //------------------------------------------------------------------------- + const LLVector3& getPoleVector(); + + //------------------------------------------------------------------------- + // setPoleVector() + // Sets the pole vector. + // The pole vector is defined relative to (in the space of) jointA's parent. + // The default pole vector is (1,0,0), and this is used if this function + // is never called. + // This vector is normalized when set. + //------------------------------------------------------------------------- + void setPoleVector( const LLVector3& poleVector ); + + //------------------------------------------------------------------------- + // setBAxis() + // Sets the joint's axis in B's local frame, and enable "smarter" solve(). + // This allows for smarter IK when for twisted limbs. + //------------------------------------------------------------------------- + void setBAxis( const LLVector3& bAxis ); + + //------------------------------------------------------------------------- + // getTwist() + // Returns the current twist in radians. + //------------------------------------------------------------------------- + F32 getTwist(); + + //------------------------------------------------------------------------- + // setTwist() + // Sets the twist value. + // The default is 0.0. + //------------------------------------------------------------------------- + void setTwist( F32 twist ); + + //------------------------------------------------------------------------- + // solve() + // This is the "work" function. + // When called, the rotations of jointA and jointB will be modified + // such that jointC attempts to reach jointGoal. + //------------------------------------------------------------------------- + void solve(); +}; + +#endif // LL_LLJOINTSOLVERRP3_H + diff --git a/indra/llcharacter/lljointstate.h b/indra/llcharacter/lljointstate.h new file mode 100644 index 0000000000..82a2b345b0 --- /dev/null +++ b/indra/llcharacter/lljointstate.h @@ -0,0 +1,106 @@ +/** + * @file lljointstate.h + * @brief Implementation of LLJointState class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLJOINTSTATE_H +#define LL_LLJOINTSTATE_H + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "lljoint.h" + +//----------------------------------------------------------------------------- +// class LLJointState +//----------------------------------------------------------------------------- +class LLJointState +{ +public: + enum BlendPhase + { + INACTIVE, + EASE_IN, + ACTIVE, + EASE_OUT + }; +protected: + // associated joint + LLJoint *mJoint; + + // indicates which members are used + U32 mUsage; + + // indicates weighted effect of this joint + F32 mWeight; + + // transformation members + LLVector3 mPosition; // position relative to parent joint + LLQuaternion mRotation; // joint rotation relative to parent joint + LLVector3 mScale; // scale relative to rotated frame + LLJoint::JointPriority mPriority; // how important this joint state is relative to others +public: + // Constructor + LLJointState() + { + mUsage = 0; + mJoint = NULL; + mUsage = 0; + mWeight = 0.f; + mPriority = LLJoint::USE_MOTION_PRIORITY; + } + + LLJointState(LLJoint* joint) + { + mUsage = 0; + mJoint = joint; + mUsage = 0; + mWeight = 0.f; + mPriority = LLJoint::USE_MOTION_PRIORITY; + } + + // Destructor + virtual ~LLJointState() + { + } + + // joint that this state is applied to + LLJoint *getJoint() { return mJoint; } + BOOL setJoint( LLJoint *joint ) { mJoint = joint; return mJoint != NULL; } + + // transform type (bitwise flags can be combined) + // Note that these are set automatically when various + // member setPos/setRot/setScale functions are called. + enum Usage + { + POS = 1, + ROT = 2, + SCALE = 4, + }; + U32 getUsage() { return mUsage; } + void setUsage( U32 usage ) { mUsage = usage; } + F32 getWeight() { return mWeight; } + void setWeight( F32 weight ) { mWeight = weight; } + + // get/set position + const LLVector3& getPosition() { return mPosition; } + void setPosition( const LLVector3& pos ) { llassert(mUsage & POS); mPosition = pos; } + + // get/set rotation + const LLQuaternion& getRotation() { return mRotation; } + void setRotation( const LLQuaternion& rot ) { llassert(mUsage & ROT); mRotation = rot; } + + // get/set scale + const LLVector3& getScale() { return mScale; } + void setScale( const LLVector3& scale ) { llassert(mUsage & SCALE); mScale = scale; } + + // get/set priority + const LLJoint::JointPriority getPriority() { return mPriority; } + void setPriority( const LLJoint::JointPriority priority ) { mPriority = priority; } +}; + +#endif // LL_LLJOINTSTATE_H + diff --git a/indra/llcharacter/llkeyframefallmotion.cpp b/indra/llcharacter/llkeyframefallmotion.cpp new file mode 100644 index 0000000000..248d22c22c --- /dev/null +++ b/indra/llcharacter/llkeyframefallmotion.cpp @@ -0,0 +1,123 @@ +/** + * @file llkeyframefallmotion.cpp + * @brief Implementation of LLKeyframeFallMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llkeyframefallmotion.h" +#include "llcharacter.h" +#include "m3math.h" + +//----------------------------------------------------------------------------- +// Macros +//----------------------------------------------------------------------------- +#define GO_TO_KEY_POSE 1 +#define MIN_TRACK_SPEED 0.01f + +//----------------------------------------------------------------------------- +// LLKeyframeFallMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLKeyframeFallMotion::LLKeyframeFallMotion(const LLUUID &id) : LLKeyframeMotion(id) +{ + mVelocityZ = 0.f; + mCharacter = NULL; +} + + +//----------------------------------------------------------------------------- +// ~LLKeyframeFallMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLKeyframeFallMotion::~LLKeyframeFallMotion() +{ +} + + +//----------------------------------------------------------------------------- +// LLKeyframeFallMotion::onInitialize() +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLKeyframeFallMotion::onInitialize(LLCharacter *character) +{ + // save character pointer for later use + mCharacter = character; + + // load keyframe data, setup pose and joint states + LLMotion::LLMotionInitStatus result = LLKeyframeMotion::onInitialize(character); + + for (U32 jm=0; jmmNumJointMotions; jm++) + { + if (!mJointStates[jm].getJoint()) + continue; + if (mJointStates[jm].getJoint()->getName() == std::string("mPelvis")) + { + mPelvisStatep = &mJointStates[jm]; + } + } + + return result; +} + +//----------------------------------------------------------------------------- +// LLKeyframeFallMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeFallMotion::onActivate() +{ + LLVector3 ground_pos; + LLVector3 ground_normal; + LLQuaternion inverse_pelvis_rot; + LLVector3 fwd_axis(1.f, 0.f, 0.f); + + mVelocityZ = -mCharacter->getCharacterVelocity().mV[VZ]; + mCharacter->getGround( mCharacter->getCharacterPosition(), ground_pos, ground_normal); + ground_normal.normVec(); + + inverse_pelvis_rot = mCharacter->getCharacterRotation(); + inverse_pelvis_rot.transQuat(); + + // find ground normal in pelvis space + ground_normal = ground_normal * inverse_pelvis_rot; + + // calculate new foward axis + fwd_axis = fwd_axis - (ground_normal * (ground_normal * fwd_axis)); + fwd_axis.normVec(); + mRotationToGroundNormal = LLQuaternion(fwd_axis, ground_normal % fwd_axis, ground_normal); + + return LLKeyframeMotion::onActivate(); +} + +//----------------------------------------------------------------------------- +// LLKeyframeFallMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeFallMotion::onUpdate(F32 activeTime, U8* joint_mask) +{ + BOOL result = LLKeyframeMotion::onUpdate(activeTime, joint_mask); + F32 slerp_amt = clamp_rescale(activeTime / getDuration(), 0.5f, 0.75f, 0.f, 1.f); + + mPelvisStatep->setRotation(mPelvisStatep->getRotation() * slerp(slerp_amt, mRotationToGroundNormal, LLQuaternion())); + + return result; +} + +//----------------------------------------------------------------------------- +// LLKeyframeFallMotion::getEaseInDuration() +//----------------------------------------------------------------------------- +F32 LLKeyframeFallMotion::getEaseInDuration() +{ + if (mVelocityZ == 0.f) + { + // we've already hit the ground + return 0.4f; + } + + return mCharacter->getPreferredPelvisHeight() / mVelocityZ; +} + +// End diff --git a/indra/llcharacter/llkeyframefallmotion.h b/indra/llcharacter/llkeyframefallmotion.h new file mode 100644 index 0000000000..5cc15fd38b --- /dev/null +++ b/indra/llcharacter/llkeyframefallmotion.h @@ -0,0 +1,60 @@ +/** + * @file llkeyframefallmotion.h + * @brief Implementation of LLKeframeWalkMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLKeyframeFallMotion_H +#define LL_LLKeyframeFallMotion_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include "llkeyframemotion.h" +#include "llcharacter.h" + +//----------------------------------------------------------------------------- +// class LLKeyframeFallMotion +//----------------------------------------------------------------------------- +class LLKeyframeFallMotion : + public LLKeyframeMotion +{ +public: + // Constructor + LLKeyframeFallMotion(const LLUUID &id); + + // Destructor + virtual ~LLKeyframeFallMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLKeyframeFallMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + virtual BOOL onActivate(); + virtual F32 getEaseInDuration(); + virtual BOOL onUpdate(F32 activeTime, U8* joint_mask); + +protected: + //------------------------------------------------------------------------- + // Member Data + //------------------------------------------------------------------------- + LLCharacter* mCharacter; + F32 mVelocityZ; + LLJointState* mPelvisStatep; + LLQuaternion mRotationToGroundNormal; +}; + +#endif // LL_LLKeyframeFallMotion_H + diff --git a/indra/llcharacter/llkeyframemotion.cpp b/indra/llcharacter/llkeyframemotion.cpp new file mode 100644 index 0000000000..4dd976d5f1 --- /dev/null +++ b/indra/llcharacter/llkeyframemotion.cpp @@ -0,0 +1,2112 @@ +/** + * @file llkeyframemotion.cpp + * @brief Implementation of LLKeyframeMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llmath.h" +#include "llanimationstates.h" +#include "llassetstorage.h" +#include "lldatapacker.h" +#include "llcharacter.h" +#include "llcriticaldamp.h" +#include "lldir.h" +#include "llendianswizzle.h" +#include "llkeyframemotion.h" +#include "llquantize.h" +#include "llvfile.h" +#include "m3math.h" +#include "message.h" + +//----------------------------------------------------------------------------- +// Static Definitions +//----------------------------------------------------------------------------- +LLVFS* LLKeyframeMotion::sVFS = NULL; +LLKeyframeDataCache::LLKeyframeDataMap LLKeyframeDataCache::sKeyframeDataMap; + +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- +static F32 JOINT_LENGTH_K = 0.7f; +static S32 MAX_ITERATIONS = 20; +static S32 MIN_ITERATIONS = 1; +static S32 MIN_ITERATION_COUNT = 2; +static F32 MAX_PIXEL_AREA_CONSTRAINTS = 80000.f; +static F32 MIN_PIXEL_AREA_CONSTRAINTS = 1000.f; +static F32 MIN_ACCELERATION_SQUARED = 0.0005f * 0.0005f; + +static F32 MAX_CONSTRAINTS = 10; + +//----------------------------------------------------------------------------- +// JointMotionList::dumpDiagInfo() +//----------------------------------------------------------------------------- +U32 LLKeyframeMotion::JointMotionList::dumpDiagInfo() +{ + S32 total_size = sizeof(JointMotionList); + + for (U32 i = 0; i < mNumJointMotions; i++) + { + LLKeyframeMotion::JointMotion* joint_motion_p = &mJointMotionArray[i]; + + llinfos << "\tJoint " << joint_motion_p->mJointName << llendl; + if (joint_motion_p->mUsage & LLJointState::SCALE) + { + llinfos << "\t" << joint_motion_p->mScaleCurve.mNumKeys << " scale keys at " + << joint_motion_p->mScaleCurve.mNumKeys * sizeof(ScaleKey) << " bytes" << llendl; + + total_size += joint_motion_p->mScaleCurve.mNumKeys * sizeof(ScaleKey); + } + if (joint_motion_p->mUsage & LLJointState::ROT) + { + llinfos << "\t" << joint_motion_p->mRotationCurve.mNumKeys << " rotation keys at " + << joint_motion_p->mRotationCurve.mNumKeys * sizeof(RotationKey) << " bytes" << llendl; + + total_size += joint_motion_p->mRotationCurve.mNumKeys * sizeof(RotationKey); + } + if (joint_motion_p->mUsage & LLJointState::POS) + { + llinfos << "\t" << joint_motion_p->mPositionCurve.mNumKeys << " position keys at " + << joint_motion_p->mPositionCurve.mNumKeys * sizeof(PositionKey) << " bytes" << llendl; + + total_size += joint_motion_p->mPositionCurve.mNumKeys * sizeof(PositionKey); + } + } + llinfos << "Size: " << total_size << " bytes" << llendl; + + return total_size; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// ****Curve classes +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// ScaleCurve::ScaleCurve() +//----------------------------------------------------------------------------- +LLKeyframeMotion::ScaleCurve::ScaleCurve() +{ + mInterpolationType = LLKeyframeMotion::IT_LINEAR; + mNumKeys = 0; +} + +//----------------------------------------------------------------------------- +// ScaleCurve::~ScaleCurve() +//----------------------------------------------------------------------------- +LLKeyframeMotion::ScaleCurve::~ScaleCurve() +{ + mKeys.deleteAllData(); + mNumKeys = 0; +} + +//----------------------------------------------------------------------------- +// getValue() +//----------------------------------------------------------------------------- +LLVector3 LLKeyframeMotion::ScaleCurve::getValue(F32 time, F32 duration) +{ + LLVector3 value; + F32 index_before, index_after; + ScaleKey* scale_before; + ScaleKey* scale_after; + + mKeys.getInterval(time, index_before, index_after, scale_before, scale_after); + if (scale_before) + { + if (!scale_after) + { + scale_after = &mLoopInKey; + index_after = duration; + } + + if (index_after == index_before) + { + value = scale_after->mScale; + } + else + { + F32 u = (time - index_before) / (index_after - index_before); + value = interp(u, *scale_before, *scale_after); + } + } + else + { + // before first key + if (scale_after) + { + value = scale_after->mScale; + } + // no keys? + else + { + value.clearVec(); + } + } + + return value; +} + +//----------------------------------------------------------------------------- +// interp() +//----------------------------------------------------------------------------- +LLVector3 LLKeyframeMotion::ScaleCurve::interp(F32 u, ScaleKey& before, ScaleKey& after) +{ + switch (mInterpolationType) + { + case IT_STEP: + return before.mScale; + + default: + case IT_LINEAR: + case IT_SPLINE: + return lerp(before.mScale, after.mScale, u); + } +} + +//----------------------------------------------------------------------------- +// RotationCurve::RotationCurve() +//----------------------------------------------------------------------------- +LLKeyframeMotion::RotationCurve::RotationCurve() +{ + mInterpolationType = LLKeyframeMotion::IT_LINEAR; + mNumKeys = 0; +} + +//----------------------------------------------------------------------------- +// RotationCurve::~RotationCurve() +//----------------------------------------------------------------------------- +LLKeyframeMotion::RotationCurve::~RotationCurve() +{ + mKeys.deleteAllData(); + mNumKeys = 0; +} + +//----------------------------------------------------------------------------- +// RotationCurve::getValue() +//----------------------------------------------------------------------------- +LLQuaternion LLKeyframeMotion::RotationCurve::getValue(F32 time, F32 duration) +{ + LLQuaternion value; + F32 index_before, index_after; + RotationKey* rot_before; + RotationKey* rot_after; + + mKeys.getInterval(time, index_before, index_after, rot_before, rot_after); + + if (rot_before) + { + if (!rot_after) + { + rot_after = &mLoopInKey; + index_after = duration; + } + + if (index_after == index_before) + { + value = rot_after->mRotation; + } + else + { + F32 u = (time - index_before) / (index_after - index_before); + value = interp(u, *rot_before, *rot_after); + } + } + else + { + // before first key + if (rot_after) + { + value = rot_after->mRotation; + } + // no keys? + else + { + value = LLQuaternion::DEFAULT; + } + } + + return value; +} + +//----------------------------------------------------------------------------- +// interp() +//----------------------------------------------------------------------------- +LLQuaternion LLKeyframeMotion::RotationCurve::interp(F32 u, RotationKey& before, RotationKey& after) +{ + switch (mInterpolationType) + { + case IT_STEP: + return before.mRotation; + + default: + case IT_LINEAR: + case IT_SPLINE: + return nlerp(u, before.mRotation, after.mRotation); + } +} + + +//----------------------------------------------------------------------------- +// PositionCurve::PositionCurve() +//----------------------------------------------------------------------------- +LLKeyframeMotion::PositionCurve::PositionCurve() +{ + mInterpolationType = LLKeyframeMotion::IT_LINEAR; + mNumKeys = 0; +} + +//----------------------------------------------------------------------------- +// PositionCurve::~PositionCurve() +//----------------------------------------------------------------------------- +LLKeyframeMotion::PositionCurve::~PositionCurve() +{ + mKeys.deleteAllData(); + mNumKeys = 0; +} + +//----------------------------------------------------------------------------- +// PositionCurve::getValue() +//----------------------------------------------------------------------------- +LLVector3 LLKeyframeMotion::PositionCurve::getValue(F32 time, F32 duration) +{ + LLVector3 value; + F32 index_before, index_after; + PositionKey* pos_before; + PositionKey* pos_after; + + mKeys.getInterval(time, index_before, index_after, pos_before, pos_after); + + if (pos_before) + { + if (!pos_after) + { + pos_after = &mLoopInKey; + index_after = duration; + } + + if (index_after == index_before) + { + value = pos_after->mPosition; + } + else + { + F32 u = (time - index_before) / (index_after - index_before); + value = interp(u, *pos_before, *pos_after); + } + } + else + { + // before first key + if (pos_after) + { + value = pos_after->mPosition; + } + // no keys? + else + { + value.clearVec(); + } + } + + llassert(value.isFinite()); + + return value; +} + +//----------------------------------------------------------------------------- +// interp() +//----------------------------------------------------------------------------- +LLVector3 LLKeyframeMotion::PositionCurve::interp(F32 u, PositionKey& before, PositionKey& after) +{ + switch (mInterpolationType) + { + case IT_STEP: + return before.mPosition; + default: + case IT_LINEAR: + case IT_SPLINE: + return lerp(before.mPosition, after.mPosition, u); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// JointMotion class +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// JointMotion::update() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::JointMotion::update(LLJointState* joint_state, F32 time, F32 duration) +{ + // this value being 0 is the cause of https://jira.lindenlab.com/browse/SL-22678 but I haven't + // managed to get a stack to see how it got here. Testing for 0 here will stop the crash. + if ( joint_state == 0 ) + { + return; + }; + + U32 usage = joint_state->getUsage(); + + //------------------------------------------------------------------------- + // update scale component of joint state + //------------------------------------------------------------------------- + if ((usage & LLJointState::SCALE) && mScaleCurve.mNumKeys) + { + joint_state->setScale( mScaleCurve.getValue( time, duration ) ); + } + + //------------------------------------------------------------------------- + // update rotation component of joint state + //------------------------------------------------------------------------- + if ((usage & LLJointState::ROT) && mRotationCurve.mNumKeys) + { + joint_state->setRotation( mRotationCurve.getValue( time, duration ) ); + } + + //------------------------------------------------------------------------- + // update position component of joint state + //------------------------------------------------------------------------- + if ((usage & LLJointState::POS) && mPositionCurve.mNumKeys) + { + joint_state->setPosition( mPositionCurve.getValue( time, duration ) ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// LLKeyframeMotion class +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// LLKeyframeMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLKeyframeMotion::LLKeyframeMotion( const LLUUID &id) : LLMotion(id) +{ + mJointMotionList = NULL; + mJointStates = NULL; + mLastSkeletonSerialNum = 0; + mLastLoopedTime = 0.f; + mLastUpdateTime = 0.f; + mAssetStatus = ASSET_UNDEFINED; + mPelvisp = NULL; +} + + +//----------------------------------------------------------------------------- +// ~LLKeyframeMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLKeyframeMotion::~LLKeyframeMotion() +{ + if (mJointStates) + { + delete [] mJointStates; + } + mConstraints.deleteAllData(); +} + +//----------------------------------------------------------------------------- +// create() +//----------------------------------------------------------------------------- +LLMotion *LLKeyframeMotion::create(const LLUUID &id) +{ + return new LLKeyframeMotion(id); +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotion::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLKeyframeMotion::onInitialize(LLCharacter *character) +{ + mCharacter = character; + + LLUUID* character_id; + + // asset already loaded? + switch(mAssetStatus) + { + case ASSET_NEEDS_FETCH: + // request asset + mAssetStatus = ASSET_FETCHED; + + character_id = new LLUUID(mCharacter->getID()); + gAssetStorage->getAssetData(mID, + LLAssetType::AT_ANIMATION, + onLoadComplete, + (void *)character_id, + FALSE); + + return STATUS_HOLD; + case ASSET_FETCHED: + return STATUS_HOLD; + case ASSET_FETCH_FAILED: + return STATUS_FAILURE; + case ASSET_LOADED: + return STATUS_SUCCESS; + default: + // we don't know what state the asset is in yet, so keep going + // check keyframe cache first then static vfs then asset request + break; + } + + LLKeyframeMotion::JointMotionList* joint_motion_list = LLKeyframeDataCache::getKeyframeData(getID()); + + if(joint_motion_list) + { + // motion already existed in cache, so grab it + mJointMotionList = joint_motion_list; + + // don't forget to allocate joint states + mJointStates = new LLJointState[mJointMotionList->mNumJointMotions]; + + // set up joint states to point to character joints + for(U32 i = 0; i < mJointMotionList->mNumJointMotions; i++) + { + if (LLJoint *jointp = mCharacter->getJoint(mJointMotionList->mJointMotionArray[i].mJointName)) + { + mJointStates[i].setJoint(jointp); + mJointStates[i].setUsage(mJointMotionList->mJointMotionArray[i].mUsage); + mJointStates[i].setPriority(joint_motion_list->mJointMotionArray[i].mPriority); + } + } + mAssetStatus = ASSET_LOADED; + setupPose(); + return STATUS_SUCCESS; + } + + //------------------------------------------------------------------------- + // Load named file by concatenating the character prefix with the motion name. + // Load data into a buffer to be parsed. + //------------------------------------------------------------------------- + U8 *anim_data; + S32 anim_file_size; + + if (!sVFS) + { + llerrs << "Must call LLKeyframeMotion::setVFS() first before loading a keyframe file!" << llendl; + } + + BOOL success = FALSE; + LLVFile* anim_file = new LLVFile(sVFS, mID, LLAssetType::AT_ANIMATION); + if (!anim_file || !anim_file->getSize()) + { + delete anim_file; + anim_file = NULL; + + // request asset over network on next call to load + mAssetStatus = ASSET_NEEDS_FETCH; + + return STATUS_HOLD; + } + else + { + anim_file_size = anim_file->getSize(); + anim_data = new U8[anim_file_size]; + success = anim_file->read(anim_data, anim_file_size); /*Flawfinder: ignore*/ + delete anim_file; + anim_file = NULL; + } + + if (!success) + { + llwarns << "Can't open animation file " << mID << llendl; + mAssetStatus = ASSET_FETCH_FAILED; + return STATUS_FAILURE; + } + + lldebugs << "Loading keyframe data for: " << getName() << ":" << getID() << " (" << anim_file_size << " bytes)" << llendl; + + LLDataPackerBinaryBuffer dp(anim_data, anim_file_size); + + if (!deserialize(dp)) + { + llwarns << "Failed to decode asset for animation " << getName() << ":" << getID() << llendl; + mAssetStatus = ASSET_FETCH_FAILED; + return STATUS_FAILURE; + } + + delete []anim_data; + + mAssetStatus = ASSET_LOADED; + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// setupPose() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotion::setupPose() +{ + // add all valid joint states to the pose + U32 jm; + for (jm=0; jmmNumJointMotions; jm++) + { + if ( mJointStates[jm].getJoint() ) + { + addJointState( &mJointStates[jm] ); + } + } + + // initialize joint constraints + for (JointConstraintSharedData* shared_constraintp = mJointMotionList->mConstraints.getFirstData(); + shared_constraintp; + shared_constraintp = mJointMotionList->mConstraints.getNextData()) + { + JointConstraint* constraintp = new JointConstraint(shared_constraintp); + initializeConstraint(constraintp); + mConstraints.addData(constraintp); + } + + if (mJointMotionList->mConstraints.getLength()) + { + mPelvisp = mCharacter->getJoint("mPelvis"); + if (!mPelvisp) + { + return FALSE; + } + } + + // setup loop keys + setLoopIn(mJointMotionList->mLoopInPoint); + setLoopOut(mJointMotionList->mLoopOutPoint); + + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotion::onActivate() +{ + // If the keyframe anim has an associated emote, trigger it. + if( mEmoteName.length() > 0 ) + { + mCharacter->startMotion( gAnimLibrary.stringToAnimState(mEmoteName.c_str()) ); + } + + mLastLoopedTime = 0.f; + + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotion::onUpdate(F32 time, U8* joint_mask) +{ + llassert(time >= 0.f); + + if (mJointMotionList->mLoop) + { + if (mJointMotionList->mDuration == 0.0f) + { + time = 0.f; + mLastLoopedTime = 0.0f; + } + else if (mStopped) + { + mLastLoopedTime = llmin(mJointMotionList->mDuration, mLastLoopedTime + time - mLastUpdateTime); + } + else if (time > mJointMotionList->mLoopOutPoint) + { + if ((mJointMotionList->mLoopOutPoint - mJointMotionList->mLoopInPoint) == 0.f) + { + mLastLoopedTime = mJointMotionList->mLoopOutPoint; + } + else + { + mLastLoopedTime = mJointMotionList->mLoopInPoint + + fmod(time - mJointMotionList->mLoopOutPoint, + mJointMotionList->mLoopOutPoint - mJointMotionList->mLoopInPoint); + } + } + else + { + mLastLoopedTime = time; + } + } + else + { + mLastLoopedTime = time; + } + + applyKeyframes(mLastLoopedTime); + + applyConstraints(mLastLoopedTime, joint_mask); + + mLastUpdateTime = time; + + return mLastLoopedTime <= mJointMotionList->mDuration; +} + +//----------------------------------------------------------------------------- +// applyKeyframes() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::applyKeyframes(F32 time) +{ + U32 i; + for (i=0; imNumJointMotions; i++) + { + mJointMotionList->mJointMotionArray[i].update( + &mJointStates[i], + time, + mJointMotionList->mDuration ); + } + + LLJoint::JointPriority* pose_priority = (LLJoint::JointPriority* )mCharacter->getAnimationData("Hand Pose Priority"); + if (pose_priority) + { + if (mJointMotionList->mMaxPriority >= *pose_priority) + { + mCharacter->setAnimationData("Hand Pose", &mJointMotionList->mHandPose); + mCharacter->setAnimationData("Hand Pose Priority", &mJointMotionList->mMaxPriority); + } + } + else + { + mCharacter->setAnimationData("Hand Pose", &mJointMotionList->mHandPose); + mCharacter->setAnimationData("Hand Pose Priority", &mJointMotionList->mMaxPriority); + } +} + +//----------------------------------------------------------------------------- +// applyConstraints() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::applyConstraints(F32 time, U8* joint_mask) +{ + //TODO: investigate replacing spring simulation with critically damped motion + + // re-init constraints if skeleton has changed + if (mCharacter->getSkeletonSerialNum() != mLastSkeletonSerialNum) + { + mLastSkeletonSerialNum = mCharacter->getSkeletonSerialNum(); + for (JointConstraint* constraintp = mConstraints.getFirstData(); + constraintp; + constraintp = mConstraints.getNextData()) + { + initializeConstraint(constraintp); + } + } + + // apply constraints + for (JointConstraint* constraintp = mConstraints.getFirstData(); + constraintp; + constraintp = mConstraints.getNextData()) + { + applyConstraint(constraintp, time, joint_mask); + } +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::onDeactivate() +{ + for (JointConstraint* constraintp = mConstraints.getFirstData(); + constraintp; + constraintp = mConstraints.getNextData()) + { + deactivateConstraint(constraintp); + } +} + +//----------------------------------------------------------------------------- +// setStopTime() +//----------------------------------------------------------------------------- +// time is in seconds since character creation +void LLKeyframeMotion::setStopTime(F32 time) +{ + LLMotion::setStopTime(time); + + if (mJointMotionList->mLoop && mJointMotionList->mLoopOutPoint != mJointMotionList->mDuration) + { + F32 start_loop_time = mActivationTimestamp + mJointMotionList->mLoopInPoint; + F32 loop_fraction_time; + if (mJointMotionList->mLoopOutPoint == mJointMotionList->mLoopInPoint) + { + loop_fraction_time = 0.f; + } + else + { + loop_fraction_time = fmod(time - start_loop_time, + mJointMotionList->mLoopOutPoint - mJointMotionList->mLoopInPoint); + } + mStopTimestamp = llmax(time, + (time - loop_fraction_time) + (mJointMotionList->mDuration - mJointMotionList->mLoopInPoint) - getEaseOutDuration()); + } +} + +//----------------------------------------------------------------------------- +// initializeConstraint() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::initializeConstraint(JointConstraint* constraint) +{ + JointConstraintSharedData *shared_data = constraint->mSharedData; + + S32 joint_num; + LLVector3 source_pos = mCharacter->getVolumePos(shared_data->mSourceConstraintVolume, shared_data->mSourceConstraintOffset); + LLJoint* cur_joint = mJointStates[shared_data->mJointStateIndices[0]].getJoint(); + + F32 source_pos_offset = dist_vec(source_pos, cur_joint->getWorldPosition()); + + constraint->mTotalLength = constraint->mJointLengths[0] = dist_vec(cur_joint->getParent()->getWorldPosition(), source_pos); + + // grab joint lengths + for (joint_num = 1; joint_num < shared_data->mChainLength; joint_num++) + { + cur_joint = mJointStates[shared_data->mJointStateIndices[joint_num]].getJoint(); + if (!cur_joint) + { + return; + } + constraint->mJointLengths[joint_num] = dist_vec(cur_joint->getWorldPosition(), cur_joint->getParent()->getWorldPosition()); + constraint->mTotalLength += constraint->mJointLengths[joint_num]; + } + + // store fraction of total chain length so we know how to shear the entire chain towards the goal position + for (joint_num = 1; joint_num < shared_data->mChainLength; joint_num++) + { + constraint->mJointLengthFractions[joint_num] = constraint->mJointLengths[joint_num] / constraint->mTotalLength; + } + + // add last step in chain, from final joint to constraint position + constraint->mTotalLength += source_pos_offset; + + constraint->mSourceVolume = mCharacter->findCollisionVolume(shared_data->mSourceConstraintVolume); + constraint->mTargetVolume = mCharacter->findCollisionVolume(shared_data->mTargetConstraintVolume); +} + +//----------------------------------------------------------------------------- +// activateConstraint() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::activateConstraint(JointConstraint* constraint) +{ + JointConstraintSharedData *shared_data = constraint->mSharedData; + constraint->mActive = TRUE; + S32 joint_num; + + // grab ground position if we need to + if (shared_data->mConstraintTargetType == TYPE_GROUND) + { + LLVector3 source_pos = mCharacter->getVolumePos(shared_data->mSourceConstraintVolume, shared_data->mSourceConstraintOffset); + LLVector3 ground_pos_agent; + mCharacter->getGround(source_pos, ground_pos_agent, constraint->mGroundNorm); + constraint->mGroundPos = mCharacter->getPosGlobalFromAgent(ground_pos_agent + shared_data->mTargetConstraintOffset); + } + + for (joint_num = 1; joint_num < shared_data->mChainLength; joint_num++) + { + LLJoint* cur_joint = mJointStates[shared_data->mJointStateIndices[joint_num]].getJoint(); + constraint->mPositions[joint_num] = (cur_joint->getWorldPosition() - mPelvisp->getWorldPosition()) * ~mPelvisp->getWorldRotation(); + } + + constraint->mWeight = 1.f; +} + +//----------------------------------------------------------------------------- +// deactivateConstraint() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::deactivateConstraint(JointConstraint *constraintp) +{ + if (constraintp->mSourceVolume) + { + constraintp->mSourceVolume->mUpdateXform = FALSE; + } + + if (!constraintp->mSharedData->mConstraintTargetType == TYPE_GROUND) + { + if (constraintp->mTargetVolume) + { + constraintp->mTargetVolume->mUpdateXform = FALSE; + } + } + constraintp->mActive = FALSE; +} + +//----------------------------------------------------------------------------- +// applyConstraint() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::applyConstraint(JointConstraint* constraint, F32 time, U8* joint_mask) +{ + JointConstraintSharedData *shared_data = constraint->mSharedData; + if (!shared_data) return; + + LLVector3 positions[MAX_CHAIN_LENGTH]; + const F32* joint_lengths = constraint->mJointLengths; + LLVector3 velocities[MAX_CHAIN_LENGTH - 1]; + LLQuaternion old_rots[MAX_CHAIN_LENGTH]; + S32 joint_num; + LLJoint* cur_joint; + + if (time < shared_data->mEaseInStartTime) + { + return; + } + + if (time > shared_data->mEaseOutStopTime) + { + if (constraint->mActive) + { + deactivateConstraint(constraint); + } + return; + } + + if (!constraint->mActive || time < shared_data->mEaseInStopTime) + { + activateConstraint(constraint); + } + + LLJoint* root_joint = mJointStates[shared_data->mJointStateIndices[shared_data->mChainLength]].getJoint(); + LLVector3 root_pos = root_joint->getWorldPosition(); +// LLQuaternion root_rot = + root_joint->getParent()->getWorldRotation(); +// LLQuaternion inv_root_rot = ~root_rot; + +// LLVector3 current_source_pos = mCharacter->getVolumePos(shared_data->mSourceConstraintVolume, shared_data->mSourceConstraintOffset); + + //apply underlying keyframe animation to get nominal "kinematic" joint positions + for (joint_num = 0; joint_num <= shared_data->mChainLength; joint_num++) + { + cur_joint = mJointStates[shared_data->mJointStateIndices[joint_num]].getJoint(); + if (joint_mask[cur_joint->getJointNum()] >= (0xff >> (7 - getPriority()))) + { + // skip constraint + return; + } + old_rots[joint_num] = cur_joint->getRotation(); + cur_joint->setRotation(mJointStates[shared_data->mJointStateIndices[joint_num]].getRotation()); + } + + + LLVector3 keyframe_source_pos = mCharacter->getVolumePos(shared_data->mSourceConstraintVolume, shared_data->mSourceConstraintOffset); + LLVector3 target_pos; + + switch(shared_data->mConstraintTargetType) + { + case TYPE_GROUND: + target_pos = mCharacter->getPosAgentFromGlobal(constraint->mGroundPos); +// llinfos << "Target Pos " << constraint->mGroundPos << " on " << mCharacter->findCollisionVolume(shared_data->mSourceConstraintVolume)->getName() << llendl; + break; + case TYPE_BODY: + target_pos = mCharacter->getVolumePos(shared_data->mTargetConstraintVolume, shared_data->mTargetConstraintOffset); + break; + default: + break; + } + + LLVector3 norm; + LLJoint *source_jointp = NULL; + LLJoint *target_jointp = NULL; + + if (shared_data->mConstraintType == TYPE_PLANE) + { + switch(shared_data->mConstraintTargetType) + { + case TYPE_GROUND: + norm = constraint->mGroundNorm; + break; + case TYPE_BODY: + target_jointp = mCharacter->findCollisionVolume(shared_data->mTargetConstraintVolume); + if (target_jointp) + { + // FIXME: do proper normal calculation for stretched spheres (inverse transpose) + norm = target_pos - target_jointp->getWorldPosition(); + } + + if (norm.isExactlyZero()) + { + source_jointp = mCharacter->findCollisionVolume(shared_data->mSourceConstraintVolume); + norm = -1.f * shared_data->mSourceConstraintOffset; + if (source_jointp) + { + norm = norm * source_jointp->getWorldRotation(); + } + } + norm.normVec(); + break; + default: + norm.clearVec(); + break; + } + + target_pos = keyframe_source_pos + (norm * ((target_pos - keyframe_source_pos) * norm)); + } + + if (constraint->mSharedData->mChainLength != 0 && + dist_vec_squared(root_pos, target_pos) * 0.95f > constraint->mTotalLength * constraint->mTotalLength) + { + constraint->mWeight = lerp(constraint->mWeight, 0.f, LLCriticalDamp::getInterpolant(0.1f)); + } + else + { + constraint->mWeight = lerp(constraint->mWeight, 1.f, LLCriticalDamp::getInterpolant(0.3f)); + } + + F32 weight = constraint->mWeight * ((shared_data->mEaseOutStopTime == 0.f) ? 1.f : + llmin(clamp_rescale(time, shared_data->mEaseInStartTime, shared_data->mEaseInStopTime, 0.f, 1.f), + clamp_rescale(time, shared_data->mEaseOutStartTime, shared_data->mEaseOutStopTime, 1.f, 0.f))); + + LLVector3 source_to_target = target_pos - keyframe_source_pos; + + S32 max_iteration_count = llround(clamp_rescale( + mCharacter->getPixelArea(), + MAX_PIXEL_AREA_CONSTRAINTS, + MIN_PIXEL_AREA_CONSTRAINTS, + (F32)MAX_ITERATIONS, + (F32)MIN_ITERATIONS)); + + if (shared_data->mChainLength) + { + LLQuaternion end_rot = mJointStates[shared_data->mJointStateIndices[0]].getJoint()->getWorldRotation(); + + // slam start and end of chain to the proper positions (rest of chain stays put) + positions[0] = lerp(keyframe_source_pos, target_pos, weight); + positions[shared_data->mChainLength] = root_pos; + + // grab keyframe-specified positions of joints + for (joint_num = 1; joint_num < shared_data->mChainLength; joint_num++) + { + LLVector3 kinematic_position = mJointStates[shared_data->mJointStateIndices[joint_num]].getJoint()->getWorldPosition() + + (source_to_target * constraint->mJointLengthFractions[joint_num]); + + // convert intermediate joint positions to world coordinates + positions[joint_num] = ( constraint->mPositions[joint_num] * mPelvisp->getWorldRotation()) + mPelvisp->getWorldPosition(); + F32 time_constant = 1.f / clamp_rescale(constraint->mFixupDistanceRMS, 0.f, 0.5f, 0.2f, 8.f); +// llinfos << "Interpolant " << LLCriticalDamp::getInterpolant(time_constant, FALSE) << " and fixup distance " << constraint->mFixupDistanceRMS << " on " << mCharacter->findCollisionVolume(shared_data->mSourceConstraintVolume)->getName() << llendl; + positions[joint_num] = lerp(positions[joint_num], kinematic_position, + LLCriticalDamp::getInterpolant(time_constant, FALSE)); + } + + S32 iteration_count; + for (iteration_count = 0; iteration_count < max_iteration_count; iteration_count++) + { + S32 num_joints_finished = 0; + for (joint_num = 1; joint_num < shared_data->mChainLength; joint_num++) + { + // constraint to child + LLVector3 acceleration = (positions[joint_num - 1] - positions[joint_num]) * + (dist_vec(positions[joint_num], positions[joint_num - 1]) - joint_lengths[joint_num - 1]) * JOINT_LENGTH_K; + // constraint to parent + acceleration += (positions[joint_num + 1] - positions[joint_num]) * + (dist_vec(positions[joint_num + 1], positions[joint_num]) - joint_lengths[joint_num]) * JOINT_LENGTH_K; + + if (acceleration.magVecSquared() < MIN_ACCELERATION_SQUARED) + { + num_joints_finished++; + } + + velocities[joint_num - 1] = velocities[joint_num - 1] * 0.7f; + positions[joint_num] += velocities[joint_num - 1] + (acceleration * 0.5f); + velocities[joint_num - 1] += acceleration; + } + + if ((iteration_count >= MIN_ITERATION_COUNT) && + (num_joints_finished == shared_data->mChainLength - 1)) + { +// llinfos << iteration_count << " iterations on " << +// mCharacter->findCollisionVolume(shared_data->mSourceConstraintVolume)->getName() << llendl; + break; + } + } + + for (joint_num = shared_data->mChainLength; joint_num > 0; joint_num--) + { + LLQuaternion parent_rot = mJointStates[shared_data->mJointStateIndices[joint_num]].getJoint()->getParent()->getWorldRotation(); + cur_joint = mJointStates[shared_data->mJointStateIndices[joint_num]].getJoint(); + LLJoint* child_joint = mJointStates[shared_data->mJointStateIndices[joint_num - 1]].getJoint(); + + LLQuaternion cur_rot = cur_joint->getWorldRotation(); + LLQuaternion fixup_rot; + + LLVector3 target_at = positions[joint_num - 1] - positions[joint_num]; + LLVector3 current_at; + + // at bottom of chain, use point on collision volume, not joint position + if (joint_num == 1) + { + current_at = mCharacter->getVolumePos(shared_data->mSourceConstraintVolume, shared_data->mSourceConstraintOffset) - + cur_joint->getWorldPosition(); + } + else + { + current_at = child_joint->getPosition() * cur_rot; + } + fixup_rot.shortestArc(current_at, target_at); + + LLQuaternion target_rot = cur_rot * fixup_rot; + target_rot = target_rot * ~parent_rot; + + if (weight != 1.f) + { + LLQuaternion cur_rot = mJointStates[shared_data->mJointStateIndices[joint_num]].getRotation(); + target_rot = nlerp(weight, cur_rot, target_rot); + } + + mJointStates[shared_data->mJointStateIndices[joint_num]].setRotation(target_rot); + cur_joint->setRotation(target_rot); + } + + LLJoint* end_joint = mJointStates[shared_data->mJointStateIndices[0]].getJoint(); + LLQuaternion end_local_rot = end_rot * ~end_joint->getParent()->getWorldRotation(); + + if (weight == 1.f) + { + mJointStates[shared_data->mJointStateIndices[0]].setRotation(end_local_rot); + } + else + { + LLQuaternion cur_rot = mJointStates[shared_data->mJointStateIndices[0]].getRotation(); + mJointStates[shared_data->mJointStateIndices[0]].setRotation(nlerp(weight, cur_rot, end_local_rot)); + } + + // save simulated positions in pelvis-space and calculate total fixup distance + constraint->mFixupDistanceRMS = 0.f; + F32 delta_time = llmax(0.02f, llabs(time - mLastUpdateTime)); + for (joint_num = 1; joint_num < shared_data->mChainLength; joint_num++) + { + LLVector3 new_pos = (positions[joint_num] - mPelvisp->getWorldPosition()) * ~mPelvisp->getWorldRotation(); + constraint->mFixupDistanceRMS += dist_vec_squared(new_pos, constraint->mPositions[joint_num]) / delta_time; + constraint->mPositions[joint_num] = new_pos; + } + constraint->mFixupDistanceRMS *= 1.f / (constraint->mTotalLength * (F32)(shared_data->mChainLength - 1)); + constraint->mFixupDistanceRMS = fsqrtf(constraint->mFixupDistanceRMS); + + //reset old joint rots + for (joint_num = 0; joint_num <= shared_data->mChainLength; joint_num++) + { + mJointStates[shared_data->mJointStateIndices[joint_num]].getJoint()->setRotation(old_rots[joint_num]); + } + } + // simple positional constraint (pelvis only) + else if (mJointStates[shared_data->mJointStateIndices[0]].getUsage() & LLJointState::POS) + { + LLVector3 delta = source_to_target * weight; + LLJointState* current_joint_statep = &mJointStates[shared_data->mJointStateIndices[0]]; + LLQuaternion parent_rot = current_joint_statep->getJoint()->getParent()->getWorldRotation(); + delta = delta * ~parent_rot; + current_joint_statep->setPosition(current_joint_statep->getJoint()->getPosition() + delta); + } +} + +//----------------------------------------------------------------------------- +// deserialize() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotion::deserialize(LLDataPacker& dp) +{ + BOOL old_version = FALSE; + mJointMotionList = new LLKeyframeMotion::JointMotionList; + mJointMotionList->mNumJointMotions = 0; + + //------------------------------------------------------------------------- + // get base priority + //------------------------------------------------------------------------- + S32 temp_priority; + U16 version; + U16 sub_version; + + if (!dp.unpackU16(version, "version")) + { + llwarns << "can't read version number" << llendl; + return FALSE; + } + + if (!dp.unpackU16(sub_version, "sub_version")) + { + llwarns << "can't read sub version number" << llendl; + return FALSE; + } + + if (version == 0 && sub_version == 1) + { + old_version = TRUE; + } + else if (version != KEYFRAME_MOTION_VERSION || sub_version != KEYFRAME_MOTION_SUBVERSION) + { +#if LL_RELEASE + llwarns << "Bad animation version " << version << "." << sub_version << llendl; + return FALSE; +#else + llerrs << "Bad animation version " << version << "." << sub_version << llendl; +#endif + } + + if (!dp.unpackS32(temp_priority, "base_priority")) + { + llwarns << "can't read priority" << llendl; + return FALSE; + } + mJointMotionList->mBasePriority = (LLJoint::JointPriority) temp_priority; + + if (mJointMotionList->mBasePriority >= LLJoint::ADDITIVE_PRIORITY) + { + mJointMotionList->mBasePriority = (LLJoint::JointPriority)((int)LLJoint::ADDITIVE_PRIORITY-1); + mJointMotionList->mMaxPriority = mJointMotionList->mBasePriority; + } + + //------------------------------------------------------------------------- + // get duration + //------------------------------------------------------------------------- + if (!dp.unpackF32(mJointMotionList->mDuration, "duration")) + { + llwarns << "can't read duration" << llendl; + return FALSE; + } + + //------------------------------------------------------------------------- + // get emote (optional) + //------------------------------------------------------------------------- + char read_string[128]; + if (!dp.unpackString(read_string, "emote_name")) + { + llwarns << "can't read optional_emote_animation" << llendl; + return FALSE; + } + + mEmoteName.assign( read_string ); + + //------------------------------------------------------------------------- + // get loop + //------------------------------------------------------------------------- + if (!dp.unpackF32(mJointMotionList->mLoopInPoint, "loop_in_point")) + { + llwarns << "can't read loop point" << llendl; + return FALSE; + } + + if (!dp.unpackF32(mJointMotionList->mLoopOutPoint, "loop_out_point")) + { + llwarns << "can't read loop point" << llendl; + return FALSE; + } + + if (!dp.unpackS32(mJointMotionList->mLoop, "loop")) + { + llwarns << "can't read loop" << llendl; + return FALSE; + } + + //------------------------------------------------------------------------- + // get easeIn and easeOut + //------------------------------------------------------------------------- + if (!dp.unpackF32(mJointMotionList->mEaseInDuration, "ease_in_duration")) + { + llwarns << "can't read easeIn" << llendl; + return FALSE; + } + + if (!dp.unpackF32(mJointMotionList->mEaseOutDuration, "ease_out_duration")) + { + llwarns << "can't read easeOut" << llendl; + return FALSE; + } + + //------------------------------------------------------------------------- + // get hand pose + //------------------------------------------------------------------------- + U32 word; + if (!dp.unpackU32(word, "hand_pose")) + { + llwarns << "can't read hand pose" << llendl; + return FALSE; + } + mJointMotionList->mHandPose = (LLHandMotion::eHandPose)word; + + //------------------------------------------------------------------------- + // get number of joint motions + //------------------------------------------------------------------------- + if (!dp.unpackU32(mJointMotionList->mNumJointMotions, "num_joints")) + { + llwarns << "can't read number of joints" << llendl; + return FALSE; + } + + if (mJointMotionList->mNumJointMotions == 0) + { + llwarns << "no joints in animation" << llendl; + return FALSE; + } + else if (mJointMotionList->mNumJointMotions > LL_CHARACTER_MAX_JOINTS) + { + llwarns << "too many joints in animation" << llendl; + return FALSE; + } + + mJointMotionList->mJointMotionArray = new JointMotion[mJointMotionList->mNumJointMotions]; + mJointStates = new LLJointState[mJointMotionList->mNumJointMotions]; + + if (!mJointMotionList->mJointMotionArray) + { + mJointMotionList->mDuration = 0.0f; + mJointMotionList->mEaseInDuration = 0.0f; + mJointMotionList->mEaseOutDuration = 0.0f; + return FALSE; + } + + //------------------------------------------------------------------------- + // initialize joint motions + //------------------------------------------------------------------------- + S32 k; + for(U32 i=0; imNumJointMotions; ++i) + { + if (!dp.unpackString(read_string, "joint_name")) + { + llwarns << "can't read joint name" << llendl; + return FALSE; + } + + //--------------------------------------------------------------------- + // find the corresponding joint + //--------------------------------------------------------------------- + LLJoint *joint = mCharacter->getJoint( read_string ); + if (joint) + { +// llinfos << " joint: " << read_string << llendl; + } + else + { + llwarns << "joint not found: " << read_string << llendl; + //return FALSE; + } + + mJointMotionList->mJointMotionArray[i].mJointName = read_string; + mJointStates[i].setJoint( joint ); + mJointStates[i].setUsage( 0 ); + + //--------------------------------------------------------------------- + // get joint priority + //--------------------------------------------------------------------- + S32 joint_priority; + if (!dp.unpackS32(joint_priority, "joint_priority")) + { + llwarns << "can't read joint priority." << llendl; + return FALSE; + } + + mJointMotionList->mJointMotionArray[i].mPriority = (LLJoint::JointPriority)joint_priority; + if (joint_priority != LLJoint::USE_MOTION_PRIORITY && + joint_priority > mJointMotionList->mMaxPriority) + { + mJointMotionList->mMaxPriority = (LLJoint::JointPriority)joint_priority; + } + + mJointStates[i].setPriority((LLJoint::JointPriority)joint_priority); + + //--------------------------------------------------------------------- + // scan rotation curve header + //--------------------------------------------------------------------- + if (!dp.unpackS32(mJointMotionList->mJointMotionArray[i].mRotationCurve.mNumKeys, "num_rot_keys")) + { + llwarns << "can't read number of rotation keys" << llendl; + return FALSE; + } + + mJointMotionList->mJointMotionArray[i].mRotationCurve.mInterpolationType = IT_LINEAR; + if (mJointMotionList->mJointMotionArray[i].mRotationCurve.mNumKeys != 0) + { + mJointStates[i].setUsage(mJointStates[i].getUsage() | LLJointState::ROT ); + } + + //--------------------------------------------------------------------- + // scan rotation curve keys + //--------------------------------------------------------------------- + RotationCurve *rCurve = &mJointMotionList->mJointMotionArray[i].mRotationCurve; + + for (k = 0; k < mJointMotionList->mJointMotionArray[i].mRotationCurve.mNumKeys; k++) + { + F32 time; + U16 time_short; + + if (old_version) + { + if (!dp.unpackF32(time, "time")) + { + llwarns << "can't read rotation key (" << k << ")" << llendl; + return FALSE; + } + + } + else + { + if (!dp.unpackU16(time_short, "time")) + { + llwarns << "can't read rotation key (" << k << ")" << llendl; + return FALSE; + } + + time = U16_to_F32(time_short, 0.f, mJointMotionList->mDuration); + } + + RotationKey *rot_key = new RotationKey; + rot_key->mTime = time; + LLVector3 rot_angles; + U16 x, y, z; + + BOOL success = TRUE; + + if (old_version) + { + success = dp.unpackVector3(rot_angles, "rot_angles"); + + LLQuaternion::Order ro = StringToOrder("ZYX"); + rot_key->mRotation = mayaQ(rot_angles.mV[VX], rot_angles.mV[VY], rot_angles.mV[VZ], ro); + } + else + { + success &= dp.unpackU16(x, "rot_angle_x"); + success &= dp.unpackU16(y, "rot_angle_y"); + success &= dp.unpackU16(z, "rot_angle_z"); + + LLVector3 rot_vec; + rot_vec.mV[VX] = U16_to_F32(x, -1.f, 1.f); + rot_vec.mV[VY] = U16_to_F32(y, -1.f, 1.f); + rot_vec.mV[VZ] = U16_to_F32(z, -1.f, 1.f); + rot_key->mRotation.unpackFromVector3(rot_vec); + } + + if (!success) + { + llwarns << "can't read rotation key (" << k << ")" << llendl; + return FALSE; + } + + rCurve->mKeys[time] = rot_key; + } + + //--------------------------------------------------------------------- + // scan position curve header + //--------------------------------------------------------------------- + if (!dp.unpackS32(mJointMotionList->mJointMotionArray[i].mPositionCurve.mNumKeys, "num_pos_keys")) + { + llwarns << "can't read number of position keys" << llendl; + return FALSE; + } + + mJointMotionList->mJointMotionArray[i].mPositionCurve.mInterpolationType = IT_LINEAR; + if (mJointMotionList->mJointMotionArray[i].mPositionCurve.mNumKeys != 0) + { + mJointStates[i].setUsage(mJointStates[i].getUsage() | LLJointState::POS ); + } + + //--------------------------------------------------------------------- + // scan position curve keys + //--------------------------------------------------------------------- + PositionCurve *pCurve = &mJointMotionList->mJointMotionArray[i].mPositionCurve; + BOOL is_pelvis = mJointMotionList->mJointMotionArray[i].mJointName == "mPelvis"; + for (k = 0; k < mJointMotionList->mJointMotionArray[i].mPositionCurve.mNumKeys; k++) + { + U16 time_short; + PositionKey* pos_key = new PositionKey; + + if (old_version) + { + if (!dp.unpackF32(pos_key->mTime, "time")) + { + llwarns << "can't read position key (" << k << ")" << llendl; + delete pos_key; + return FALSE; + } + } + else + { + if (!dp.unpackU16(time_short, "time")) + { + llwarns << "can't read position key (" << k << ")" << llendl; + delete pos_key; + return FALSE; + } + + pos_key->mTime = U16_to_F32(time_short, 0.f, mJointMotionList->mDuration); + } + + BOOL success = TRUE; + + if (old_version) + { + success = dp.unpackVector3(pos_key->mPosition, "pos"); + } + else + { + U16 x, y, z; + + success &= dp.unpackU16(x, "pos_x"); + success &= dp.unpackU16(y, "pos_y"); + success &= dp.unpackU16(z, "pos_z"); + + pos_key->mPosition.mV[VX] = U16_to_F32(x, -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + pos_key->mPosition.mV[VY] = U16_to_F32(y, -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + pos_key->mPosition.mV[VZ] = U16_to_F32(z, -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + } + + if (!success) + { + llwarns << "can't read position key (" << k << ")" << llendl; + delete pos_key; + return FALSE; + } + + pCurve->mKeys[pos_key->mTime] = pos_key; + + if (is_pelvis) + { + mJointMotionList->mPelvisBBox.addPoint(pos_key->mPosition); + } + } + + mJointMotionList->mJointMotionArray[i].mUsage = mJointStates[i].getUsage(); + } + + //------------------------------------------------------------------------- + // get number of constraints + //------------------------------------------------------------------------- + S32 num_constraints = 0; + if (!dp.unpackS32(num_constraints, "num_constraints")) + { + llwarns << "can't read number of constraints" << llendl; + return FALSE; + } + + if (num_constraints > MAX_CONSTRAINTS) + { + llwarns << "Too many constraints...ignoring" << llendl; + } + else + { + //------------------------------------------------------------------------- + // get constraints + //------------------------------------------------------------------------- + std::string str; + for(S32 i = 0; i < num_constraints; ++i) + { + // read in constraint data + JointConstraintSharedData* constraintp = new JointConstraintSharedData; + U8 byte = 0; + + if (!dp.unpackU8(byte, "chain_length")) + { + llwarns << "can't read constraint chain length" << llendl; + return FALSE; + } + constraintp->mChainLength = (S32) byte; + + if (!dp.unpackU8(byte, "constraint_type")) + { + llwarns << "can't read constraint type" << llendl; + return FALSE; + } + constraintp->mConstraintType = (EConstraintType)byte; + + if (!dp.unpackBinaryDataFixed((unsigned char*)read_string, 16, "source_volume")) + { + llwarns << "can't read source volume name" << llendl; + return FALSE; + } + + str.assign(read_string); + constraintp->mSourceConstraintVolume = mCharacter->getCollisionVolumeID(str); + + if (!dp.unpackVector3(constraintp->mSourceConstraintOffset, "source_offset")) + { + llwarns << "can't read constraint source offset" << llendl; + return FALSE; + } + + if (!dp.unpackBinaryDataFixed((unsigned char*)read_string, 16, "target_volume")) + { + llwarns << "can't read target volume name" << llendl; + return FALSE; + } + + str.assign(read_string); + if (str == "GROUND") + { + // constrain to ground + constraintp->mConstraintTargetType = TYPE_GROUND; + } + else + { + constraintp->mConstraintTargetType = TYPE_BODY; + constraintp->mTargetConstraintVolume = mCharacter->getCollisionVolumeID(str); + } + + if (!dp.unpackVector3(constraintp->mTargetConstraintOffset, "target_offset")) + { + llwarns << "can't read constraint target offset" << llendl; + return FALSE; + } + + if (!dp.unpackVector3(constraintp->mTargetConstraintDir, "target_dir")) + { + llwarns << "can't read constraint target direction" << llendl; + return FALSE; + } + + if (!constraintp->mTargetConstraintDir.isExactlyZero()) + { + constraintp->mUseTargetOffset = TRUE; + // constraintp->mTargetConstraintDir *= constraintp->mSourceConstraintOffset.magVec(); + } + + if (!dp.unpackF32(constraintp->mEaseInStartTime, "ease_in_start")) + { + llwarns << "can't read constraint ease in start time" << llendl; + return FALSE; + } + + if (!dp.unpackF32(constraintp->mEaseInStopTime, "ease_in_stop")) + { + llwarns << "can't read constraint ease in stop time" << llendl; + return FALSE; + } + + if (!dp.unpackF32(constraintp->mEaseOutStartTime, "ease_out_start")) + { + llwarns << "can't read constraint ease out start time" << llendl; + return FALSE; + } + + if (!dp.unpackF32(constraintp->mEaseOutStopTime, "ease_out_stop")) + { + llwarns << "can't read constraint ease out stop time" << llendl; + return FALSE; + } + + mJointMotionList->mConstraints.addData(constraintp); + + constraintp->mJointStateIndices = new S32[constraintp->mChainLength + 1]; + + LLJoint* joint = mCharacter->findCollisionVolume(constraintp->mSourceConstraintVolume); + if (!joint) + { + return FALSE; + } + + // get joint to which this collision volume is attached + joint = joint->getParent(); + + for (S32 i = 0; i < constraintp->mChainLength + 1; i++) + { + constraintp->mJointStateIndices[i] = -1; + for (U32 j = 0; j < mJointMotionList->mNumJointMotions; j++) + { + if(mJointStates[j].getJoint() == joint) + { + constraintp->mJointStateIndices[i] = (S32)j; + break; + } + } + joint = joint->getParent(); + } + + } + } + + // FIXME: support cleanup of old keyframe data + LLKeyframeDataCache::addKeyframeData(getID(), mJointMotionList); + mAssetStatus = ASSET_LOADED; + + setupPose(); + + return TRUE; +} + +//----------------------------------------------------------------------------- +// serialize() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotion::serialize(LLDataPacker& dp) const +{ + BOOL success = TRUE; + + success &= dp.packU16(KEYFRAME_MOTION_VERSION, "version"); + success &= dp.packU16(KEYFRAME_MOTION_SUBVERSION, "sub_version"); + success &= dp.packS32(mJointMotionList->mBasePriority, "base_priority"); + success &= dp.packF32(mJointMotionList->mDuration, "duration"); + success &= dp.packString(mEmoteName.c_str(), "emote_name"); + success &= dp.packF32(mJointMotionList->mLoopInPoint, "loop_in_point"); + success &= dp.packF32(mJointMotionList->mLoopOutPoint, "loop_out_point"); + success &= dp.packS32(mJointMotionList->mLoop, "loop"); + success &= dp.packF32(mJointMotionList->mEaseInDuration, "ease_in_duration"); + success &= dp.packF32(mJointMotionList->mEaseOutDuration, "ease_out_duration"); + success &= dp.packU32(mJointMotionList->mHandPose, "hand_pose"); + success &= dp.packU32(mJointMotionList->mNumJointMotions, "num_joints"); + + for (U32 i = 0; i < mJointMotionList->mNumJointMotions; i++) + { + JointMotion* joint_motionp = &mJointMotionList->mJointMotionArray[i]; + success &= dp.packString(joint_motionp->mJointName.c_str(), "joint_name"); + success &= dp.packS32(joint_motionp->mPriority, "joint_priority"); + success &= dp.packS32(joint_motionp->mRotationCurve.mNumKeys, "num_rot_keys"); + + for (RotationKey* rot_keyp = joint_motionp->mRotationCurve.mKeys.getFirstData(); + rot_keyp; + rot_keyp = joint_motionp->mRotationCurve.mKeys.getNextData()) + { + U16 time_short = F32_to_U16(rot_keyp->mTime, 0.f, mJointMotionList->mDuration); + success &= dp.packU16(time_short, "time"); + + LLVector3 rot_angles = rot_keyp->mRotation.packToVector3(); + + U16 x, y, z; + rot_angles.quantize16(-1.f, 1.f, -1.f, 1.f); + x = F32_to_U16(rot_angles.mV[VX], -1.f, 1.f); + y = F32_to_U16(rot_angles.mV[VY], -1.f, 1.f); + z = F32_to_U16(rot_angles.mV[VZ], -1.f, 1.f); + success &= dp.packU16(x, "rot_angle_x"); + success &= dp.packU16(y, "rot_angle_y"); + success &= dp.packU16(z, "rot_angle_z"); + } + + success &= dp.packS32(joint_motionp->mPositionCurve.mNumKeys, "num_pos_keys"); + for (PositionKey* pos_keyp = joint_motionp->mPositionCurve.mKeys.getFirstData(); + pos_keyp; + pos_keyp = joint_motionp->mPositionCurve.mKeys.getNextData()) + { + U16 time_short = F32_to_U16(pos_keyp->mTime, 0.f, mJointMotionList->mDuration); + success &= dp.packU16(time_short, "time"); + + U16 x, y, z; + pos_keyp->mPosition.quantize16(-LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET, -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + x = F32_to_U16(pos_keyp->mPosition.mV[VX], -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + y = F32_to_U16(pos_keyp->mPosition.mV[VY], -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + z = F32_to_U16(pos_keyp->mPosition.mV[VZ], -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + success &= dp.packU16(x, "pos_x"); + success &= dp.packU16(y, "pos_y"); + success &= dp.packU16(z, "pos_z"); + } + } + + success &= dp.packS32(mJointMotionList->mConstraints.getLength(), "num_constraints"); + for (JointConstraintSharedData* shared_constraintp = mJointMotionList->mConstraints.getFirstData(); + shared_constraintp; + shared_constraintp = mJointMotionList->mConstraints.getNextData()) + { + success &= dp.packU8(shared_constraintp->mChainLength, "chain_length"); + success &= dp.packU8(shared_constraintp->mConstraintType, "constraint_type"); + char volume_name[16]; + snprintf(volume_name, sizeof(volume_name), "%s", + mCharacter->findCollisionVolume(shared_constraintp->mSourceConstraintVolume)->getName().c_str()); /* Flawfinder: ignore */ + success &= dp.packBinaryDataFixed((U8*)volume_name, 16, "source_volume"); + success &= dp.packVector3(shared_constraintp->mSourceConstraintOffset, "source_offset"); + if (shared_constraintp->mConstraintTargetType == TYPE_GROUND) + { + snprintf(volume_name,sizeof(volume_name), "%s", "GROUND"); /* Flawfinder: ignore */ + } + else + { + snprintf(volume_name, sizeof(volume_name),"%s", + mCharacter->findCollisionVolume(shared_constraintp->mTargetConstraintVolume)->getName().c_str()); /* Flawfinder: ignore */ + } + success &= dp.packBinaryDataFixed((U8*)volume_name, 16, "target_volume"); + success &= dp.packVector3(shared_constraintp->mTargetConstraintOffset, "target_offset"); + success &= dp.packVector3(shared_constraintp->mTargetConstraintDir, "target_dir"); + success &= dp.packF32(shared_constraintp->mEaseInStartTime, "ease_in_start"); + success &= dp.packF32(shared_constraintp->mEaseInStopTime, "ease_in_stop"); + success &= dp.packF32(shared_constraintp->mEaseOutStartTime, "ease_out_start"); + success &= dp.packF32(shared_constraintp->mEaseOutStopTime, "ease_out_stop"); + } + + return success; +} + +//----------------------------------------------------------------------------- +// getFileSize() +//----------------------------------------------------------------------------- +U32 LLKeyframeMotion::getFileSize() +{ + // serialize into a dummy buffer to calculate required size + LLDataPackerBinaryBuffer dp; + serialize(dp); + + return dp.getCurrentSize(); +} + +//----------------------------------------------------------------------------- +// getPelvisBBox() +//----------------------------------------------------------------------------- +const LLBBoxLocal &LLKeyframeMotion::getPelvisBBox() +{ + return mJointMotionList->mPelvisBBox; +} + +//----------------------------------------------------------------------------- +// setPriority() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::setPriority(S32 priority) +{ + if (mJointMotionList) + { + S32 priority_delta = priority - mJointMotionList->mBasePriority; + mJointMotionList->mBasePriority = (LLJoint::JointPriority)priority; + mJointMotionList->mMaxPriority = mJointMotionList->mBasePriority; + + for (U32 i = 0; i < mJointMotionList->mNumJointMotions; i++) + { + mJointMotionList->mJointMotionArray[i].mPriority = (LLJoint::JointPriority)llclamp( + (S32)mJointMotionList->mJointMotionArray[i].mPriority + priority_delta, + (S32)LLJoint::LOW_PRIORITY, + (S32)LLJoint::HIGHEST_PRIORITY); + mJointStates[i].setPriority(mJointMotionList->mJointMotionArray[i].mPriority); + } + } +} + +//----------------------------------------------------------------------------- +// setEmote() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::setEmote(const LLUUID& emote_id) +{ + const char* emote_name = gAnimLibrary.animStateToString(emote_id); + if (emote_name) + { + mEmoteName = emote_name; + } + else + { + mEmoteName = ""; + } +} + +//----------------------------------------------------------------------------- +// setEaseIn() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::setEaseIn(F32 ease_in) +{ + if (mJointMotionList) + { + mJointMotionList->mEaseInDuration = llmax(ease_in, 0.f); + } +} + +//----------------------------------------------------------------------------- +// setEaseOut() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::setEaseOut(F32 ease_in) +{ + if (mJointMotionList) + { + mJointMotionList->mEaseOutDuration = llmax(ease_in, 0.f); + } +} + + +//----------------------------------------------------------------------------- +// flushKeyframeCache() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::flushKeyframeCache() +{ + LLKeyframeDataCache::clear(); +} + +//----------------------------------------------------------------------------- +// setLoop() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::setLoop(BOOL loop) +{ + if (mJointMotionList) + { + mJointMotionList->mLoop = loop; + mSendStopTimestamp = F32_MAX; + } +} + + +//----------------------------------------------------------------------------- +// setLoopIn() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::setLoopIn(F32 in_point) +{ + if (mJointMotionList) + { + mJointMotionList->mLoopInPoint = in_point; + + // set up loop keys + for (U32 i = 0; i < mJointMotionList->mNumJointMotions; i++) + { + PositionCurve* pos_curve = &mJointMotionList->mJointMotionArray[i].mPositionCurve; + RotationCurve* rot_curve = &mJointMotionList->mJointMotionArray[i].mRotationCurve; + ScaleCurve* scale_curve = &mJointMotionList->mJointMotionArray[i].mScaleCurve; + + pos_curve->mLoopInKey.mTime = mJointMotionList->mLoopInPoint; + rot_curve->mLoopInKey.mTime = mJointMotionList->mLoopInPoint; + scale_curve->mLoopInKey.mTime = mJointMotionList->mLoopInPoint; + + pos_curve->mLoopInKey.mPosition = pos_curve->getValue(mJointMotionList->mLoopInPoint, mJointMotionList->mDuration); + rot_curve->mLoopInKey.mRotation = rot_curve->getValue(mJointMotionList->mLoopInPoint, mJointMotionList->mDuration); + scale_curve->mLoopInKey.mScale = scale_curve->getValue(mJointMotionList->mLoopInPoint, mJointMotionList->mDuration); + } + } +} + +//----------------------------------------------------------------------------- +// setLoopOut() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::setLoopOut(F32 out_point) +{ + if (mJointMotionList) + { + mJointMotionList->mLoopOutPoint = out_point; + + // set up loop keys + for (U32 i = 0; i < mJointMotionList->mNumJointMotions; i++) + { + PositionCurve* pos_curve = &mJointMotionList->mJointMotionArray[i].mPositionCurve; + RotationCurve* rot_curve = &mJointMotionList->mJointMotionArray[i].mRotationCurve; + ScaleCurve* scale_curve = &mJointMotionList->mJointMotionArray[i].mScaleCurve; + + pos_curve->mLoopOutKey.mTime = mJointMotionList->mLoopOutPoint; + rot_curve->mLoopOutKey.mTime = mJointMotionList->mLoopOutPoint; + scale_curve->mLoopOutKey.mTime = mJointMotionList->mLoopOutPoint; + + pos_curve->mLoopOutKey.mPosition = pos_curve->getValue(mJointMotionList->mLoopOutPoint, mJointMotionList->mDuration); + rot_curve->mLoopOutKey.mRotation = rot_curve->getValue(mJointMotionList->mLoopOutPoint, mJointMotionList->mDuration); + scale_curve->mLoopOutKey.mScale = scale_curve->getValue(mJointMotionList->mLoopOutPoint, mJointMotionList->mDuration); + } + } +} + +//----------------------------------------------------------------------------- +// onLoadComplete() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::onLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status) +{ + LLUUID* id = (LLUUID*)user_data; + + LLCharacter* character = NULL; + + for(character = LLCharacter::sInstances.getFirstData(); + character; + character = LLCharacter::sInstances.getNextData()) + { + if (character->getID() == *id) + { + break; + } + } + + delete id; + + if (!character) + { + return; + } + + // create an instance of this motion (it may or may not already exist) + LLKeyframeMotion* motionp = (LLKeyframeMotion*)character->createMotion(asset_uuid); + + if (0 == status && motionp) + { + if (motionp->mAssetStatus == ASSET_LOADED) + { + // asset already loaded + return; + } + LLVFile file(vfs, asset_uuid, type, LLVFile::READ); + S32 size = file.getSize(); + + U8* buffer = new U8[size]; + file.read((U8*)buffer, size); /*Flawfinder: ignore*/ + + lldebugs << "Loading keyframe data for: " << motionp->getName() << ":" << motionp->getID() << " (" << size << " bytes)" << llendl; + + LLDataPackerBinaryBuffer dp(buffer, size); + if (motionp->deserialize(dp)) + { + motionp->mAssetStatus = ASSET_LOADED; + } + else + { + llwarns << "Failed to decode asset for animation " << motionp->getName() << ":" << motionp->getID() << llendl; + motionp->mAssetStatus = ASSET_FETCH_FAILED; + } + + delete []buffer; + + } + else + { + llwarns << "Failed to load asset for animation " << motionp->getName() << ":" << motionp->getID() << llendl; + motionp->mAssetStatus = ASSET_FETCH_FAILED; + } +} + + +//----------------------------------------------------------------------------- +// writeCAL3D() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::writeCAL3D(apr_file_t* fp) +{ +// +// +// +// 0 0 48.8332 +// 0.0512905 0.05657 0.66973 0.738668 +// +// +// + + apr_file_printf(fp, "\n", getDuration(), mJointMotionList->mNumJointMotions); + for (U32 joint_index = 0; joint_index < mJointMotionList->mNumJointMotions; joint_index++) + { + JointMotion* joint_motionp = &mJointMotionList->mJointMotionArray[joint_index]; + LLJoint* animated_joint = mCharacter->getJoint(joint_motionp->mJointName); + S32 joint_num = animated_joint->mJointNum + 1; + + apr_file_printf(fp, " \n", joint_num, joint_motionp->mRotationCurve.mNumKeys ); + PositionKey* pos_keyp = joint_motionp->mPositionCurve.mKeys.getFirstData(); + for (RotationKey* rot_keyp = joint_motionp->mRotationCurve.mKeys.getFirstData(); + rot_keyp; + rot_keyp = joint_motionp->mRotationCurve.mKeys.getNextData()) + { + apr_file_printf(fp, " \n", rot_keyp->mTime); + LLVector3 nominal_pos = animated_joint->getPosition(); + if (animated_joint->getParent()) + { + nominal_pos.scaleVec(animated_joint->getParent()->getScale()); + } + nominal_pos = nominal_pos * 100.f; + + if (joint_motionp->mUsage & LLJointState::POS && pos_keyp) + { + LLVector3 pos_val = pos_keyp->mPosition; + pos_val = pos_val * 100.f; + pos_val += nominal_pos; + apr_file_printf(fp, " %0.4f %0.4f %0.4f\n", pos_val.mV[VX], pos_val.mV[VY], pos_val.mV[VZ]); + pos_keyp = joint_motionp->mPositionCurve.mKeys.getNextData(); + } + else + { + apr_file_printf(fp, " %0.4f %0.4f %0.4f\n", nominal_pos.mV[VX], nominal_pos.mV[VY], nominal_pos.mV[VZ]); + } + + LLQuaternion rot_val = ~rot_keyp->mRotation; + apr_file_printf(fp, " %0.4f %0.4f %0.4f %0.4f\n", rot_val.mQ[VX], rot_val.mQ[VY], rot_val.mQ[VZ], rot_val.mQ[VW]); + apr_file_printf(fp, " \n"); + } + apr_file_printf(fp, " \n"); + } + apr_file_printf(fp, "\n"); +} + +//-------------------------------------------------------------------- +// LLKeyframeDataCache::dumpDiagInfo() +//-------------------------------------------------------------------- +void LLKeyframeDataCache::dumpDiagInfo() +{ + // keep track of totals + U32 total_size = 0; + + char buf[1024]; /* Flawfinder: ignore */ + + llinfos << "-----------------------------------------------------" << llendl; + llinfos << " Global Motion Table (DEBUG only)" << llendl; + llinfos << "-----------------------------------------------------" << llendl; + + // print each loaded mesh, and it's memory usage + LLKeyframeDataMap::iterator map_it; + for (map_it = sKeyframeDataMap.begin(); map_it != sKeyframeDataMap.end(); ++map_it) + { + U32 joint_motion_kb; + + LLKeyframeMotion::JointMotionList *motion_list_p = map_it->second; + + llinfos << "Motion: " << map_it->first << llendl; + + joint_motion_kb = motion_list_p->dumpDiagInfo(); + + total_size += joint_motion_kb; + } + + llinfos << "-----------------------------------------------------" << llendl; + llinfos << "Motions\tTotal Size" << llendl; + snprintf(buf, sizeof(buf), "%d\t\t%d bytes", (S32)sKeyframeDataMap.size(), total_size ); /* Flawfinder: ignore */ + llinfos << buf << llendl; + llinfos << "-----------------------------------------------------" << llendl; +} + + +//-------------------------------------------------------------------- +// LLKeyframeDataCache::addKeyframeData() +//-------------------------------------------------------------------- +void LLKeyframeDataCache::addKeyframeData(const LLUUID& id, LLKeyframeMotion::JointMotionList* joint_motion_listp) +{ + sKeyframeDataMap[id] = joint_motion_listp; +} + +//-------------------------------------------------------------------- +// LLKeyframeDataCache::removeKeyframeData() +//-------------------------------------------------------------------- +void LLKeyframeDataCache::removeKeyframeData(const LLUUID& id) +{ + LLKeyframeMotion::JointMotionList* joint_motion_listp = getKeyframeData(id); + if (joint_motion_listp) + { + delete joint_motion_listp; + } + sKeyframeDataMap.erase(id); +} + +//-------------------------------------------------------------------- +// LLKeyframeDataCache::getKeyframeData() +//-------------------------------------------------------------------- +LLKeyframeMotion::JointMotionList* LLKeyframeDataCache::getKeyframeData(const LLUUID& id) +{ + LLKeyframeDataMap::iterator found_data = sKeyframeDataMap.find(id); + if (found_data == sKeyframeDataMap.end()) + { + return NULL; + } + return found_data->second; +} + +//-------------------------------------------------------------------- +// ~LLKeyframeDataCache::LLKeyframeDataCache() +//-------------------------------------------------------------------- +LLKeyframeDataCache::~LLKeyframeDataCache() +{ + clear(); +} + +//----------------------------------------------------------------------------- +// clear() +//----------------------------------------------------------------------------- +void LLKeyframeDataCache::clear() +{ + for_each(sKeyframeDataMap.begin(), sKeyframeDataMap.end(), DeletePairedPointer()); + sKeyframeDataMap.clear(); +} + +//----------------------------------------------------------------------------- +// JointConstraint() +//----------------------------------------------------------------------------- +LLKeyframeMotion::JointConstraint::JointConstraint(JointConstraintSharedData* shared_data) : mSharedData(shared_data) +{ + mTotalLength = 0.f; + mActive = FALSE; + mSourceVolume = NULL; + mTargetVolume = NULL; + mFixupDistanceRMS = 0.f; +} + +//----------------------------------------------------------------------------- +// ~JointConstraint() +//----------------------------------------------------------------------------- +LLKeyframeMotion::JointConstraint::~JointConstraint() +{ +} + +// End diff --git a/indra/llcharacter/llkeyframemotion.h b/indra/llcharacter/llkeyframemotion.h new file mode 100644 index 0000000000..4e82c0672a --- /dev/null +++ b/indra/llcharacter/llkeyframemotion.h @@ -0,0 +1,437 @@ +/** + * @file llkeyframemotion.h + * @brief Implementation of LLKeframeMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLKEYFRAMEMOTION_H +#define LL_LLKEYFRAMEMOTION_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- + +#include + +#include "llassetstorage.h" +#include "llassoclist.h" +#include "llbboxlocal.h" +#include "llhandmotion.h" +#include "lljointstate.h" +#include "llmotion.h" +#include "llptrskipmap.h" +#include "llquaternion.h" +#include "v3dmath.h" +#include "v3math.h" +#include "llapr.h" + +class LLKeyframeDataCache; +class LLVFS; +class LLDataPacker; + +#define MIN_REQUIRED_PIXEL_AREA_KEYFRAME (40.f) +#define MAX_CHAIN_LENGTH (4) + +const S32 KEYFRAME_MOTION_VERSION = 1; +const S32 KEYFRAME_MOTION_SUBVERSION = 0; + +//----------------------------------------------------------------------------- +// class LLKeyframeMotion +//----------------------------------------------------------------------------- +class LLKeyframeMotion : + public LLMotion +{ + friend class LLKeyframeDataCache; +public: + // Constructor + LLKeyframeMotion(const LLUUID &id); + + // Destructor + virtual ~LLKeyframeMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID& id); + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual BOOL getLoop() { + if (mJointMotionList) return mJointMotionList->mLoop; + else return FALSE; + } + + // motions must report their total duration + virtual F32 getDuration() { + if (mJointMotionList) return mJointMotionList->mDuration; + else return 0.f; + } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { + if (mJointMotionList) return mJointMotionList->mEaseInDuration; + else return 0.f; + } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { + if (mJointMotionList) return mJointMotionList->mEaseOutDuration; + else return 0.f; + } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { + if (mJointMotionList) return mJointMotionList->mBasePriority; + else return LLJoint::LOW_PRIORITY; + } + + virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_KEYFRAME; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + virtual BOOL onActivate(); + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + virtual BOOL onUpdate(F32 time, U8* joint_mask); + + // called when a motion is deactivated + virtual void onDeactivate(); + + virtual void setStopTime(F32 time); + + static void setVFS(LLVFS* vfs) { sVFS = vfs; } + + static void onLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status); + +public: + U32 getFileSize(); + BOOL serialize(LLDataPacker& dp) const; + BOOL deserialize(LLDataPacker& dp); + void writeCAL3D(apr_file_t* fp); + BOOL isLoaded() { return mJointMotionList != NULL; } + + + // setters for modifying a keyframe animation + void setLoop(BOOL loop); + + F32 getLoopIn() { + return (mJointMotionList) ? mJointMotionList->mLoopInPoint : 0.f; + } + + F32 getLoopOut() { + return (mJointMotionList) ? mJointMotionList->mLoopOutPoint : 0.f; + } + + void setLoopIn(F32 in_point); + + void setLoopOut(F32 out_point); + + void setHandPose(LLHandMotion::eHandPose pose) { + if (mJointMotionList) mJointMotionList->mHandPose = pose; + } + + LLHandMotion::eHandPose getHandPose() { + return (mJointMotionList) ? mJointMotionList->mHandPose : LLHandMotion::HAND_POSE_RELAXED; + } + + void setPriority(S32 priority); + + void setEmote(const LLUUID& emote_id); + + void setEaseIn(F32 ease_in); + + void setEaseOut(F32 ease_in); + + F32 getLastUpdateTime() { return mLastLoopedTime; } + + const LLBBoxLocal& getPelvisBBox(); + + static void flushKeyframeCache(); + + typedef enum e_constraint_type + { + TYPE_POINT, + TYPE_PLANE + } EConstraintType; + + typedef enum e_constraint_target_type + { + TYPE_BODY, + TYPE_GROUND + } EConstraintTargetType; + +protected: + //------------------------------------------------------------------------- + // JointConstraintSharedData + //------------------------------------------------------------------------- + class JointConstraintSharedData + { + public: + JointConstraintSharedData() : + mChainLength(0), + mEaseInStartTime(0.f), + mEaseInStopTime(0.f), + mEaseOutStartTime(0.f), + mEaseOutStopTime(0.f), + mUseTargetOffset(FALSE), + mConstraintType(TYPE_POINT), + mConstraintTargetType(TYPE_BODY) {}; + ~JointConstraintSharedData() { delete [] mJointStateIndices; } + + S32 mSourceConstraintVolume; + LLVector3 mSourceConstraintOffset; + S32 mTargetConstraintVolume; + LLVector3 mTargetConstraintOffset; + LLVector3 mTargetConstraintDir; + S32 mChainLength; + S32* mJointStateIndices; + F32 mEaseInStartTime; + F32 mEaseInStopTime; + F32 mEaseOutStartTime; + F32 mEaseOutStopTime; + BOOL mUseTargetOffset; + EConstraintType mConstraintType; + EConstraintTargetType mConstraintTargetType; + }; + + //----------------------------------------------------------------------------- + // JointConstraint() + //----------------------------------------------------------------------------- + class JointConstraint + { + public: + JointConstraint(JointConstraintSharedData* shared_data); + ~JointConstraint(); + + JointConstraintSharedData* mSharedData; + F32 mWeight; + F32 mTotalLength; + LLVector3 mPositions[MAX_CHAIN_LENGTH]; + F32 mJointLengths[MAX_CHAIN_LENGTH]; + F32 mJointLengthFractions[MAX_CHAIN_LENGTH]; + BOOL mActive; + LLVector3d mGroundPos; + LLVector3 mGroundNorm; + LLJoint* mSourceVolume; + LLJoint* mTargetVolume; + F32 mFixupDistanceRMS; + }; + + void applyKeyframes(F32 time); + + void applyConstraints(F32 time, U8* joint_mask); + + void activateConstraint(JointConstraint* constraintp); + + void initializeConstraint(JointConstraint* constraint); + + void deactivateConstraint(JointConstraint *constraintp); + + void applyConstraint(JointConstraint* constraintp, F32 time, U8* joint_mask); + + BOOL setupPose(); + +public: + enum AssetStatus { ASSET_LOADED, ASSET_FETCHED, ASSET_NEEDS_FETCH, ASSET_FETCH_FAILED, ASSET_UNDEFINED }; + + enum InterpolationType { IT_STEP, IT_LINEAR, IT_SPLINE }; + + //------------------------------------------------------------------------- + // ScaleKey + //------------------------------------------------------------------------- + class ScaleKey + { + public: + ScaleKey() { mTime = 0.0f; } + ScaleKey(F32 time, const LLVector3 &scale) { mTime = time; mScale = scale; } + + F32 mTime; + LLVector3 mScale; + }; + + //------------------------------------------------------------------------- + // RotationKey + //------------------------------------------------------------------------- + class RotationKey + { + public: + RotationKey() { mTime = 0.0f; } + RotationKey(F32 time, const LLQuaternion &rotation) { mTime = time; mRotation = rotation; } + + F32 mTime; + LLQuaternion mRotation; + }; + + //------------------------------------------------------------------------- + // PositionKey + //------------------------------------------------------------------------- + class PositionKey + { + public: + PositionKey() { mTime = 0.0f; } + PositionKey(F32 time, const LLVector3 &position) { mTime = time; mPosition = position; } + + F32 mTime; + LLVector3 mPosition; + }; + + //------------------------------------------------------------------------- + // ScaleCurve + //------------------------------------------------------------------------- + class ScaleCurve + { + public: + ScaleCurve(); + ~ScaleCurve(); + LLVector3 getValue(F32 time, F32 duration); + LLVector3 interp(F32 u, ScaleKey& before, ScaleKey& after); + + InterpolationType mInterpolationType; + S32 mNumKeys; + LLPtrSkipMap mKeys; + ScaleKey mLoopInKey; + ScaleKey mLoopOutKey; + }; + + //------------------------------------------------------------------------- + // RotationCurve + //------------------------------------------------------------------------- + class RotationCurve + { + public: + RotationCurve(); + ~RotationCurve(); + LLQuaternion getValue(F32 time, F32 duration); + LLQuaternion interp(F32 u, RotationKey& before, RotationKey& after); + + InterpolationType mInterpolationType; + S32 mNumKeys; + LLPtrSkipMap mKeys; + RotationKey mLoopInKey; + RotationKey mLoopOutKey; + }; + + //------------------------------------------------------------------------- + // PositionCurve + //------------------------------------------------------------------------- + class PositionCurve + { + public: + PositionCurve(); + ~PositionCurve(); + LLVector3 getValue(F32 time, F32 duration); + LLVector3 interp(F32 u, PositionKey& before, PositionKey& after); + + InterpolationType mInterpolationType; + S32 mNumKeys; + LLPtrSkipMap mKeys; + PositionKey mLoopInKey; + PositionKey mLoopOutKey; + }; + + //------------------------------------------------------------------------- + // JointMotion + //------------------------------------------------------------------------- + class JointMotion + { + public: + PositionCurve mPositionCurve; + RotationCurve mRotationCurve; + ScaleCurve mScaleCurve; + std::string mJointName; + U32 mUsage; + LLJoint::JointPriority mPriority; + + void update(LLJointState *joint_state, F32 time, F32 duration); + }; + + //------------------------------------------------------------------------- + // JointMotionList + //------------------------------------------------------------------------- + class JointMotionList + { + public: + U32 mNumJointMotions; + JointMotion* mJointMotionArray; + F32 mDuration; + BOOL mLoop; + F32 mLoopInPoint; + F32 mLoopOutPoint; + F32 mEaseInDuration; + F32 mEaseOutDuration; + LLJoint::JointPriority mBasePriority; + LLHandMotion::eHandPose mHandPose; + LLJoint::JointPriority mMaxPriority; + LLLinkedList mConstraints; + LLBBoxLocal mPelvisBBox; + public: + JointMotionList() : mNumJointMotions(0), mJointMotionArray(NULL) {}; + ~JointMotionList() { mConstraints.deleteAllData(); delete [] mJointMotionArray; } + U32 dumpDiagInfo(); + }; + + +protected: + static LLVFS* sVFS; + + //------------------------------------------------------------------------- + // Member Data + //------------------------------------------------------------------------- + JointMotionList* mJointMotionList; + LLJointState* mJointStates; + LLJoint* mPelvisp; + LLCharacter* mCharacter; + std::string mEmoteName; + LLLinkedList mConstraints; + U32 mLastSkeletonSerialNum; + F32 mLastUpdateTime; + F32 mLastLoopedTime; + AssetStatus mAssetStatus; +}; + +class LLKeyframeDataCache +{ +public: + //FIXME: implement this as an actual singleton member of LLKeyframeMotion + LLKeyframeDataCache(){}; + ~LLKeyframeDataCache(); + + typedef std::map LLKeyframeDataMap; + static LLKeyframeDataMap sKeyframeDataMap; + + static void addKeyframeData(const LLUUID& id, LLKeyframeMotion::JointMotionList*); + static LLKeyframeMotion::JointMotionList* getKeyframeData(const LLUUID& id); + + static void removeKeyframeData(const LLUUID& id); + + //print out diagnostic info + static void dumpDiagInfo(); + static void clear(); +}; + +#endif // LL_LLKEYFRAMEMOTION_H + diff --git a/indra/llcharacter/llkeyframemotionparam.cpp b/indra/llcharacter/llkeyframemotionparam.cpp new file mode 100644 index 0000000000..c57079fc2b --- /dev/null +++ b/indra/llcharacter/llkeyframemotionparam.cpp @@ -0,0 +1,442 @@ +/** + * @file llkeyframemotionparam.cpp + * @brief Implementation of LLKeyframeMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llkeyframemotionparam.h" +#include "llcharacter.h" +#include "llmath.h" +#include "m3math.h" +#include "lldir.h" +#include "llanimationstates.h" + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// LLKeyframeMotionParam class +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// sortFunc() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotionParam::sortFunc(ParameterizedMotion *new_motion, ParameterizedMotion *tested_motion) +{ + return (new_motion->second < tested_motion->second); +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotionParam() +// Class Constructor +//----------------------------------------------------------------------------- +LLKeyframeMotionParam::LLKeyframeMotionParam( const LLUUID &id) : LLMotion(id) +{ + mJointStates = NULL; + mDefaultKeyframeMotion = NULL; + mCharacter = NULL; + + mEaseInDuration = 0.f; + mEaseOutDuration = 0.f; + mDuration = 0.f; + mPriority = LLJoint::LOW_PRIORITY; +} + + +//----------------------------------------------------------------------------- +// ~LLKeyframeMotionParam() +// Class Destructor +//----------------------------------------------------------------------------- +LLKeyframeMotionParam::~LLKeyframeMotionParam() +{ + for (U32 i = 0; i < mParameterizedMotions.length(); i++) + { + LLLinkedList< ParameterizedMotion > *motionList = *mParameterizedMotions.getValueAt(i); + for (ParameterizedMotion* paramMotion = motionList->getFirstData(); paramMotion; paramMotion = motionList->getNextData()) + { + delete paramMotion->first; + } + delete motionList; + } + + mParameterizedMotions.removeAll(); +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotionParam::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLKeyframeMotionParam::onInitialize(LLCharacter *character) +{ + mCharacter = character; + + if (!loadMotions()) + { + return STATUS_FAILURE; + } + + for (U32 i = 0; i < mParameterizedMotions.length(); i++) + { + LLLinkedList< ParameterizedMotion > *motionList = *mParameterizedMotions.getValueAt(i); + for (ParameterizedMotion* paramMotion = motionList->getFirstData(); paramMotion; paramMotion = motionList->getNextData()) + { + paramMotion->first->onInitialize(character); + + if (paramMotion->first->getDuration() > mEaseInDuration) + { + mEaseInDuration = paramMotion->first->getEaseInDuration(); + } + + if (paramMotion->first->getEaseOutDuration() > mEaseOutDuration) + { + mEaseOutDuration = paramMotion->first->getEaseOutDuration(); + } + + if (paramMotion->first->getDuration() > mDuration) + { + mDuration = paramMotion->first->getDuration(); + } + + if (paramMotion->first->getPriority() > mPriority) + { + mPriority = paramMotion->first->getPriority(); + } + + LLPose *pose = paramMotion->first->getPose(); + + mPoseBlender.addMotion(paramMotion->first); + for (LLJointState *jsp = pose->getFirstJointState(); jsp; jsp = pose->getNextJointState()) + { + LLPose *blendedPose = mPoseBlender.getBlendedPose(); + blendedPose->addJointState(jsp); + } + } + } + + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotionParam::onActivate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotionParam::onActivate() +{ + for (U32 i = 0; i < mParameterizedMotions.length(); i++) + { + LLLinkedList< ParameterizedMotion > *motionList = *mParameterizedMotions.getValueAt(i); + for (ParameterizedMotion* paramMotion = motionList->getFirstData(); paramMotion; paramMotion = motionList->getNextData()) + { + paramMotion->first->activate(); + } + } + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLKeyframeMotionParam::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotionParam::onUpdate(F32 time, U8* joint_mask) +{ + F32 weightFactor = 1.f / (F32)mParameterizedMotions.length(); + U32 i; + + // zero out all pose weights + for (i = 0; i < mParameterizedMotions.length(); i++) + { + LLLinkedList< ParameterizedMotion > *motionList = *mParameterizedMotions.getValueAt(i); + + for (ParameterizedMotion* paramMotion = motionList->getFirstData(); paramMotion; paramMotion = motionList->getNextData()) + { +// llinfos << "Weight for pose " << paramMotion->first->getName() << " is " << paramMotion->first->getPose()->getWeight() << llendl; + paramMotion->first->getPose()->setWeight(0.f); + } + } + + + for (i = 0; i < mParameterizedMotions.length(); i++) + { + LLLinkedList< ParameterizedMotion > *motionList = *mParameterizedMotions.getValueAt(i); + std::string *paramName = mParameterizedMotions.getIndexAt(i); + F32* paramValue = (F32 *)mCharacter->getAnimationData(*paramName); + ParameterizedMotion* firstMotion = NULL; + ParameterizedMotion* secondMotion = NULL; + + for (ParameterizedMotion* paramMotion = motionList->getFirstData(); paramMotion; paramMotion = motionList->getNextData()) + { + paramMotion->first->onUpdate(time, joint_mask); + + F32 distToParam = paramMotion->second - *paramValue; + + if ( distToParam <= 0.f) + { + // keep track of the motion closest to the parameter value + firstMotion = paramMotion; + } + else + { + // we've passed the parameter value + // so store the first motion we find as the second one we want to blend... + if (firstMotion && !secondMotion ) + { + secondMotion = paramMotion; + } + //...or, if we've seen no other motion so far, make sure we blend to this only + else if (!firstMotion) + { + firstMotion = paramMotion; + secondMotion = paramMotion; + } + } + } + + LLPose *firstPose; + LLPose *secondPose; + + if (firstMotion) + firstPose = firstMotion->first->getPose(); + else + firstPose = NULL; + + if (secondMotion) + secondPose = secondMotion->first->getPose(); + else + secondPose = NULL; + + // now modify weight of the subanim (only if we are blending between two motions) + if (firstMotion && secondMotion) + { + if (firstMotion == secondMotion) + { + firstPose->setWeight(weightFactor); + } + else if (firstMotion->second == secondMotion->second) + { + firstPose->setWeight(0.5f * weightFactor); + secondPose->setWeight(0.5f * weightFactor); + } + else + { + F32 first_weight = 1.f - + ((llclamp(*paramValue - firstMotion->second, 0.f, (secondMotion->second - firstMotion->second))) / + (secondMotion->second - firstMotion->second)); + first_weight = llclamp(first_weight, 0.f, 1.f); + + F32 second_weight = 1.f - first_weight; + + firstPose->setWeight(first_weight * weightFactor); + secondPose->setWeight(second_weight * weightFactor); + +// llinfos << "Parameter " << *paramName << ": " << *paramValue << llendl; +// llinfos << "Weights " << firstPose->getWeight() << " " << secondPose->getWeight() << llendl; + } + } + else if (firstMotion && !secondMotion) + { + firstPose->setWeight(weightFactor); + } + } + + // blend poses + mPoseBlender.blendAndApply(); + + llinfos << "Param Motion weight " << mPoseBlender.getBlendedPose()->getWeight() << llendl; + + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotionParam::onDeactivate() +//----------------------------------------------------------------------------- +void LLKeyframeMotionParam::onDeactivate() +{ + for (U32 i = 0; i < mParameterizedMotions.length(); i++) + { + LLLinkedList< ParameterizedMotion > *motionList = *mParameterizedMotions.getValueAt(i); + for (ParameterizedMotion* paramMotion = motionList->getFirstData(); paramMotion; paramMotion = motionList->getNextData()) + { + paramMotion->first->onDeactivate(); + } + } +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotionParam::addKeyframeMotion() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotionParam::addKeyframeMotion(char *name, const LLUUID &id, char *param, F32 value) +{ + LLMotion *newMotion = mCharacter->createMotion( id ); + + if (!newMotion) + { + return FALSE; + } + + newMotion->setName(name); + + // make sure a list of motions exists for this parameter + LLLinkedList< ParameterizedMotion > *motionList; + if (mParameterizedMotions.getValue(param)) + { + motionList = *mParameterizedMotions.getValue(param); + } + else + { + motionList = new LLLinkedList< ParameterizedMotion >; + motionList->setInsertBefore(sortFunc); + mParameterizedMotions.addToHead(param, motionList); + } + + // now add motion to this list + ParameterizedMotion *parameterizedMotion = new ParameterizedMotion(newMotion, value); + + motionList->addDataSorted(parameterizedMotion); + + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLKeyframeMotionParam::setDefaultKeyframeMotion() +//----------------------------------------------------------------------------- +void LLKeyframeMotionParam::setDefaultKeyframeMotion(char *name) +{ + for (U32 i = 0; i < mParameterizedMotions.length(); i++) + { + LLLinkedList< ParameterizedMotion > *motionList = *mParameterizedMotions.getValueAt(i); + for (ParameterizedMotion* paramMotion = motionList->getFirstData(); paramMotion; paramMotion = motionList->getNextData()) + { + if (paramMotion->first->getName() == name) + { + mDefaultKeyframeMotion = paramMotion->first; + } + } + } +} + +//----------------------------------------------------------------------------- +// loadMotions() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotionParam::loadMotions() +{ + //------------------------------------------------------------------------- + // Load named file by concatenating the character prefix with the motion name. + // Load data into a buffer to be parsed. + //------------------------------------------------------------------------- + char path[LL_MAX_PATH]; /* Flawfinder: ignore */ + snprintf( path, sizeof(path), "%s_%s.llp", + gDirUtilp->getExpandedFilename(LL_PATH_MOTIONS,mCharacter->getAnimationPrefix()).c_str(), + getName().c_str() ); /* Flawfinder: ignore */ + + //------------------------------------------------------------------------- + // open the file + //------------------------------------------------------------------------- + S32 fileSize = 0; + apr_file_t* fp = ll_apr_file_open(path, LL_APR_R, &fileSize); + if (!fp || fileSize == 0) + { + llinfos << "ERROR: can't open: " << path << llendl; + return FALSE; + } + + // allocate a text buffer + char *text = new char[ fileSize+1 ]; + if ( !text ) + { + llinfos << "ERROR: can't allocated keyframe text buffer." << llendl; + apr_file_close(fp); + return FALSE; + } + + //------------------------------------------------------------------------- + // load data from file into buffer + //------------------------------------------------------------------------- + bool error = false; + char *p = text; + while ( 1 ) + { + if (apr_file_eof(fp) == APR_EOF) + { + break; + } + if (apr_file_gets(p, 1024, fp) != APR_SUCCESS) + { + error = true; + break; + } + while ( *(++p) ) + ; + } + + //------------------------------------------------------------------------- + // close the file + //------------------------------------------------------------------------- + apr_file_close( fp ); + + //------------------------------------------------------------------------- + // check for error + //------------------------------------------------------------------------- + llassert( p <= (text+fileSize) ); + + if ( error ) + { + llinfos << "ERROR: error while reading from " << path << llendl; + delete [] text; + return FALSE; + } + + llinfos << "Loading parametric keyframe data for: " << getName() << llendl; + + //------------------------------------------------------------------------- + // parse the text and build keyframe data structures + //------------------------------------------------------------------------- + p = text; + S32 num; + char strA[80]; /* Flawfinder: ignore */ + char strB[80]; /* Flawfinder: ignore */ + F32 floatA = 0.0f; + + + //------------------------------------------------------------------------- + // get priority + //------------------------------------------------------------------------- + BOOL isFirstMotion = TRUE; + num = sscanf(p, "%79s %79s %f", strA, strB, &floatA); + + while(1) + { + if (num == 0 || num == EOF) break; + if ((num != 3)) + { + llinfos << "WARNING: can't read parametric motion" << llendl; + delete [] text; + return FALSE; + } + + addKeyframeMotion(strA, gAnimLibrary.stringToAnimState(strA), strB, floatA); + if (isFirstMotion) + { + isFirstMotion = FALSE; + setDefaultKeyframeMotion(strA); + } + + p = strstr(p, "\n"); + if (!p) + { + break; + } + + p++; + num = sscanf(p, "%79s %79s %f", strA, strB, &floatA); + } + + delete [] text; + return TRUE; +} + +// End diff --git a/indra/llcharacter/llkeyframemotionparam.h b/indra/llcharacter/llkeyframemotionparam.h new file mode 100644 index 0000000000..6b23100d5b --- /dev/null +++ b/indra/llcharacter/llkeyframemotionparam.h @@ -0,0 +1,138 @@ +/** + * @file llkeyframemotionparam.h + * @brief Implementation of LLKeframeMotionParam class. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLKEYFRAMEMOTIONPARAM_H +#define LL_LLKEYFRAMEMOTIONPARAM_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- + +#include + +#include "llmotion.h" +#include "lljointstate.h" +#include "v3math.h" +#include "llquaternion.h" +#include "linked_lists.h" +#include "llkeyframemotion.h" + +//----------------------------------------------------------------------------- +// class LLKeyframeMotionParam +//----------------------------------------------------------------------------- +class LLKeyframeMotionParam : + public LLMotion +{ +public: + // Constructor + LLKeyframeMotionParam(const LLUUID &id); + + // Destructor + virtual ~LLKeyframeMotionParam(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLKeyframeMotionParam(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual BOOL getLoop() { + return TRUE; + } + + // motions must report their total duration + virtual F32 getDuration() { + return mDuration; + } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { + return mEaseInDuration; + } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { + return mEaseOutDuration; + } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { + return mPriority; + } + + virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_KEYFRAME; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + virtual BOOL onActivate(); + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + virtual BOOL onUpdate(F32 time, U8* joint_mask); + + // called when a motion is deactivated + virtual void onDeactivate(); + + virtual LLPose* getPose() { return mPoseBlender.getBlendedPose();} + +protected: + //------------------------------------------------------------------------- + // new functions defined by this subclass + //------------------------------------------------------------------------- + typedef std::pair ParameterizedMotion; + + // add a motion and associated parameter triplet + BOOL addKeyframeMotion(char *name, const LLUUID &id, char *param, F32 value); + + // set default motion for LOD and retrieving blend constants + void setDefaultKeyframeMotion(char *); + + static BOOL sortFunc(ParameterizedMotion *new_motion, ParameterizedMotion *tested_motion); + + BOOL loadMotions(); + +protected: + //------------------------------------------------------------------------- + // Member Data + //------------------------------------------------------------------------- + + typedef LLLinkedList < ParameterizedMotion > motion_list_t; + LLAssocList mParameterizedMotions; + LLJointState* mJointStates; + LLMotion* mDefaultKeyframeMotion; + LLCharacter* mCharacter; + LLPoseBlender mPoseBlender; + + F32 mEaseInDuration; + F32 mEaseOutDuration; + F32 mDuration; + LLJoint::JointPriority mPriority; + + LLUUID mTransactionID; +}; + +#endif // LL_LLKEYFRAMEMOTIONPARAM_H diff --git a/indra/llcharacter/llkeyframestandmotion.cpp b/indra/llcharacter/llkeyframestandmotion.cpp new file mode 100644 index 0000000000..6810fb450f --- /dev/null +++ b/indra/llcharacter/llkeyframestandmotion.cpp @@ -0,0 +1,320 @@ +/** + * @file llkeyframestandmotion.cpp + * @brief Implementation of LLKeyframeStandMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llkeyframestandmotion.h" +#include "llcharacter.h" + +//----------------------------------------------------------------------------- +// Macros and consts +//----------------------------------------------------------------------------- +#define GO_TO_KEY_POSE 1 +#define MIN_TRACK_SPEED 0.01f +const F32 ROTATION_THRESHOLD = 0.6f; +const F32 POSITION_THRESHOLD = 0.1f; + +//----------------------------------------------------------------------------- +// LLKeyframeStandMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLKeyframeStandMotion::LLKeyframeStandMotion(const LLUUID &id) : LLKeyframeMotion(id) +{ + mFlipFeet = FALSE; + mCharacter = NULL; + + // create kinematic hierarchy + mPelvisJoint.addChild( &mHipLeftJoint ); + mHipLeftJoint.addChild( &mKneeLeftJoint ); + mKneeLeftJoint.addChild( &mAnkleLeftJoint ); + mPelvisJoint.addChild( &mHipRightJoint ); + mHipRightJoint.addChild( &mKneeRightJoint ); + mKneeRightJoint.addChild( &mAnkleRightJoint ); + + mPelvisState = NULL; + + mHipLeftState = NULL; + mKneeLeftState = NULL; + mAnkleLeftState = NULL; + + mHipRightState = NULL; + mKneeRightState = NULL; + mAnkleRightState = NULL; + + mTrackAnkles = TRUE; + + mFrameNum = 0; +} + + +//----------------------------------------------------------------------------- +// ~LLKeyframeStandMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLKeyframeStandMotion::~LLKeyframeStandMotion() +{ +} + + +//----------------------------------------------------------------------------- +// LLKeyframeStandMotion::onInitialize() +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLKeyframeStandMotion::onInitialize(LLCharacter *character) +{ + // save character pointer for later use + mCharacter = character; + + mFlipFeet = FALSE; + + // load keyframe data, setup pose and joint states + LLMotion::LLMotionInitStatus status = LLKeyframeMotion::onInitialize(character); + if ( status == STATUS_FAILURE ) + { + return status; + } + + // find the necessary joint states + LLPose *pose = getPose(); + mPelvisState = pose->findJointState("mPelvis"); + + mHipLeftState = pose->findJointState("mHipLeft"); + mKneeLeftState = pose->findJointState("mKneeLeft"); + mAnkleLeftState = pose->findJointState("mAnkleLeft"); + + mHipRightState = pose->findJointState("mHipRight"); + mKneeRightState = pose->findJointState("mKneeRight"); + mAnkleRightState = pose->findJointState("mAnkleRight"); + + if ( !mPelvisState || + !mHipLeftState || + !mKneeLeftState || + !mAnkleLeftState || + !mHipRightState || + !mKneeRightState || + !mAnkleRightState ) + { + llinfos << getName() << ": Can't find necessary joint states" << llendl; + return STATUS_FAILURE; + } + + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// LLKeyframeStandMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeStandMotion::onActivate() +{ + //------------------------------------------------------------------------- + // setup the IK solvers + //------------------------------------------------------------------------- + mIKLeft.setPoleVector( LLVector3(1.0f, 0.0f, 0.0f)); + mIKRight.setPoleVector( LLVector3(1.0f, 0.0f, 0.0f)); + mIKLeft.setBAxis( LLVector3(0.05f, 1.0f, 0.0f)); + mIKRight.setBAxis( LLVector3(-0.05f, 1.0f, 0.0f)); + + mLastGoodPelvisRotation.loadIdentity(); + mLastGoodPosition.clearVec(); + + mFrameNum = 0; + + return LLKeyframeMotion::onActivate(); +} + +//----------------------------------------------------------------------------- +// LLKeyframeStandMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLKeyframeStandMotion::onDeactivate() +{ + LLKeyframeMotion::onDeactivate(); +} + +//----------------------------------------------------------------------------- +// LLKeyframeStandMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeStandMotion::onUpdate(F32 time, U8* joint_mask) +{ + //------------------------------------------------------------------------- + // let the base class update the cycle + //------------------------------------------------------------------------- + BOOL status = LLKeyframeMotion::onUpdate(time, joint_mask); + if (!status) + { + return FALSE; + } + + LLVector3 root_world_pos = mPelvisState->getJoint()->getParent()->getWorldPosition(); + + // have we received a valid world position for this avatar? + if (root_world_pos.isExactlyZero()) + { + return TRUE; + } + + //------------------------------------------------------------------------- + // Stop tracking (start locking) ankles once ease in is done. + // Setting this here ensures we track until we get valid foot position. + //------------------------------------------------------------------------- + if (dot(mPelvisState->getJoint()->getWorldRotation(), mLastGoodPelvisRotation) < ROTATION_THRESHOLD) + { + mLastGoodPelvisRotation = mPelvisState->getJoint()->getWorldRotation(); + mLastGoodPelvisRotation.normQuat(); + mTrackAnkles = TRUE; + } + else if ((mCharacter->getCharacterPosition() - mLastGoodPosition).magVecSquared() > POSITION_THRESHOLD) + { + mLastGoodPosition = mCharacter->getCharacterPosition(); + mTrackAnkles = TRUE; + } + else if (mPose.getWeight() < 1.f) + { + mTrackAnkles = TRUE; + } + + + //------------------------------------------------------------------------- + // propagate joint positions to internal versions + //------------------------------------------------------------------------- + mPelvisJoint.setPosition( + root_world_pos + + mPelvisState->getPosition() ); + + mHipLeftJoint.setPosition( mHipLeftState->getJoint()->getPosition() ); + mKneeLeftJoint.setPosition( mKneeLeftState->getJoint()->getPosition() ); + mAnkleLeftJoint.setPosition( mAnkleLeftState->getJoint()->getPosition() ); + + mHipLeftJoint.setScale( mHipLeftState->getJoint()->getScale() ); + mKneeLeftJoint.setScale( mKneeLeftState->getJoint()->getScale() ); + mAnkleLeftJoint.setScale( mAnkleLeftState->getJoint()->getScale() ); + + mHipRightJoint.setPosition( mHipRightState->getJoint()->getPosition() ); + mKneeRightJoint.setPosition( mKneeRightState->getJoint()->getPosition() ); + mAnkleRightJoint.setPosition( mAnkleRightState->getJoint()->getPosition() ); + + mHipRightJoint.setScale( mHipRightState->getJoint()->getScale() ); + mKneeRightJoint.setScale( mKneeRightState->getJoint()->getScale() ); + mAnkleRightJoint.setScale( mAnkleRightState->getJoint()->getScale() ); + //------------------------------------------------------------------------- + // propagate joint rotations to internal versions + //------------------------------------------------------------------------- + mPelvisJoint.setRotation( mPelvisState->getJoint()->getWorldRotation() ); + +#if GO_TO_KEY_POSE + mHipLeftJoint.setRotation( mHipLeftState->getRotation() ); + mKneeLeftJoint.setRotation( mKneeLeftState->getRotation() ); + mAnkleLeftJoint.setRotation( mAnkleLeftState->getRotation() ); + + mHipRightJoint.setRotation( mHipRightState->getRotation() ); + mKneeRightJoint.setRotation( mKneeRightState->getRotation() ); + mAnkleRightJoint.setRotation( mAnkleRightState->getRotation() ); +#else + mHipLeftJoint.setRotation( mHipLeftState->getJoint()->getRotation() ); + mKneeLeftJoint.setRotation( mKneeLeftState->getJoint()->getRotation() ); + mAnkleLeftJoint.setRotation( mAnkleLeftState->getJoint()->getRotation() ); + + mHipRightJoint.setRotation( mHipRightState->getJoint()->getRotation() ); + mKneeRightJoint.setRotation( mKneeRightState->getJoint()->getRotation() ); + mAnkleRightJoint.setRotation( mAnkleRightState->getJoint()->getRotation() ); +#endif + + // need to wait for underlying keyframe motion to affect the skeleton + if (mFrameNum == 2) + { + mIKLeft.setupJoints( &mHipLeftJoint, &mKneeLeftJoint, &mAnkleLeftJoint, &mTargetLeft ); + mIKRight.setupJoints( &mHipRightJoint, &mKneeRightJoint, &mAnkleRightJoint, &mTargetRight ); + } + else if (mFrameNum < 2) + { + mFrameNum++; + return TRUE; + } + + mFrameNum++; + + //------------------------------------------------------------------------- + // compute target position by projecting ankles to the ground + //------------------------------------------------------------------------- + if ( mTrackAnkles ) + { + mCharacter->getGround( mAnkleLeftJoint.getWorldPosition(), mPositionLeft, mNormalLeft); + mCharacter->getGround( mAnkleRightJoint.getWorldPosition(), mPositionRight, mNormalRight); + + mTargetLeft.setPosition( mPositionLeft ); + mTargetRight.setPosition( mPositionRight ); + } + + //------------------------------------------------------------------------- + // update solvers + //------------------------------------------------------------------------- + mIKLeft.solve(); + mIKRight.solve(); + + //------------------------------------------------------------------------- + // make ankle rotation conform to the ground + //------------------------------------------------------------------------- + if ( mTrackAnkles ) + { + LLVector4 dirLeft4 = mAnkleLeftJoint.getWorldMatrix().getFwdRow4(); + LLVector4 dirRight4 = mAnkleRightJoint.getWorldMatrix().getFwdRow4(); + LLVector3 dirLeft = vec4to3( dirLeft4 ); + LLVector3 dirRight = vec4to3( dirRight4 ); + + LLVector3 up; + LLVector3 dir; + LLVector3 left; + + up = mNormalLeft; + up.normVec(); + if (mFlipFeet) + { + up *= -1.0f; + } + dir = dirLeft; + dir.normVec(); + left = up % dir; + left.normVec(); + dir = left % up; + mRotationLeft = LLQuaternion( dir, left, up ); + + up = mNormalRight; + up.normVec(); + if (mFlipFeet) + { + up *= -1.0f; + } + dir = dirRight; + dir.normVec(); + left = up % dir; + left.normVec(); + dir = left % up; + mRotationRight = LLQuaternion( dir, left, up ); + } + mAnkleLeftJoint.setWorldRotation( mRotationLeft ); + mAnkleRightJoint.setWorldRotation( mRotationRight ); + + //------------------------------------------------------------------------- + // propagate joint rotations to joint states + //------------------------------------------------------------------------- + mHipLeftState->setRotation( mHipLeftJoint.getRotation() ); + mKneeLeftState->setRotation( mKneeLeftJoint.getRotation() ); + mAnkleLeftState->setRotation( mAnkleLeftJoint.getRotation() ); + + mHipRightState->setRotation( mHipRightJoint.getRotation() ); + mKneeRightState->setRotation( mKneeRightJoint.getRotation() ); + mAnkleRightState->setRotation( mAnkleRightJoint.getRotation() ); + + //llinfos << "Stand drift amount " << (mCharacter->getCharacterPosition() - mLastGoodPosition).magVec() << llendl; + +// llinfos << "DEBUG: " << speed << " : " << mTrackAnkles << llendl; + return TRUE; +} + +// End diff --git a/indra/llcharacter/llkeyframestandmotion.h b/indra/llcharacter/llkeyframestandmotion.h new file mode 100644 index 0000000000..a82c92d07f --- /dev/null +++ b/indra/llcharacter/llkeyframestandmotion.h @@ -0,0 +1,98 @@ +/** + * @file llkeyframestandmotion.h + * @brief Implementation of LLKeyframeStandMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLKEYFRAMESTANDMOTION_H +#define LL_LLKEYFRAMESTANDMOTION_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include "llkeyframemotion.h" +#include "lljointsolverrp3.h" + + +//----------------------------------------------------------------------------- +// class LLKeyframeStandMotion +//----------------------------------------------------------------------------- +class LLKeyframeStandMotion : + public LLKeyframeMotion +{ +public: + // Constructor + LLKeyframeStandMotion(const LLUUID &id); + + // Destructor + virtual ~LLKeyframeStandMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLKeyframeStandMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + virtual BOOL onActivate(); + void onDeactivate(); + virtual BOOL onUpdate(F32 time, U8* joint_mask); + +public: + //------------------------------------------------------------------------- + // Member Data + //------------------------------------------------------------------------- + LLCharacter *mCharacter; + + BOOL mFlipFeet; + + LLJointState *mPelvisState; + + LLJointState *mHipLeftState; + LLJointState *mKneeLeftState; + LLJointState *mAnkleLeftState; + + LLJointState *mHipRightState; + LLJointState *mKneeRightState; + LLJointState *mAnkleRightState; + + LLJoint mPelvisJoint; + + LLJoint mHipLeftJoint; + LLJoint mKneeLeftJoint; + LLJoint mAnkleLeftJoint; + LLJoint mTargetLeft; + + LLJoint mHipRightJoint; + LLJoint mKneeRightJoint; + LLJoint mAnkleRightJoint; + LLJoint mTargetRight; + + LLJointSolverRP3 mIKLeft; + LLJointSolverRP3 mIKRight; + + LLVector3 mPositionLeft; + LLVector3 mPositionRight; + LLVector3 mNormalLeft; + LLVector3 mNormalRight; + LLQuaternion mRotationLeft; + LLQuaternion mRotationRight; + + LLQuaternion mLastGoodPelvisRotation; + LLVector3 mLastGoodPosition; + BOOL mTrackAnkles; + + S32 mFrameNum; +}; + +#endif // LL_LLKEYFRAMESTANDMOTION_H + diff --git a/indra/llcharacter/llkeyframewalkmotion.cpp b/indra/llcharacter/llkeyframewalkmotion.cpp new file mode 100644 index 0000000000..930427aa7a --- /dev/null +++ b/indra/llcharacter/llkeyframewalkmotion.cpp @@ -0,0 +1,379 @@ +/** + * @file llkeyframewalkmotion.cpp + * @brief Implementation of LLKeyframeWalkMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llkeyframewalkmotion.h" +#include "llcharacter.h" +#include "llmath.h" +#include "m3math.h" +#include "llcriticaldamp.h" + +//----------------------------------------------------------------------------- +// Macros +//----------------------------------------------------------------------------- +const F32 MAX_WALK_PLAYBACK_SPEED = 8.f; // max m/s for which we adjust walk cycle speed + +const F32 MIN_WALK_SPEED = 0.1f; // minimum speed at which we use velocity for down foot detection +const F32 MAX_TIME_DELTA = 2.f; //max two seconds a frame for calculating interpolation +const F32 SPEED_ADJUST_MAX = 2.5f; // maximum adjustment of walk animation playback speed +const F32 SPEED_ADJUST_MAX_SEC = 3.f; // maximum adjustment to walk animation playback speed for a second +const F32 DRIFT_COMP_MAX_TOTAL = 0.07f;//0.55f; // maximum drift compensation overall, in any direction +const F32 DRIFT_COMP_MAX_SPEED = 4.f; // speed at which drift compensation total maxes out +const F32 MAX_ROLL = 0.6f; + +//----------------------------------------------------------------------------- +// LLKeyframeWalkMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLKeyframeWalkMotion::LLKeyframeWalkMotion(const LLUUID &id) : LLKeyframeMotion(id) +{ + mRealTimeLast = 0.0f; + mAdjTimeLast = 0.0f; + mCharacter = NULL; +} + + +//----------------------------------------------------------------------------- +// ~LLKeyframeWalkMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLKeyframeWalkMotion::~LLKeyframeWalkMotion() +{ +} + + +//----------------------------------------------------------------------------- +// LLKeyframeWalkMotion::onInitialize() +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLKeyframeWalkMotion::onInitialize(LLCharacter *character) +{ + mCharacter = character; + + return LLKeyframeMotion::onInitialize(character); +} + +//----------------------------------------------------------------------------- +// LLKeyframeWalkMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeWalkMotion::onActivate() +{ + mRealTimeLast = 0.0f; + mAdjTimeLast = 0.0f; + + return LLKeyframeMotion::onActivate(); +} + +//----------------------------------------------------------------------------- +// LLKeyframeWalkMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLKeyframeWalkMotion::onDeactivate() +{ + mCharacter->removeAnimationData("Down Foot"); + LLKeyframeMotion::onDeactivate(); +} + +//----------------------------------------------------------------------------- +// LLKeyframeWalkMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeWalkMotion::onUpdate(F32 time, U8* joint_mask) +{ + // compute time since last update + F32 deltaTime = time - mRealTimeLast; + + void* speed_ptr = mCharacter->getAnimationData("Walk Speed"); + F32 speed = (speed_ptr) ? *((F32 *)speed_ptr) : 1.f; + + // adjust the passage of time accordingly + F32 adjusted_time = mAdjTimeLast + (deltaTime * speed); + + // save time for next update + mRealTimeLast = time; + mAdjTimeLast = adjusted_time; + + // handle wrap around + if (adjusted_time < 0.0f) + { + adjusted_time = getDuration() + fmod(adjusted_time, getDuration()); + } + + // let the base class update the cycle + return LLKeyframeMotion::onUpdate( adjusted_time, joint_mask ); +} + +// End + + +//----------------------------------------------------------------------------- +// LLWalkAdjustMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLWalkAdjustMotion::LLWalkAdjustMotion(const LLUUID &id) : LLMotion(id) +{ + mLastTime = 0.f; + mName = "walk_adjust"; +} + +//----------------------------------------------------------------------------- +// LLWalkAdjustMotion::onInitialize() +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLWalkAdjustMotion::onInitialize(LLCharacter *character) +{ + mCharacter = character; + mLeftAnkleJoint = mCharacter->getJoint("mAnkleLeft"); + mRightAnkleJoint = mCharacter->getJoint("mAnkleRight"); + + mPelvisJoint = mCharacter->getJoint("mPelvis"); + mPelvisState.setJoint( mPelvisJoint ); + if ( !mPelvisJoint ) + { + llwarns << getName() << ": Can't get pelvis joint." << llendl; + return STATUS_FAILURE; + } + + mPelvisState.setUsage(LLJointState::POS); + addJointState( &mPelvisState ); + + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// LLWalkAdjustMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLWalkAdjustMotion::onActivate() +{ + mAvgCorrection = 0.f; + mSpeedAdjust = 0.f; + mAnimSpeed = 0.f; + mAvgSpeed = 0.f; + mRelativeDir = 1.f; + mPelvisState.setPosition(LLVector3::zero); + // store ankle positions for next frame + mLastLeftAnklePos = mCharacter->getPosGlobalFromAgent(mLeftAnkleJoint->getWorldPosition()); + mLastRightAnklePos = mCharacter->getPosGlobalFromAgent(mRightAnkleJoint->getWorldPosition()); + + F32 leftAnkleOffset = (mLeftAnkleJoint->getWorldPosition() - mCharacter->getCharacterPosition()).magVec(); + F32 rightAnkleOffset = (mRightAnkleJoint->getWorldPosition() - mCharacter->getCharacterPosition()).magVec(); + mAnkleOffset = llmax(leftAnkleOffset, rightAnkleOffset); + + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLWalkAdjustMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLWalkAdjustMotion::onUpdate(F32 time, U8* joint_mask) +{ + LLVector3 footCorrection; + LLVector3 vel = mCharacter->getCharacterVelocity() * mCharacter->getTimeDilation(); + F32 deltaTime = llclamp(time - mLastTime, 0.f, MAX_TIME_DELTA); + mLastTime = time; + + LLQuaternion inv_rotation = ~mPelvisJoint->getWorldRotation(); + + // get speed and normalize velocity vector + LLVector3 ang_vel = mCharacter->getCharacterAngularVelocity() * mCharacter->getTimeDilation(); + F32 speed = llmin(vel.normVec(), MAX_WALK_PLAYBACK_SPEED); + mAvgSpeed = lerp(mAvgSpeed, speed, LLCriticalDamp::getInterpolant(0.2f)); + + // calculate facing vector in pelvis-local space + // (either straight forward or back, depending on velocity) + LLVector3 localVel = vel * inv_rotation; + if (localVel.mV[VX] > 0.f) + { + mRelativeDir = 1.f; + } + else if (localVel.mV[VX] < 0.f) + { + mRelativeDir = -1.f; + } + + // calculate world-space foot drift + LLVector3 leftFootDelta; + LLVector3 leftFootWorldPosition = mLeftAnkleJoint->getWorldPosition(); + LLVector3d leftFootGlobalPosition = mCharacter->getPosGlobalFromAgent(leftFootWorldPosition); + leftFootDelta.setVec(mLastLeftAnklePos - leftFootGlobalPosition); + mLastLeftAnklePos = leftFootGlobalPosition; + + LLVector3 rightFootDelta; + LLVector3 rightFootWorldPosition = mRightAnkleJoint->getWorldPosition(); + LLVector3d rightFootGlobalPosition = mCharacter->getPosGlobalFromAgent(rightFootWorldPosition); + rightFootDelta.setVec(mLastRightAnklePos - rightFootGlobalPosition); + mLastRightAnklePos = rightFootGlobalPosition; + + // find foot drift along velocity vector + if (mAvgSpeed > 0.1) + { + // walking/running + F32 leftFootDriftAmt = leftFootDelta * vel; + F32 rightFootDriftAmt = rightFootDelta * vel; + + if (rightFootDriftAmt > leftFootDriftAmt) + { + footCorrection = rightFootDelta; + } else + { + footCorrection = leftFootDelta; + } + } + else + { + mAvgSpeed = ang_vel.magVec() * mAnkleOffset; + mRelativeDir = 1.f; + + // standing/turning + // find the lower foot + if (leftFootWorldPosition.mV[VZ] < rightFootWorldPosition.mV[VZ]) + { + // pivot on left foot + footCorrection = leftFootDelta; + } + else + { + // pivot on right foot + footCorrection = rightFootDelta; + } + } + + // rotate into avatar coordinates + footCorrection = footCorrection * inv_rotation; + + // calculate ideal pelvis offset so that foot is glued to ground and damp towards it + // the amount of foot slippage this frame + the offset applied last frame + mPelvisOffset = mPelvisState.getPosition() + lerp(LLVector3::zero, footCorrection, LLCriticalDamp::getInterpolant(0.2f)); + + // pelvis drift (along walk direction) + mAvgCorrection = lerp(mAvgCorrection, footCorrection.mV[VX] * mRelativeDir, LLCriticalDamp::getInterpolant(0.1f)); + + // calculate average velocity of foot slippage + F32 footSlipVelocity = (deltaTime != 0.f) ? (-mAvgCorrection / deltaTime) : 0.f; + + F32 newSpeedAdjust = 0.f; + + // modulate speed by dot products of facing and velocity + // so that if we are moving sideways, we slow down the animation + // and if we're moving backward, we walk backward + + F32 directional_factor = localVel.mV[VX] * mRelativeDir; + if (speed > 0.1f) + { + // calculate ratio of desired foot velocity to detected foot velocity + newSpeedAdjust = llclamp(footSlipVelocity - mAvgSpeed * (1.f - directional_factor), + -SPEED_ADJUST_MAX, SPEED_ADJUST_MAX); + newSpeedAdjust = lerp(mSpeedAdjust, newSpeedAdjust, LLCriticalDamp::getInterpolant(0.2f)); + + F32 speedDelta = newSpeedAdjust - mSpeedAdjust; + speedDelta = llclamp(speedDelta, -SPEED_ADJUST_MAX_SEC * deltaTime, SPEED_ADJUST_MAX_SEC * deltaTime); + + mSpeedAdjust = mSpeedAdjust + speedDelta; + } + else + { + mSpeedAdjust = lerp(mSpeedAdjust, 0.f, LLCriticalDamp::getInterpolant(0.2f)); + } + + mAnimSpeed = (mAvgSpeed + mSpeedAdjust) * mRelativeDir; +// char debug_text[64]; +// sprintf(debug_text, "Foot slip vel: %.2f", footSlipVelocity); +// mCharacter->addDebugText(debug_text); +// sprintf(debug_text, "Speed: %.2f", mAvgSpeed); +// mCharacter->addDebugText(debug_text); +// sprintf(debug_text, "Speed Adjust: %.2f", mSpeedAdjust); +// mCharacter->addDebugText(debug_text); +// sprintf(debug_text, "Animation Playback Speed: %.2f", mAnimSpeed); +// mCharacter->addDebugText(debug_text); + mCharacter->setAnimationData("Walk Speed", &mAnimSpeed); + + // clamp pelvis offset to a 90 degree arc behind the nominal position + F32 drift_comp_max = llclamp(speed, 0.f, DRIFT_COMP_MAX_SPEED) / DRIFT_COMP_MAX_SPEED; + drift_comp_max *= DRIFT_COMP_MAX_TOTAL; + + LLVector3 currentPelvisPos = mPelvisState.getJoint()->getPosition(); + + // NB: this is an ADDITIVE amount that is accumulated every frame, so clamping it alone won't do the trick + // must clamp with absolute position of pelvis in mind + mPelvisOffset.mV[VX] = llclamp( mPelvisOffset.mV[VX], -drift_comp_max - currentPelvisPos.mV[VX], drift_comp_max - currentPelvisPos.mV[VX] ); + mPelvisOffset.mV[VY] = llclamp( mPelvisOffset.mV[VY], -drift_comp_max - currentPelvisPos.mV[VY], drift_comp_max - currentPelvisPos.mV[VY]); + mPelvisOffset.mV[VZ] = 0.f; + + // set position + mPelvisState.setPosition(mPelvisOffset); + + mCharacter->setAnimationData("Pelvis Offset", &mPelvisOffset); + + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLWalkAdjustMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLWalkAdjustMotion::onDeactivate() +{ + mCharacter->removeAnimationData("Walk Speed"); +} + +//----------------------------------------------------------------------------- +// LLFlyAdjustMotion::onInitialize() +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLFlyAdjustMotion::onInitialize(LLCharacter *character) +{ + mCharacter = character; + + LLJoint* pelvisJoint = mCharacter->getJoint("mPelvis"); + mPelvisState.setJoint( pelvisJoint ); + if ( !pelvisJoint ) + { + llwarns << getName() << ": Can't get pelvis joint." << llendl; + return STATUS_FAILURE; + } + + mPelvisState.setUsage(LLJointState::POS | LLJointState::ROT); + addJointState( &mPelvisState ); + + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// LLFlyAdjustMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLFlyAdjustMotion::onActivate() +{ + mPelvisState.setPosition(LLVector3::zero); + mPelvisState.setRotation(LLQuaternion::DEFAULT); + mRoll = 0.f; + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLFlyAdjustMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLFlyAdjustMotion::onUpdate(F32 time, U8* joint_mask) +{ + LLVector3 ang_vel = mCharacter->getCharacterAngularVelocity() * mCharacter->getTimeDilation(); + F32 speed = mCharacter->getCharacterVelocity().magVec(); + + F32 roll_factor = clamp_rescale(speed, 7.f, 15.f, 0.f, -MAX_ROLL); + F32 target_roll = llclamp(ang_vel.mV[VZ], -4.f, 4.f) * roll_factor; + + // roll is critically damped interpolation between current roll and angular velocity-derived target roll + mRoll = lerp(mRoll, target_roll, LLCriticalDamp::getInterpolant(0.1f)); + +// llinfos << mRoll << llendl; + + LLQuaternion roll(mRoll, LLVector3(0.f, 0.f, 1.f)); + mPelvisState.setRotation(roll); + +// F32 lerp_amt = LLCriticalDamp::getInterpolant(0.2f); +// +// LLVector3 pelvis_correction = mPelvisState.getPosition() - lerp(LLVector3::zero, mPelvisState.getJoint()->getPosition() + mPelvisState.getPosition(), lerp_amt); +// mPelvisState.setPosition(pelvis_correction); + return TRUE; +} diff --git a/indra/llcharacter/llkeyframewalkmotion.h b/indra/llcharacter/llkeyframewalkmotion.h new file mode 100644 index 0000000000..05286456d3 --- /dev/null +++ b/indra/llcharacter/llkeyframewalkmotion.h @@ -0,0 +1,158 @@ +/** + * @file llkeyframewalkmotion.h + * @brief Implementation of LLKeframeWalkMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLKEYFRAMEWALKMOTION_H +#define LL_LLKEYFRAMEWALKMOTION_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include "llkeyframemotion.h" +#include "llcharacter.h" +#include "v3dmath.h" + +#define MIN_REQUIRED_PIXEL_AREA_WALK_ADJUST (20.f) +#define MIN_REQUIRED_PIXEL_AREA_FLY_ADJUST (20.f) + +//----------------------------------------------------------------------------- +// class LLKeyframeWalkMotion +//----------------------------------------------------------------------------- +class LLKeyframeWalkMotion : + public LLKeyframeMotion +{ + friend class LLWalkAdjustMotion; +public: + // Constructor + LLKeyframeWalkMotion(const LLUUID &id); + + // Destructor + virtual ~LLKeyframeWalkMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLKeyframeWalkMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + virtual BOOL onActivate(); + virtual void onDeactivate(); + virtual BOOL onUpdate(F32 time, U8* joint_mask); + +public: + //------------------------------------------------------------------------- + // Member Data + //------------------------------------------------------------------------- + LLCharacter *mCharacter; + F32 mCyclePhase; + F32 mRealTimeLast; + F32 mAdjTimeLast; + S32 mDownFoot; +}; + +class LLWalkAdjustMotion : public LLMotion +{ +public: + // Constructor + LLWalkAdjustMotion(const LLUUID &id); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLWalkAdjustMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + virtual BOOL onActivate(); + virtual void onDeactivate(); + virtual BOOL onUpdate(F32 time, U8* joint_mask); + virtual LLJoint::JointPriority getPriority(){return LLJoint::HIGH_PRIORITY;} + virtual BOOL getLoop() { return TRUE; } + virtual F32 getDuration() { return 0.f; } + virtual F32 getEaseInDuration() { return 0.f; } + virtual F32 getEaseOutDuration() { return 0.f; } + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_WALK_ADJUST; } + virtual LLMotionBlendType getBlendType() { return ADDITIVE_BLEND; } + +public: + //------------------------------------------------------------------------- + // Member Data + //------------------------------------------------------------------------- + LLCharacter *mCharacter; + LLJoint* mLeftAnkleJoint; + LLJoint* mRightAnkleJoint; + LLJointState mPelvisState; + LLJoint* mPelvisJoint; + LLVector3d mLastLeftAnklePos; + LLVector3d mLastRightAnklePos; + F32 mLastTime; + F32 mAvgCorrection; + F32 mSpeedAdjust; + F32 mAnimSpeed; + F32 mAvgSpeed; + F32 mRelativeDir; + LLVector3 mPelvisOffset; + F32 mAnkleOffset; +}; + +class LLFlyAdjustMotion : public LLMotion +{ +public: + // Constructor + LLFlyAdjustMotion(const LLUUID &id) : LLMotion(id) {mName = "fly_adjust";} + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLFlyAdjustMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + virtual BOOL onActivate(); + virtual void onDeactivate() {}; + virtual BOOL onUpdate(F32 time, U8* joint_mask); + virtual LLJoint::JointPriority getPriority(){return LLJoint::HIGHER_PRIORITY;} + virtual BOOL getLoop() { return TRUE; } + virtual F32 getDuration() { return 0.f; } + virtual F32 getEaseInDuration() { return 0.f; } + virtual F32 getEaseOutDuration() { return 0.f; } + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_FLY_ADJUST; } + virtual LLMotionBlendType getBlendType() { return ADDITIVE_BLEND; } + +protected: + //------------------------------------------------------------------------- + // Member Data + //------------------------------------------------------------------------- + LLCharacter *mCharacter; + LLJointState mPelvisState; + F32 mRoll; +}; + +#endif // LL_LLKeyframeWalkMotion_H + diff --git a/indra/llcharacter/llmotion.cpp b/indra/llcharacter/llmotion.cpp new file mode 100644 index 0000000000..a53956223c --- /dev/null +++ b/indra/llcharacter/llmotion.cpp @@ -0,0 +1,131 @@ +/** + * @file llmotion.cpp + * @brief Implementation of LLMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llmotion.h" +#include "llcriticaldamp.h" + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// LLMotion class +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// LLMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLMotion::LLMotion( const LLUUID &id ) +{ + mActivationTimestamp = 0.f; + mStopTimestamp = 0.f; + mSendStopTimestamp = F32_MAX; + mResidualWeight = 0.f; + mFadeWeight = 1.f; + mStopped = TRUE; + mActive = FALSE; + mDeactivateCallback = NULL; + + memset(&mJointSignature[0][0], 0, sizeof(U8) * LL_CHARACTER_MAX_JOINTS); + memset(&mJointSignature[1][0], 0, sizeof(U8) * LL_CHARACTER_MAX_JOINTS); + memset(&mJointSignature[2][0], 0, sizeof(U8) * LL_CHARACTER_MAX_JOINTS); + + mID = id; +} + +//----------------------------------------------------------------------------- +// ~LLMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLMotion::~LLMotion() +{ +} + +//----------------------------------------------------------------------------- +// fadeOut() +//----------------------------------------------------------------------------- +void LLMotion::fadeOut() +{ + if (mFadeWeight > 0.01f) + { + mFadeWeight = lerp(mFadeWeight, 0.f, LLCriticalDamp::getInterpolant(0.15f)); + } + else + { + mFadeWeight = 0.f; + } +} + +//----------------------------------------------------------------------------- +// fadeIn() +//----------------------------------------------------------------------------- +void LLMotion::fadeIn() +{ + if (mFadeWeight < 0.99f) + { + mFadeWeight = lerp(mFadeWeight, 1.f, LLCriticalDamp::getInterpolant(0.15f)); + } + else + { + mFadeWeight = 1.f; + } +} + +//----------------------------------------------------------------------------- +// addJointState() +//----------------------------------------------------------------------------- +void LLMotion::addJointState(LLJointState* jointState) +{ + mPose.addJointState(jointState); + S32 priority = jointState->getPriority(); + if (priority == LLJoint::USE_MOTION_PRIORITY) + { + priority = getPriority(); + } + + U32 usage = jointState->getUsage(); + + // for now, usage is everything + mJointSignature[0][jointState->getJoint()->getJointNum()] = (usage & LLJointState::POS) ? (0xff >> (7 - priority)) : 0; + mJointSignature[1][jointState->getJoint()->getJointNum()] = (usage & LLJointState::ROT) ? (0xff >> (7 - priority)) : 0; + mJointSignature[2][jointState->getJoint()->getJointNum()] = (usage & LLJointState::SCALE) ? (0xff >> (7 - priority)) : 0; +} + +void LLMotion::setDeactivateCallback( void (*cb)(void *), void* userdata ) +{ + mDeactivateCallback = cb; + mDeactivateCallbackUserData = userdata; +} + +//----------------------------------------------------------------------------- +// activate() +//----------------------------------------------------------------------------- +void LLMotion::activate() +{ + mStopped = FALSE; + mActive = TRUE; + onActivate(); +} + +//----------------------------------------------------------------------------- +// deactivate() +//----------------------------------------------------------------------------- +void LLMotion::deactivate() +{ + mActive = FALSE; + + if (mDeactivateCallback) (*mDeactivateCallback)(mDeactivateCallbackUserData); + + onDeactivate(); +} + +// End diff --git a/indra/llcharacter/llmotion.h b/indra/llcharacter/llmotion.h new file mode 100644 index 0000000000..66882a2c11 --- /dev/null +++ b/indra/llcharacter/llmotion.h @@ -0,0 +1,237 @@ +/** + * @file llmotion.h + * @brief Implementation of LLMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMOTION_H +#define LL_LLMOTION_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include + +#include "llerror.h" +#include "llpose.h" +#include "lluuid.h" + +class LLCharacter; + +//----------------------------------------------------------------------------- +// class LLMotion +//----------------------------------------------------------------------------- +class LLMotion +{ +public: + enum LLMotionBlendType + { + NORMAL_BLEND, + ADDITIVE_BLEND + }; + + enum LLMotionInitStatus + { + STATUS_FAILURE, + STATUS_SUCCESS, + STATUS_HOLD + }; + + // Constructor + LLMotion(const LLUUID &id); + + // Destructor + virtual ~LLMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return NULL; } + + // get the name of this instance + const std::string &getName() const { return mName; } + + // set the name of this instance + void setName(const std::string &name) { mName = name; } + + const LLUUID& getID() const { return mID; } + + // returns the pose associated with the current state of this motion + virtual LLPose* getPose() { return &mPose;} + + void fadeOut(); + + void fadeIn(); + + F32 getFadeWeight() const { return mFadeWeight; } + + F32 getStopTime() const { return mStopTimestamp; } + + virtual void setStopTime(F32 time) { mStopTimestamp = time; mStopped = TRUE; } + + BOOL isStopped() const { return mStopped; } + + void setStopped(BOOL stopped) { mStopped = stopped; } + + void activate(); + + void deactivate(); + + BOOL isActive() { return mActive; } + + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual BOOL getLoop() = 0; + + // motions must report their total duration + virtual F32 getDuration() = 0; + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() = 0; + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() = 0; + + // motions must report their priority level + virtual LLJoint::JointPriority getPriority() = 0; + + // motions must report their blend type + virtual LLMotionBlendType getBlendType() = 0; + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() = 0; + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character) = 0; + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + virtual BOOL onUpdate(F32 activeTime, U8* joint_mask) = 0; + + // called when a motion is deactivated + virtual void onDeactivate() = 0; + + // optional callback routine called when animation deactivated. + void setDeactivateCallback( void (*cb)(void *), void* userdata ); + +protected: + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + virtual BOOL onActivate() = 0; + + void addJointState(LLJointState* jointState); + +protected: + LLPose mPose; + BOOL mStopped; // motion has been stopped; + BOOL mActive; // motion is on active list (can be stopped or not stopped) + +public: + //------------------------------------------------------------------------- + // these are set implicitly by the motion controller and + // may be referenced (read only) in the above handlers. + //------------------------------------------------------------------------- + std::string mName; // instance name assigned by motion controller + LLUUID mID; + + F32 mActivationTimestamp; // time when motion was activated + F32 mStopTimestamp; // time when motion was told to stop + F32 mSendStopTimestamp; // time when simulator should be told to stop this motion + F32 mResidualWeight; // blend weight at beginning of stop motion phase + F32 mFadeWeight; // for fading in and out based on LOD + U8 mJointSignature[3][LL_CHARACTER_MAX_JOINTS]; // signature of which joints are animated at what priority + void (*mDeactivateCallback)(void* data); + void* mDeactivateCallbackUserData; +}; + + +//----------------------------------------------------------------------------- +// LLTestMotion +//----------------------------------------------------------------------------- +class LLTestMotion : public LLMotion +{ +public: + LLTestMotion(const LLUUID &id) : LLMotion(id){} + ~LLTestMotion() {} + static LLMotion *create(const LLUUID& id) { return new LLTestMotion(id); } + BOOL getLoop() { return FALSE; } + F32 getDuration() { return 0.0f; } + F32 getEaseInDuration() { return 0.0f; } + F32 getEaseOutDuration() { return 0.0f; } + LLJoint::JointPriority getPriority() { return LLJoint::HIGH_PRIORITY; } + LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + F32 getMinPixelArea() { return 0.f; } + + LLMotionInitStatus onInitialize(LLCharacter*) { llinfos << "LLTestMotion::onInitialize()" << llendl; return STATUS_SUCCESS; } + BOOL onActivate() { llinfos << "LLTestMotion::onActivate()" << llendl; return TRUE; } + BOOL onUpdate(F32 time, U8* joint_mask) { llinfos << "LLTestMotion::onUpdate(" << time << ")" << llendl; return TRUE; } + void onDeactivate() { llinfos << "LLTestMotion::onDeactivate()" << llendl; } +}; + + +//----------------------------------------------------------------------------- +// LLNullMotion +//----------------------------------------------------------------------------- +class LLNullMotion : public LLMotion +{ +public: + LLNullMotion(const LLUUID &id) : LLMotion(id) {} + ~LLNullMotion() {} + static LLMotion *create(const LLUUID &id) { return new LLNullMotion(id); } + + // motions must specify whether or not they loop + /*virtual*/ BOOL getLoop() { return TRUE; } + + // motions must report their total duration + /*virtual*/ F32 getDuration() { return 1.f; } + + // motions must report their "ease in" duration + /*virtual*/ F32 getEaseInDuration() { return 0.f; } + + // motions must report their "ease out" duration. + /*virtual*/ F32 getEaseOutDuration() { return 0.f; } + + // motions must report their priority level + /*virtual*/ LLJoint::JointPriority getPriority() { return LLJoint::HIGH_PRIORITY; } + + // motions must report their blend type + /*virtual*/ LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + /*virtual*/ F32 getMinPixelArea() { return 0.f; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + /*virtual*/ LLMotionInitStatus onInitialize(LLCharacter *character) { return STATUS_SUCCESS; } + + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + /*virtual*/ BOOL onActivate() { return TRUE; } + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + /*virtual*/ BOOL onUpdate(F32 activeTime, U8* joint_mask) { return TRUE; } + + // called when a motion is deactivated + /*virtual*/ void onDeactivate() {} +}; +#endif // LL_LLMOTION_H + diff --git a/indra/llcharacter/llmotioncontroller.cpp b/indra/llcharacter/llmotioncontroller.cpp new file mode 100644 index 0000000000..7ec67b5fd4 --- /dev/null +++ b/indra/llcharacter/llmotioncontroller.cpp @@ -0,0 +1,927 @@ +/** + * @file llmotioncontroller.cpp + * @brief Implementation of LLMotionController class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llmotioncontroller.h" +#include "llkeyframemotion.h" +#include "llmath.h" +#include "lltimer.h" +#include "llanimationstates.h" +#include "llstl.h" + +const S32 NUM_JOINT_SIGNATURE_STRIDES = LL_CHARACTER_MAX_JOINTS / 4; +const U32 MAX_MOTION_INSTANCES = 32; + +//----------------------------------------------------------------------------- +// Constants and statics +//----------------------------------------------------------------------------- +LLMotionRegistry LLMotionController::sRegistry; + +//----------------------------------------------------------------------------- +// LLMotionTableEntry() +//----------------------------------------------------------------------------- +LLMotionTableEntry::LLMotionTableEntry() +{ + mConstructor = NULL; + mID.setNull(); +} + +LLMotionTableEntry::LLMotionTableEntry(LLMotionConstructor constructor, const LLUUID& id) + : mConstructor(constructor), mID(id) +{ + +} + +//----------------------------------------------------------------------------- +// create() +//----------------------------------------------------------------------------- +LLMotion* LLMotionTableEntry::create(const LLUUID &id) +{ + LLMotion* motionp = mConstructor(id); + + return motionp; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// LLMotionRegistry class +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// LLMotionRegistry() +// Class Constructor +//----------------------------------------------------------------------------- +LLMotionRegistry::LLMotionRegistry() : mMotionTable(LLMotionTableEntry::uuidEq, LLMotionTableEntry()) +{ + +} + + +//----------------------------------------------------------------------------- +// ~LLMotionRegistry() +// Class Destructor +//----------------------------------------------------------------------------- +LLMotionRegistry::~LLMotionRegistry() +{ + mMotionTable.removeAll(); +} + + +//----------------------------------------------------------------------------- +// addMotion() +//----------------------------------------------------------------------------- +BOOL LLMotionRegistry::addMotion( const LLUUID& id, LLMotionConstructor constructor ) +{ +// llinfos << "Registering motion: " << name << llendl; + if (!mMotionTable.check(id)) + { + mMotionTable.set(id, LLMotionTableEntry(constructor, id)); + return TRUE; + } + + return FALSE; +} + +//----------------------------------------------------------------------------- +// markBad() +//----------------------------------------------------------------------------- +void LLMotionRegistry::markBad( const LLUUID& id ) +{ + mMotionTable.set(id, LLMotionTableEntry()); +} + +//----------------------------------------------------------------------------- +// createMotion() +//----------------------------------------------------------------------------- +LLMotion *LLMotionRegistry::createMotion( const LLUUID &id ) +{ + LLMotionTableEntry motion_entry = mMotionTable.get(id); + LLMotion* motion = NULL; + + if ( motion_entry.getID().isNull() ) + { + //FIXME - RN: need to replace with a better default scheme + motion = LLKeyframeMotion::create(id); + } + else + { + motion = motion_entry.create(id); + } + + return motion; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// LLMotionController class +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// LLMotionController() +// Class Constructor +//----------------------------------------------------------------------------- +LLMotionController::LLMotionController( ) +{ + mTime = 0.f; + mTimeOffset = 0.f; + mLastTime = 0.0f; + mHasRunOnce = FALSE; + mPaused = FALSE; + mPauseTime = 0.f; + mTimeStep = 0.f; + mTimeStepCount = 0; + mLastInterp = 0.f; + mTimeFactor = 1.f; +} + + +//----------------------------------------------------------------------------- +// ~LLMotionController() +// Class Destructor +//----------------------------------------------------------------------------- +LLMotionController::~LLMotionController() +{ + deleteAllMotions(); +} + +//----------------------------------------------------------------------------- +// deleteAllMotions() +//----------------------------------------------------------------------------- +void LLMotionController::deleteAllMotions() +{ + mLoadingMotions.removeAllNodes(); + mLoadedMotions.clear(); + mActiveMotions.removeAllNodes(); + + for_each(mAllMotions.begin(), mAllMotions.end(), DeletePairedPointer()); + mAllMotions.clear(); +} + +//----------------------------------------------------------------------------- +// addLoadedMotion() +//----------------------------------------------------------------------------- +void LLMotionController::addLoadedMotion(LLMotion* motionp) +{ + if (mLoadedMotions.size() > MAX_MOTION_INSTANCES) + { + // too many motions active this frame, kill all blenders + mPoseBlender.clearBlenders(); + + for (U32 i = 0; i < mLoadedMotions.size(); i++) + { + LLMotion* cur_motionp = mLoadedMotions.front(); + mLoadedMotions.pop_front(); + + // motion isn't playing, delete it + if (!isMotionActive(cur_motionp)) + { + mCharacter->requestStopMotion(cur_motionp); + mAllMotions.erase(cur_motionp->getID()); + delete cur_motionp; + if (mLoadedMotions.size() <= MAX_MOTION_INSTANCES) + { + break; + } + } + else + { + // put active motion on back + mLoadedMotions.push_back(cur_motionp); + } + } + } + mLoadedMotions.push_back(motionp); +} + +//----------------------------------------------------------------------------- +// setTimeStep() +//----------------------------------------------------------------------------- +void LLMotionController::setTimeStep(F32 step) +{ + mTimeStep = step; + + if (step != 0.f) + { + // make sure timestamps conform to new quantum + for( LLMotion* motionp = mActiveMotions.getFirstData(); + motionp != NULL; + motionp = mActiveMotions.getNextData() ) + { + motionp->mActivationTimestamp = (F32)llfloor(motionp->mActivationTimestamp / step) * step; + BOOL stopped = motionp->isStopped(); + motionp->setStopTime((F32)llfloor(motionp->getStopTime() / step) * step); + motionp->setStopped(stopped); + motionp->mSendStopTimestamp = (F32)llfloor(motionp->mSendStopTimestamp / step) * step; + } + } +} + +//----------------------------------------------------------------------------- +// setTimeFactor() +//----------------------------------------------------------------------------- +void LLMotionController::setTimeFactor(F32 time_factor) +{ + mTimeOffset += mTimer.getElapsedTimeAndResetF32() * mTimeFactor; + mTimeFactor = time_factor; +} + +//----------------------------------------------------------------------------- +// getFirstActiveMotion() +//----------------------------------------------------------------------------- +LLMotion* LLMotionController::getFirstActiveMotion() +{ + return mActiveMotions.getFirstData(); +} + +//----------------------------------------------------------------------------- +// getNextActiveMotion() +//----------------------------------------------------------------------------- +LLMotion* LLMotionController::getNextActiveMotion() +{ + return mActiveMotions.getNextData(); +} + + +//----------------------------------------------------------------------------- +// setCharacter() +//----------------------------------------------------------------------------- +void LLMotionController::setCharacter(LLCharacter *character) +{ + mCharacter = character; +} + + +//----------------------------------------------------------------------------- +// addMotion() +//----------------------------------------------------------------------------- +BOOL LLMotionController::addMotion( const LLUUID& id, LLMotionConstructor constructor ) +{ + return sRegistry.addMotion(id, constructor); +} + +//----------------------------------------------------------------------------- +// removeMotion() +//----------------------------------------------------------------------------- +void LLMotionController::removeMotion( const LLUUID& id) +{ + LLMotion* motionp = findMotion(id); + if (motionp) + { + stopMotionLocally(id, TRUE); + + mLoadingMotions.deleteData(motionp); + std::deque::iterator motion_it; + for (motion_it = mLoadedMotions.begin(); motion_it != mLoadedMotions.end(); ++motion_it) + { + if(*motion_it == motionp) + { + mLoadedMotions.erase(motion_it); + break; + } + } + mActiveMotions.deleteData(motionp); + mAllMotions.erase(id); + delete motionp; + } +} + +//----------------------------------------------------------------------------- +// createMotion() +//----------------------------------------------------------------------------- +LLMotion* LLMotionController::createMotion( const LLUUID &id ) +{ + // do we have an instance of this motion for this character? + LLMotion *motion = findMotion(id); + + // if not, we need to create one + if (!motion) + { + // look up constructor and create it + motion = sRegistry.createMotion(id); + if (!motion) + { + return NULL; + } + + // look up name for default motions + const char* motion_name = gAnimLibrary.animStateToString(id); + if (motion_name) + { + motion->setName(motion_name); + } + + // initialize the new instance + LLMotion::LLMotionInitStatus stat = motion->onInitialize(mCharacter); + switch(stat) + { + case LLMotion::STATUS_FAILURE: + llinfos << "Motion " << id << " init failed." << llendl; + sRegistry.markBad(id); + delete motion; + return NULL; + case LLMotion::STATUS_HOLD: + mLoadingMotions.addData(motion); + break; + case LLMotion::STATUS_SUCCESS: + // add motion to our list + addLoadedMotion(motion); + break; + default: + llerrs << "Invalid initialization status" << llendl; + break; + } + + mAllMotions[id] = motion; + } + return motion; +} + +//----------------------------------------------------------------------------- +// startMotion() +//----------------------------------------------------------------------------- +BOOL LLMotionController::startMotion(const LLUUID &id, F32 start_offset) +{ + // look for motion in our list of created motions + LLMotion *motion = createMotion(id); + + if (!motion) + { + return FALSE; + } + //if the motion is already active, then we're done + else if (isMotionActive(motion)) // motion is playing and... + { + if (motion->isStopped()) // motion has been stopped + { + deactivateMotion(motion); + } + else if (mTime < motion->mSendStopTimestamp) // motion is still active + { + return TRUE; + } + } + +// llinfos << "Starting motion " << name << llendl; + return activateMotion(motion, mTime - start_offset); +} + + +//----------------------------------------------------------------------------- +// stopMotionLocally() +//----------------------------------------------------------------------------- +BOOL LLMotionController::stopMotionLocally(const LLUUID &id, BOOL stop_immediate) +{ + // if already inactive, return false + LLMotion *motion = findMotion(id); + if (!motion) + { + return FALSE; + } + + // If on active list, stop it + if (isMotionActive(motion) && !motion->isStopped()) + { + // when using timesteps, set stop time to last frame's time, otherwise grab current timer value + // FIXME: should investigate this inconsistency...hints of obscure bugs + + F32 stop_time = (mTimeStep != 0.f || mPaused) ? (mTime) : mTimeOffset + (mTimer.getElapsedTimeF32() * mTimeFactor); + motion->setStopTime(stop_time); + + if (stop_immediate) + { + deactivateMotion(motion); + } + return TRUE; + } + else if (isMotionLoading(motion)) + { + motion->setStopped(TRUE); + return TRUE; + } + + return FALSE; +} + + +//----------------------------------------------------------------------------- +// updateRegularMotions() +//----------------------------------------------------------------------------- +void LLMotionController::updateRegularMotions() +{ + updateMotionsByType(LLMotion::NORMAL_BLEND); +} + +//----------------------------------------------------------------------------- +// updateAdditiveMotions() +//----------------------------------------------------------------------------- +void LLMotionController::updateAdditiveMotions() +{ + updateMotionsByType(LLMotion::ADDITIVE_BLEND); +} + +//----------------------------------------------------------------------------- +// resetJointSignatures() +//----------------------------------------------------------------------------- +void LLMotionController::resetJointSignatures() +{ + memset(&mJointSignature[0][0], 0, sizeof(U8) * LL_CHARACTER_MAX_JOINTS); + memset(&mJointSignature[1][0], 0, sizeof(U8) * LL_CHARACTER_MAX_JOINTS); +} + +//----------------------------------------------------------------------------- +// updateMotionsByType() +//----------------------------------------------------------------------------- +void LLMotionController::updateMotionsByType(LLMotion::LLMotionBlendType anim_type) +{ + BOOL update_result = TRUE; + U8 last_joint_signature[LL_CHARACTER_MAX_JOINTS]; + + memset(&last_joint_signature, 0, sizeof(U8) * LL_CHARACTER_MAX_JOINTS); + + // iterate through active motions in chronological order + for(LLMotion* motionp = mActiveMotions.getFirstData(); + motionp != NULL; + motionp = mActiveMotions.getNextData()) + { + if (motionp->getBlendType() != anim_type) + { + continue; + } + + BOOL update_motion = FALSE; + + if (motionp->getPose()->getWeight() < 1.f) + { + update_motion = TRUE; + } + else + { + S32 i; + // NUM_JOINT_SIGNATURE_STRIDES should be multiple of 4 + for (i = 0; i < NUM_JOINT_SIGNATURE_STRIDES; i++) + { + U32 *current_signature = (U32*)&(mJointSignature[0][i * 4]); + U32 test_signature = *(U32*)&(motionp->mJointSignature[0][i * 4]); + + if ((*current_signature | test_signature) > (*current_signature)) + { + *current_signature |= test_signature; + update_motion = TRUE; + } + + *((U32*)&last_joint_signature[i * 4]) = *(U32*)&(mJointSignature[1][i * 4]); + current_signature = (U32*)&(mJointSignature[1][i * 4]); + test_signature = *(U32*)&(motionp->mJointSignature[1][i * 4]); + + if ((*current_signature | test_signature) > (*current_signature)) + { + *current_signature |= test_signature; + update_motion = TRUE; + } + } + } + + if (!update_motion) + { + if (motionp->isStopped() && mTime > motionp->getStopTime() + motionp->getEaseOutDuration()) + { + deactivateMotion(motionp); + } + else if (motionp->isStopped() && mTime > motionp->getStopTime()) + { + // is this the first iteration in the ease out phase? + if (mLastTime <= motionp->getStopTime()) + { + // store residual weight for this motion + motionp->mResidualWeight = motionp->getPose()->getWeight(); + } + } + else if (mTime > motionp->mSendStopTimestamp) + { + // notify character of timed stop event on first iteration past sendstoptimestamp + // this will only be called when an animation stops itself (runs out of time) + if (mLastTime <= motionp->mSendStopTimestamp) + { + mCharacter->requestStopMotion( motionp ); + stopMotionLocally(motionp->getID(), FALSE); + } + } + else if (mTime >= motionp->mActivationTimestamp) + { + if (mLastTime < motionp->mActivationTimestamp) + { + motionp->mResidualWeight = motionp->getPose()->getWeight(); + } + } + continue; + } + + LLPose *posep = motionp->getPose(); + + // only filter by LOD after running every animation at least once (to prime the avatar state) + if (mHasRunOnce && motionp->getMinPixelArea() > mCharacter->getPixelArea()) + { + motionp->fadeOut(); + + //should we notify the simulator that this motion should be stopped (check even if skipped by LOD logic) + if (mTime > motionp->mSendStopTimestamp) + { + // notify character of timed stop event on first iteration past sendstoptimestamp + // this will only be called when an animation stops itself (runs out of time) + if (mLastTime <= motionp->mSendStopTimestamp) + { + mCharacter->requestStopMotion( motionp ); + stopMotionLocally(motionp->getID(), FALSE); + } + } + + if (motionp->getFadeWeight() < 0.01f) + { + if (motionp->isStopped() && mTime > motionp->getStopTime() + motionp->getEaseOutDuration()) + { + posep->setWeight(0.f); + deactivateMotion(motionp); + } + continue; + } + } + else + { + motionp->fadeIn(); + } + + //********************** + // MOTION INACTIVE + //********************** + if (motionp->isStopped() && mTime > motionp->getStopTime() + motionp->getEaseOutDuration()) + { + // this motion has gone on too long, deactivate it + // did we have a chance to stop it? + if (mLastTime <= motionp->getStopTime()) + { + // if not, let's stop it this time through and deactivate it the next + + posep->setWeight(motionp->getFadeWeight()); + motionp->onUpdate(motionp->getStopTime() - motionp->mActivationTimestamp, last_joint_signature); + } + else + { + posep->setWeight(0.f); + deactivateMotion(motionp); + continue; + } + } + + //********************** + // MOTION EASE OUT + //********************** + else if (motionp->isStopped() && mTime > motionp->getStopTime()) + { + // is this the first iteration in the ease out phase? + if (mLastTime <= motionp->getStopTime()) + { + // store residual weight for this motion + motionp->mResidualWeight = motionp->getPose()->getWeight(); + } + + if (motionp->getEaseOutDuration() == 0.f) + { + posep->setWeight(0.f); + } + else + { + posep->setWeight(motionp->getFadeWeight() * motionp->mResidualWeight * cubic_step(1.f - ((mTime - motionp->getStopTime()) / motionp->getEaseOutDuration()))); + } + + // perform motion update + update_result = motionp->onUpdate(mTime - motionp->mActivationTimestamp, last_joint_signature); + } + + //********************** + // MOTION ACTIVE + //********************** + else if (mTime > motionp->mActivationTimestamp + motionp->getEaseInDuration()) + { + posep->setWeight(motionp->getFadeWeight()); + + //should we notify the simulator that this motion should be stopped? + if (mTime > motionp->mSendStopTimestamp) + { + // notify character of timed stop event on first iteration past sendstoptimestamp + // this will only be called when an animation stops itself (runs out of time) + if (mLastTime <= motionp->mSendStopTimestamp) + { + mCharacter->requestStopMotion( motionp ); + stopMotionLocally(motionp->getID(), FALSE); + } + } + + // perform motion update + update_result = motionp->onUpdate(mTime - motionp->mActivationTimestamp, last_joint_signature); + } + + //********************** + // MOTION EASE IN + //********************** + else if (mTime >= motionp->mActivationTimestamp) + { + if (mLastTime < motionp->mActivationTimestamp) + { + motionp->mResidualWeight = motionp->getPose()->getWeight(); + } + if (motionp->getEaseInDuration() == 0.f) + { + posep->setWeight(motionp->getFadeWeight()); + } + else + { + // perform motion update + posep->setWeight(motionp->getFadeWeight() * motionp->mResidualWeight + (1.f - motionp->mResidualWeight) * cubic_step((mTime - motionp->mActivationTimestamp) / motionp->getEaseInDuration())); + } + // perform motion update + update_result = motionp->onUpdate(mTime - motionp->mActivationTimestamp, last_joint_signature); + } + else + { + posep->setWeight(0.f); + update_result = motionp->onUpdate(0.f, last_joint_signature); + } + + // allow motions to deactivate themselves + if (!update_result) + { + if (!motionp->isStopped() || motionp->getStopTime() > mTime) + { + // animation has stopped itself due to internal logic + // propagate this to the network + // as not all viewers are guaranteed to have access to the same logic + mCharacter->requestStopMotion( motionp ); + stopMotionLocally(motionp->getID(), FALSE); + } + + } + + // even if onupdate returns FALSE, add this motion in to the blend one last time + mPoseBlender.addMotion(motionp); + } +} + + +//----------------------------------------------------------------------------- +// updateMotion() +//----------------------------------------------------------------------------- +void LLMotionController::updateMotion() +{ + BOOL use_quantum = (mTimeStep != 0.f); + + // Update timing info for this time step. + if (!mPaused) + { + F32 update_time = mTimeOffset + (mTimer.getElapsedTimeF32() * mTimeFactor); + if (use_quantum) + { + F32 time_interval = fmodf(update_time, mTimeStep); + + // always animate *ahead* of actual time + S32 quantum_count = llmax(0, llfloor((update_time - time_interval) / mTimeStep)) + 1; + if (quantum_count == mTimeStepCount) + { + // we're still in same time quantum as before, so just interpolate and exit + if (!mPaused) + { + F32 interp = time_interval / mTimeStep; + mPoseBlender.interpolate(interp - mLastInterp); + mLastInterp = interp; + } + + return; + } + + // is calculating a new keyframe pose, make sure the last one gets applied + mPoseBlender.interpolate(1.f); + mPoseBlender.clearBlenders(); + + mTimeStepCount = quantum_count; + mLastTime = mTime; + mTime = (F32)quantum_count * mTimeStep; + mLastInterp = 0.f; + } + else + { + mLastTime = mTime; + mTime = update_time; + } + } + + // query pending motions for completion + LLMotion* motionp; + + for ( motionp = mLoadingMotions.getFirstData(); + motionp != NULL; + motionp = mLoadingMotions.getNextData() ) + { + LLMotion::LLMotionInitStatus status = motionp->onInitialize(mCharacter); + if (status == LLMotion::STATUS_SUCCESS) + { + mLoadingMotions.removeCurrentData(); + // add motion to our loaded motion list + addLoadedMotion(motionp); + // this motion should be playing + if (!motionp->isStopped()) + { + activateMotion(motionp, mTime); + } + } + else if (status == LLMotion::STATUS_FAILURE) + { + llinfos << "Motion " << motionp->getID() << " init failed." << llendl; + sRegistry.markBad(motionp->getID()); + mLoadingMotions.removeCurrentData(); + mAllMotions.erase(motionp->getID()); + delete motionp; + } + } + + resetJointSignatures(); + + if (!mPaused) + { + // update additive motions + updateAdditiveMotions(); + resetJointSignatures(); + + // update all regular motions + updateRegularMotions(); + + if (use_quantum) + { + mPoseBlender.blendAndCache(TRUE); + } + else + { + mPoseBlender.blendAndApply(); + } + } + + mHasRunOnce = TRUE; +// llinfos << "Motion controller time " << motionTimer.getElapsedTimeF32() << llendl; +} + + +//----------------------------------------------------------------------------- +// activateMotion() +//----------------------------------------------------------------------------- +BOOL LLMotionController::activateMotion(LLMotion *motion, F32 time) +{ + if (mLoadingMotions.checkData(motion)) + { + // we want to start this motion, but we can't yet, so flag it as started + motion->setStopped(FALSE); + // report pending animations as activated + return TRUE; + } + + motion->mResidualWeight = motion->getPose()->getWeight(); + motion->mActivationTimestamp = time; + + // set stop time based on given duration and ease out time + if (motion->getDuration() != 0.f && !motion->getLoop()) + { + F32 ease_out_time; + F32 motion_duration; + + // should we stop at the end of motion duration, or a bit earlier + // to allow it to ease out while moving? + ease_out_time = motion->getEaseOutDuration(); + + // is the clock running when the motion is easing in? + // if not (POSTURE_EASE) then we need to wait that much longer before triggering the stop + motion_duration = llmax(motion->getDuration() - ease_out_time, 0.f); + motion->mSendStopTimestamp = time + motion_duration; + } + else + { + motion->mSendStopTimestamp = F32_MAX; + } + + mActiveMotions.addData(motion); + + motion->activate(); + motion->onUpdate(0.f, mJointSignature[1]); + + return TRUE; +} + +//----------------------------------------------------------------------------- +// deactivateMotion() +//----------------------------------------------------------------------------- +BOOL LLMotionController::deactivateMotion(LLMotion *motion) +{ + motion->deactivate(); + mActiveMotions.removeData(motion); + + return TRUE; +} + +//----------------------------------------------------------------------------- +// isMotionActive() +//----------------------------------------------------------------------------- +BOOL LLMotionController::isMotionActive(LLMotion *motion) +{ + if (motion && motion->isActive()) + { + return TRUE; + } + + return FALSE; +} + +//----------------------------------------------------------------------------- +// isMotionLoading() +//----------------------------------------------------------------------------- +BOOL LLMotionController::isMotionLoading(LLMotion* motion) +{ + return mLoadingMotions.checkData(motion); +} + + +//----------------------------------------------------------------------------- +// findMotion() +//----------------------------------------------------------------------------- +LLMotion *LLMotionController::findMotion(const LLUUID& id) +{ + return mAllMotions[id]; +} + + +//----------------------------------------------------------------------------- +// flushAllMotions() +//----------------------------------------------------------------------------- +void LLMotionController::flushAllMotions() +{ + LLDynamicArray active_motions; + LLDynamicArray active_motion_times; + + for (LLMotion* motionp = mActiveMotions.getFirstData(); + motionp; + motionp = mActiveMotions.getNextData()) + { + active_motions.put(motionp->getID()); + active_motion_times.put(mTime - motionp->mActivationTimestamp); + motionp->deactivate(); + } + + // delete all motion instances + deleteAllMotions(); + + // kill current hand pose that was previously called out by + // keyframe motion + mCharacter->removeAnimationData("Hand Pose"); + + // restart motions + for (S32 i = 0; i < active_motions.count(); i++) + { + startMotion(active_motions[i], active_motion_times[i]); + } +} + +//----------------------------------------------------------------------------- +// pause() +//----------------------------------------------------------------------------- +void LLMotionController::pause() +{ + if (!mPaused) + { + //llinfos << "Pausing animations..." << llendl; + mPauseTime = mTimer.getElapsedTimeF32(); + mPaused = TRUE; + } + +} + +//----------------------------------------------------------------------------- +// unpause() +//----------------------------------------------------------------------------- +void LLMotionController::unpause() +{ + if (mPaused) + { + //llinfos << "Unpausing animations..." << llendl; + mTimer.reset(); + mTimer.setAge(mPauseTime); + mPaused = FALSE; + } +} +// End diff --git a/indra/llcharacter/llmotioncontroller.h b/indra/llcharacter/llmotioncontroller.h new file mode 100644 index 0000000000..d43d6d9a8f --- /dev/null +++ b/indra/llcharacter/llmotioncontroller.h @@ -0,0 +1,207 @@ +/** + * @file llmotioncontroller.h + * @brief Implementation of LLMotionController class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMOTIONCONTROLLER_H +#define LL_LLMOTIONCONTROLLER_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include +#include +#include + +#include "linked_lists.h" +#include "lluuidhashmap.h" +#include "llmotion.h" +#include "llpose.h" +#include "llframetimer.h" +#include "llstatemachine.h" +#include "llstring.h" + +//----------------------------------------------------------------------------- +// Class predeclaration +// This is necessary because llcharacter.h includes this file. +//----------------------------------------------------------------------------- +class LLCharacter; + +//----------------------------------------------------------------------------- +// LLMotionRegistry +//----------------------------------------------------------------------------- +typedef LLMotion*(*LLMotionConstructor)(const LLUUID &id); + +class LLMotionTableEntry +{ +public: + LLMotionTableEntry(); + LLMotionTableEntry(LLMotionConstructor constructor, const LLUUID& id); + ~LLMotionTableEntry(){}; + + LLMotion* create(const LLUUID& id); + static BOOL uuidEq(const LLUUID &uuid, const LLMotionTableEntry &id_pair) + { + if (uuid == id_pair.mID) + { + return TRUE; + } + return FALSE; + } + + const LLUUID& getID() { return mID; } + +protected: + LLMotionConstructor mConstructor; + LLUUID mID; +}; + +class LLMotionRegistry +{ +public: + // Constructor + LLMotionRegistry(); + + // Destructor + ~LLMotionRegistry(); + + // adds motion classes to the registry + // returns true if successfull + BOOL addMotion( const LLUUID& id, LLMotionConstructor create); + + // creates a new instance of a named motion + // returns NULL motion is not registered + LLMotion *createMotion( const LLUUID &id ); + + // initialization of motion failed, don't try to create this motion again + void markBad( const LLUUID& id ); + + +protected: + LLUUIDHashMap mMotionTable; +}; + +//----------------------------------------------------------------------------- +// class LLMotionController +//----------------------------------------------------------------------------- +class LLMotionController +{ +public: + // Constructor + LLMotionController(); + + // Destructor + virtual ~LLMotionController(); + + // set associated character + // this must be called exactly once by the containing character class. + // this is generally done in the Character constructor + void setCharacter( LLCharacter *character ); + + // registers a motion with the controller + // (actually just forwards call to motion registry) + // returns true if successfull + BOOL addMotion( const LLUUID& id, LLMotionConstructor create ); + + // creates a motion from the registry + LLMotion *createMotion( const LLUUID &id ); + + // unregisters a motion with the controller + // (actually just forwards call to motion registry) + // returns true if successfull + void removeMotion( const LLUUID& id ); + + // start motion + // begins playing the specified motion + // returns true if successful + BOOL startMotion( const LLUUID &id, F32 start_offset ); + + // stop motion + // stops a playing motion + // in reality, it begins the ease out transition phase + // returns true if successful + BOOL stopMotionLocally( const LLUUID &id, BOOL stop_immediate ); + + // update motions + // invokes the update handlers for each active motion + // activates sequenced motions + // deactivates terminated motions` + void updateMotion(); + + // flush motions + // releases all motion instances + void flushAllMotions(); + + // pause and continue all motions + void pause(); + void unpause(); + BOOL isPaused() { return mPaused; } + + void setTimeStep(F32 step); + + void setTimeFactor(F32 time_factor); + F32 getTimeFactor() { return mTimeFactor; } + + LLMotion* getFirstActiveMotion(); + LLMotion* getNextActiveMotion(); + +//protected: + BOOL isMotionActive( LLMotion *motion ); + BOOL isMotionLoading( LLMotion *motion ); + LLMotion *findMotion( const LLUUID& id ); + +protected: + void deleteAllMotions(); + void addLoadedMotion(LLMotion *motion); + BOOL activateMotion(LLMotion *motion, F32 time); + BOOL deactivateMotion(LLMotion *motion); + void updateRegularMotions(); + void updateAdditiveMotions(); + void resetJointSignatures(); + void updateMotionsByType(LLMotion::LLMotionBlendType motion_type); +protected: + + F32 mTimeFactor; + static LLMotionRegistry sRegistry; + LLPoseBlender mPoseBlender; + + LLCharacter *mCharacter; + +// Life cycle of an animation: +// +// Animations are instantiated and immediately put in the mAllMotions map for their entire lifetime. +// If the animations depend on any asset data, the appropriate data is fetched from the data server, +// and the animation is put on the mLoadingMotions list. +// Once an animations is loaded, it will be initialized and put on the mLoadedMotions deque. +// Any animation that is currently playing also sits in the mActiveMotions list. + + std::map mAllMotions; + + LLLinkedList mLoadingMotions; + std::deque mLoadedMotions; + LLLinkedList mActiveMotions; + + LLFrameTimer mTimer; + F32 mTime; + F32 mTimeOffset; + F32 mLastTime; + BOOL mHasRunOnce; + BOOL mPaused; + F32 mTimeStep; + S32 mTimeStepCount; + F32 mLastInterp; + F32 mPauseTime; + + U8 mJointSignature[2][LL_CHARACTER_MAX_JOINTS]; +}; + +//----------------------------------------------------------------------------- +// Class declaractions +//----------------------------------------------------------------------------- +#include "llcharacter.h" + +#endif // LL_LLMOTIONCONTROLLER_H + diff --git a/indra/llcharacter/llmultigesture.cpp b/indra/llcharacter/llmultigesture.cpp new file mode 100644 index 0000000000..1e42352d74 --- /dev/null +++ b/indra/llcharacter/llmultigesture.cpp @@ -0,0 +1,478 @@ +/** + * @file llmultigesture.cpp + * @brief Gestures that are asset-based and can have multiple steps. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include + +#include "stdio.h" + +#include "llmultigesture.h" + +#include "llerror.h" +#include "lldatapacker.h" +#include "llstl.h" + +const S32 GESTURE_VERSION = 2; + +//--------------------------------------------------------------------------- +// LLMultiGesture +//--------------------------------------------------------------------------- +LLMultiGesture::LLMultiGesture() +: mKey(), + mMask(), + mTrigger(), + mReplaceText(), + mSteps(), + mPlaying(FALSE), + mCurrentStep(0), + mDoneCallback(NULL), + mCallbackData(NULL) +{ + reset(); +} + +LLMultiGesture::~LLMultiGesture() +{ + std::for_each(mSteps.begin(), mSteps.end(), DeletePointer()); +} + +void LLMultiGesture::reset() +{ + mPlaying = FALSE; + mCurrentStep = 0; + mWaitTimer.reset(); + mWaitingTimer = FALSE; + mWaitingAnimations = FALSE; + mWaitingAtEnd = FALSE; + mRequestedAnimIDs.clear(); + mPlayingAnimIDs.clear(); +} + +S32 LLMultiGesture::getMaxSerialSize() const +{ + S32 max_size = 0; + + // ascii format, being very conservative about possible + // label lengths. + max_size += 64; // version S32 + max_size += 64; // key U8 + max_size += 64; // mask U32 + max_size += 256; // trigger string + max_size += 256; // replace string + + max_size += 64; // step count S32 + + std::vector::const_iterator it; + for (it = mSteps.begin(); it != mSteps.end(); ++it) + { + LLGestureStep* step = *it; + max_size += 64; // type S32 + max_size += step->getMaxSerialSize(); + } + + /* binary format + max_size += sizeof(S32); // version + max_size += sizeof(mKey); + max_size += sizeof(mMask); + max_size += mTrigger.length() + 1; // for null + + max_size += sizeof(S32); // step count + + std::vector::const_iterator it; + for (it = mSteps.begin(); it != mSteps.end(); ++it) + { + LLGestureStep* step = *it; + max_size += sizeof(S32); // type + max_size += step->getMaxSerialSize(); + } + */ + + return max_size; +} + +BOOL LLMultiGesture::serialize(LLDataPacker& dp) const +{ + dp.packS32(GESTURE_VERSION, "version"); + dp.packU8(mKey, "key"); + dp.packU32(mMask, "mask"); + dp.packString(mTrigger.c_str(), "trigger"); + dp.packString(mReplaceText.c_str(), "replace"); + + S32 count = (S32)mSteps.size(); + dp.packS32(count, "step_count"); + S32 i; + for (i = 0; i < count; ++i) + { + LLGestureStep* step = mSteps[i]; + + dp.packS32(step->getType(), "step_type"); + BOOL ok = step->serialize(dp); + if (!ok) + { + return FALSE; + } + } + return TRUE; +} + +BOOL LLMultiGesture::deserialize(LLDataPacker& dp) +{ + S32 version; + dp.unpackS32(version, "version"); + if (version != GESTURE_VERSION) + { + llwarns << "Bad LLMultiGesture version " << version + << " should be " << GESTURE_VERSION + << llendl; + return FALSE; + } + + dp.unpackU8(mKey, "key"); + dp.unpackU32(mMask, "mask"); + + char buffer[256]; /* Flawfinder: ignore */ + dp.unpackString(buffer, "trigger"); + mTrigger = buffer; + + dp.unpackString(buffer, "replace"); + mReplaceText = buffer; + + S32 count; + dp.unpackS32(count, "step_count"); + if (count < 0) + { + llwarns << "Bad LLMultiGesture step count " << count << llendl; + return FALSE; + } + + S32 i; + for (i = 0; i < count; ++i) + { + S32 type; + dp.unpackS32(type, "step_type"); + + EStepType step_type = (EStepType)type; + switch(step_type) + { + case STEP_ANIMATION: + { + LLGestureStepAnimation* step = new LLGestureStepAnimation(); + BOOL ok = step->deserialize(dp); + if (!ok) return FALSE; + mSteps.push_back(step); + break; + } + case STEP_SOUND: + { + LLGestureStepSound* step = new LLGestureStepSound(); + BOOL ok = step->deserialize(dp); + if (!ok) return FALSE; + mSteps.push_back(step); + break; + } + case STEP_CHAT: + { + LLGestureStepChat* step = new LLGestureStepChat(); + BOOL ok = step->deserialize(dp); + if (!ok) return FALSE; + mSteps.push_back(step); + break; + } + case STEP_WAIT: + { + LLGestureStepWait* step = new LLGestureStepWait(); + BOOL ok = step->deserialize(dp); + if (!ok) return FALSE; + mSteps.push_back(step); + break; + } + default: + { + llwarns << "Bad LLMultiGesture step type " << type << llendl; + return FALSE; + } + } + } + return TRUE; +} + +void LLMultiGesture::dump() +{ + llinfos << "key " << S32(mKey) << " mask " << U32(mMask) + << " trigger " << mTrigger + << " replace " << mReplaceText + << llendl; + U32 i; + for (i = 0; i < mSteps.size(); ++i) + { + LLGestureStep* step = mSteps[i]; + step->dump(); + } +} + +//--------------------------------------------------------------------------- +// LLGestureStepAnimation +//--------------------------------------------------------------------------- +LLGestureStepAnimation::LLGestureStepAnimation() +: LLGestureStep(), + mAnimName("None"), + mAnimAssetID(), + mFlags(0x0) +{ } + +LLGestureStepAnimation::~LLGestureStepAnimation() +{ } + +S32 LLGestureStepAnimation::getMaxSerialSize() const +{ + S32 max_size = 0; + + // ascii + max_size += 256; // anim name + max_size += 64; // anim asset id + max_size += 64; // flags + + /* binary + max_size += mAnimName.length() + 1; + max_size += sizeof(mAnimAssetID); + max_size += sizeof(mFlags); + */ + return max_size; +} + +BOOL LLGestureStepAnimation::serialize(LLDataPacker& dp) const +{ + dp.packString(mAnimName.c_str(), "anim_name"); + dp.packUUID(mAnimAssetID, "asset_id"); + dp.packU32(mFlags, "flags"); + return TRUE; +} + +BOOL LLGestureStepAnimation::deserialize(LLDataPacker& dp) +{ + char buffer[256]; /* Flawfinder: ignore */ + dp.unpackString(buffer, "anim_name"); + mAnimName = buffer; + + // Apparently an earlier version of the gesture code added \r to the end + // of the animation names. Get rid of it. JC + if (mAnimName[mAnimName.length() - 1] == '\r') + { + // chop the last character + mAnimName.resize(mAnimName.length() - 1); + } + + dp.unpackUUID(mAnimAssetID, "asset_id"); + dp.unpackU32(mFlags, "flags"); + return TRUE; +} + +std::string LLGestureStepAnimation::getLabel() const +{ + std::string label; + if (mFlags & ANIM_FLAG_STOP) + { + label = "Stop Animation: "; + } + else + { + label = "Start Animation: "; + } + label += mAnimName; + return label; +} + +void LLGestureStepAnimation::dump() +{ + llinfos << "step animation " << mAnimName + << " id " << mAnimAssetID + << " flags " << mFlags + << llendl; +} + +//--------------------------------------------------------------------------- +// LLGestureStepSound +//--------------------------------------------------------------------------- +LLGestureStepSound::LLGestureStepSound() +: LLGestureStep(), + mSoundName("None"), + mSoundAssetID(), + mFlags(0x0) +{ } + +LLGestureStepSound::~LLGestureStepSound() +{ } + +S32 LLGestureStepSound::getMaxSerialSize() const +{ + S32 max_size = 0; + max_size += 256; // sound name + max_size += 64; // sound asset id + max_size += 64; // flags + /* binary + max_size += mSoundName.length() + 1; + max_size += sizeof(mSoundAssetID); + max_size += sizeof(mFlags); + */ + return max_size; +} + +BOOL LLGestureStepSound::serialize(LLDataPacker& dp) const +{ + dp.packString(mSoundName.c_str(), "sound_name"); + dp.packUUID(mSoundAssetID, "asset_id"); + dp.packU32(mFlags, "flags"); + return TRUE; +} + +BOOL LLGestureStepSound::deserialize(LLDataPacker& dp) +{ + char buffer[256]; /* Flawfinder: ignore */ + dp.unpackString(buffer, "sound_name"); + mSoundName = buffer; + + dp.unpackUUID(mSoundAssetID, "asset_id"); + dp.unpackU32(mFlags, "flags"); + return TRUE; +} + +std::string LLGestureStepSound::getLabel() const +{ + std::string label("Sound: "); + label += mSoundName; + return label; +} + +void LLGestureStepSound::dump() +{ + llinfos << "step sound " << mSoundName + << " id " << mSoundAssetID + << " flags " << mFlags + << llendl; +} + + +//--------------------------------------------------------------------------- +// LLGestureStepChat +//--------------------------------------------------------------------------- +LLGestureStepChat::LLGestureStepChat() +: LLGestureStep(), + mChatText(), + mFlags(0x0) +{ } + +LLGestureStepChat::~LLGestureStepChat() +{ } + +S32 LLGestureStepChat::getMaxSerialSize() const +{ + S32 max_size = 0; + max_size += 256; // chat text + max_size += 64; // flags + /* binary + max_size += mChatText.length() + 1; + max_size += sizeof(mFlags); + */ + return max_size; +} + +BOOL LLGestureStepChat::serialize(LLDataPacker& dp) const +{ + dp.packString(mChatText.c_str(), "chat_text"); + dp.packU32(mFlags, "flags"); + return TRUE; +} + +BOOL LLGestureStepChat::deserialize(LLDataPacker& dp) +{ + char buffer[256]; /* Flawfinder: ignore */ + dp.unpackString(buffer, "chat_text"); + mChatText = buffer; + + dp.unpackU32(mFlags, "flags"); + return TRUE; +} + +std::string LLGestureStepChat::getLabel() const +{ + std::string label("Chat: "); + label += mChatText; + return label; +} + +void LLGestureStepChat::dump() +{ + llinfos << "step chat " << mChatText + << " flags " << mFlags + << llendl; +} + + +//--------------------------------------------------------------------------- +// LLGestureStepWait +//--------------------------------------------------------------------------- +LLGestureStepWait::LLGestureStepWait() +: LLGestureStep(), + mWaitSeconds(0.f), + mFlags(0x0) +{ } + +LLGestureStepWait::~LLGestureStepWait() +{ } + +S32 LLGestureStepWait::getMaxSerialSize() const +{ + S32 max_size = 0; + max_size += 64; // wait seconds + max_size += 64; // flags + /* binary + max_size += sizeof(mWaitSeconds); + max_size += sizeof(mFlags); + */ + return max_size; +} + +BOOL LLGestureStepWait::serialize(LLDataPacker& dp) const +{ + dp.packF32(mWaitSeconds, "wait_seconds"); + dp.packU32(mFlags, "flags"); + return TRUE; +} + +BOOL LLGestureStepWait::deserialize(LLDataPacker& dp) +{ + dp.unpackF32(mWaitSeconds, "wait_seconds"); + dp.unpackU32(mFlags, "flags"); + return TRUE; +} + +std::string LLGestureStepWait::getLabel() const +{ + std::string label("--- Wait: "); + if (mFlags & WAIT_FLAG_TIME) + { + char buffer[64]; /* Flawfinder: ignore */ + snprintf(buffer, sizeof(buffer), "%.1f seconds", (double)mWaitSeconds); /* Flawfinder: ignore */ + label += buffer; + } + else if (mFlags & WAIT_FLAG_ALL_ANIM) + { + label += "until animations are done"; + } + + return label; +} + + +void LLGestureStepWait::dump() +{ + llinfos << "step wait " << mWaitSeconds + << " flags " << mFlags + << llendl; +} diff --git a/indra/llcharacter/llmultigesture.h b/indra/llcharacter/llmultigesture.h new file mode 100644 index 0000000000..39ee30568b --- /dev/null +++ b/indra/llcharacter/llmultigesture.h @@ -0,0 +1,213 @@ +/** + * @file llmultigesture.h + * @brief Gestures that are asset-based and can have multiple steps. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMULTIGESTURE_H +#define LL_LLMULTIGESTURE_H + +#include +#include +#include + +#include "lluuid.h" +#include "llframetimer.h" + +class LLDataPacker; +class LLGestureStep; + +class LLMultiGesture +{ +public: + LLMultiGesture(); + virtual ~LLMultiGesture(); + + // Maximum number of bytes this could hold once serialized. + S32 getMaxSerialSize() const; + + BOOL serialize(LLDataPacker& dp) const; + BOOL deserialize(LLDataPacker& dp); + + void dump(); + + void reset(); + + const std::string& getTrigger() const { return mTrigger; } +protected: + LLMultiGesture(const LLMultiGesture& gest); + const LLMultiGesture& operator=(const LLMultiGesture& rhs); + +public: + // name is stored at asset level + // desc is stored at asset level + KEY mKey; + MASK mMask; + + // String, like "/foo" or "hello" that makes it play + std::string mTrigger; + + // Replaces the trigger substring with this text + std::string mReplaceText; + + std::vector mSteps; + + // Is the gesture currently playing? + BOOL mPlaying; + + // "instruction pointer" for steps + S32 mCurrentStep; + + // We're waiting for triggered animations to stop playing + BOOL mWaitingAnimations; + + // We're waiting a fixed amount of time + BOOL mWaitingTimer; + + // Waiting after the last step played for all animations to complete + BOOL mWaitingAtEnd; + + // Timer for waiting + LLFrameTimer mWaitTimer; + + void (*mDoneCallback)(LLMultiGesture* gesture, void* data); + void* mCallbackData; + + // Animations that we requested to start + std::set mRequestedAnimIDs; + + // Once the animation starts playing (sim says to start playing) + // the ID is moved from mRequestedAnimIDs to here. + std::set mPlayingAnimIDs; +}; + + +enum EStepType +{ + STEP_ANIMATION = 0, + STEP_SOUND = 1, + STEP_CHAT = 2, + STEP_WAIT = 3, + + STEP_EOF = 4 +}; + + +class LLGestureStep +{ +public: + LLGestureStep() {} + virtual ~LLGestureStep() {} + + virtual EStepType getType() = 0; + + // Return a user-readable label for this step + virtual std::string getLabel() const = 0; + + virtual S32 getMaxSerialSize() const = 0; + virtual BOOL serialize(LLDataPacker& dp) const = 0; + virtual BOOL deserialize(LLDataPacker& dp) = 0; + + virtual void dump() = 0; +}; + + +// By default, animation steps start animations. +// If the least significant bit is 1, it will stop animations. +const U32 ANIM_FLAG_STOP = 0x01; + +class LLGestureStepAnimation : public LLGestureStep +{ +public: + LLGestureStepAnimation(); + virtual ~LLGestureStepAnimation(); + + virtual EStepType getType() { return STEP_ANIMATION; } + + virtual std::string getLabel() const; + + virtual S32 getMaxSerialSize() const; + virtual BOOL serialize(LLDataPacker& dp) const; + virtual BOOL deserialize(LLDataPacker& dp); + + virtual void dump(); + +public: + std::string mAnimName; + LLUUID mAnimAssetID; + U32 mFlags; +}; + + +class LLGestureStepSound : public LLGestureStep +{ +public: + LLGestureStepSound(); + virtual ~LLGestureStepSound(); + + virtual EStepType getType() { return STEP_SOUND; } + + virtual std::string getLabel() const; + + virtual S32 getMaxSerialSize() const; + virtual BOOL serialize(LLDataPacker& dp) const; + virtual BOOL deserialize(LLDataPacker& dp); + + virtual void dump(); + +public: + std::string mSoundName; + LLUUID mSoundAssetID; + U32 mFlags; +}; + + +class LLGestureStepChat : public LLGestureStep +{ +public: + LLGestureStepChat(); + virtual ~LLGestureStepChat(); + + virtual EStepType getType() { return STEP_CHAT; } + + virtual std::string getLabel() const; + + virtual S32 getMaxSerialSize() const; + virtual BOOL serialize(LLDataPacker& dp) const; + virtual BOOL deserialize(LLDataPacker& dp); + + virtual void dump(); + +public: + std::string mChatText; + U32 mFlags; +}; + + +const U32 WAIT_FLAG_TIME = 0x01; +const U32 WAIT_FLAG_ALL_ANIM = 0x02; + +class LLGestureStepWait : public LLGestureStep +{ +public: + LLGestureStepWait(); + virtual ~LLGestureStepWait(); + + virtual EStepType getType() { return STEP_WAIT; } + + virtual std::string getLabel() const; + + virtual S32 getMaxSerialSize() const; + virtual BOOL serialize(LLDataPacker& dp) const; + virtual BOOL deserialize(LLDataPacker& dp); + + virtual void dump(); + +public: + F32 mWaitSeconds; + U32 mFlags; +}; + +#endif diff --git a/indra/llcharacter/llpose.cpp b/indra/llcharacter/llpose.cpp new file mode 100644 index 0000000000..ff56dba585 --- /dev/null +++ b/indra/llcharacter/llpose.cpp @@ -0,0 +1,544 @@ +/** + * @file llpose.cpp + * @brief Implementation of LLPose class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llpose.h" + +#include "llmotion.h" +#include "llmath.h" + +//----------------------------------------------------------------------------- +// Static +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// LLPose +//----------------------------------------------------------------------------- +LLPose::~LLPose() +{ +} + +//----------------------------------------------------------------------------- +// getFirstJointState() +//----------------------------------------------------------------------------- +LLJointState *LLPose::getFirstJointState() +{ + mListIter = mJointMap.begin(); + if (mListIter == mJointMap.end()) + { + return NULL; + } + else + { + return mListIter->second; + } +} + +//----------------------------------------------------------------------------- +// getNextJointState() +//----------------------------------------------------------------------------- +LLJointState *LLPose::getNextJointState() +{ + mListIter++; + if (mListIter == mJointMap.end()) + { + return NULL; + } + else + { + return mListIter->second; + } +} + +//----------------------------------------------------------------------------- +// addJointState() +//----------------------------------------------------------------------------- +BOOL LLPose::addJointState(LLJointState *jointState) +{ + if (mJointMap.find(jointState->getJoint()->getName()) == mJointMap.end()) + { + mJointMap[jointState->getJoint()->getName()] = jointState; + } + return TRUE; +} + +//----------------------------------------------------------------------------- +// removeJointState() +//----------------------------------------------------------------------------- +BOOL LLPose::removeJointState(LLJointState *jointState) +{ + mJointMap.erase(jointState->getJoint()->getName()); + return TRUE; +} + +//----------------------------------------------------------------------------- +// removeAllJointStates() +//----------------------------------------------------------------------------- +BOOL LLPose::removeAllJointStates() +{ + mJointMap.clear(); + return TRUE; +} + +//----------------------------------------------------------------------------- +// findJointState() +//----------------------------------------------------------------------------- +LLJointState* LLPose::findJointState(LLJoint *joint) +{ + joint_map_iterator iter = mJointMap.find(joint->getName()); + + if (iter == mJointMap.end()) + { + return NULL; + } + else + { + return iter->second; + } +} + +//----------------------------------------------------------------------------- +// findJointState() +//----------------------------------------------------------------------------- +LLJointState* LLPose::findJointState(const std::string &name) +{ + joint_map_iterator iter = mJointMap.find(name); + + if (iter == mJointMap.end()) + { + return NULL; + } + else + { + return iter->second; + } +} + +//----------------------------------------------------------------------------- +// setWeight() +//----------------------------------------------------------------------------- +void LLPose::setWeight(F32 weight) +{ + joint_map_iterator iter; + for(iter = mJointMap.begin(); + iter != mJointMap.end(); + ++iter) + { + iter->second->setWeight(weight); + } + mWeight = weight; +} + +//----------------------------------------------------------------------------- +// getWeight() +//----------------------------------------------------------------------------- +F32 LLPose::getWeight() const +{ + return mWeight; +} + +//----------------------------------------------------------------------------- +// getNumJointStates() +//----------------------------------------------------------------------------- +S32 LLPose::getNumJointStates() const +{ + return (S32)mJointMap.size(); +} + +//----------------------------------------------------------------------------- +// LLJointStateBlender +//----------------------------------------------------------------------------- + +LLJointStateBlender::LLJointStateBlender() +{ + for(S32 i = 0; i < JSB_NUM_JOINT_STATES; i++) + { + mJointStates[i] = NULL; + mPriorities[i] = S32_MIN; + } +} + +LLJointStateBlender::~LLJointStateBlender() +{ + +} + +//----------------------------------------------------------------------------- +// addJointState() +//----------------------------------------------------------------------------- +BOOL LLJointStateBlender::addJointState(LLJointState *joint_state, S32 priority, BOOL additive_blend) +{ + llassert(joint_state); + + if (!joint_state->getJoint()) + // this joint state doesn't point to an actual joint, so we don't care about applying it + return FALSE; + + for(S32 i = 0; i < JSB_NUM_JOINT_STATES; i++) + { + if (NULL == mJointStates[i]) + { + mJointStates[i] = joint_state; + mPriorities[i] = priority; + mAdditiveBlends[i] = additive_blend; + return TRUE; + } + else if (priority > mPriorities[i]) + { + // we're at a higher priority than the current joint state in this slot + // so shift everyone over + // previous joint states (newer motions) with same priority should stay in place + for (S32 j = JSB_NUM_JOINT_STATES - 1; j > i; j--) + { + mJointStates[j] = mJointStates[j - 1]; + mPriorities[j] = mPriorities[j - 1]; + mAdditiveBlends[j] = mAdditiveBlends[j - 1]; + } + // now store ourselves in this slot + mJointStates[i] = joint_state; + mPriorities[i] = priority; + mAdditiveBlends[i] = additive_blend; + return TRUE; + } + } + + return FALSE; +} + +//----------------------------------------------------------------------------- +// blendJointStates() +//----------------------------------------------------------------------------- +void LLJointStateBlender::blendJointStates(BOOL apply_now) +{ + // we need at least one joint to blend + // if there is one, it will be in slot zero according to insertion logic + // instead of resetting joint state to default, just leave it unchanged from last frame + if (NULL == mJointStates[0]) + { + return; + } + + LLJoint* target_joint = apply_now ? mJointStates[0]->getJoint() : &mJointCache; + + const S32 POS_WEIGHT = 0; + const S32 ROT_WEIGHT = 1; + const S32 SCALE_WEIGHT = 2; + + F32 sum_weights[3]; + U32 sum_usage = 0; + + LLVector3 blended_pos = target_joint->getPosition(); + LLQuaternion blended_rot = target_joint->getRotation(); + LLVector3 blended_scale = target_joint->getScale(); + + LLVector3 added_pos; + LLQuaternion added_rot; + LLVector3 added_scale; + + //S32 joint_state_index; + + sum_weights[POS_WEIGHT] = 0.f; + sum_weights[ROT_WEIGHT] = 0.f; + sum_weights[SCALE_WEIGHT] = 0.f; + + for(S32 joint_state_index = 0; + joint_state_index < JSB_NUM_JOINT_STATES && mJointStates[joint_state_index] != NULL; + joint_state_index++) + { + U32 current_usage = mJointStates[joint_state_index]->getUsage(); + F32 current_weight = mJointStates[joint_state_index]->getWeight(); + LLJointState* jsp = mJointStates[joint_state_index]; + + if (current_weight == 0.f) + { + continue; + } + + if (mAdditiveBlends[joint_state_index]) + { + if(current_usage & LLJointState::POS) + { + F32 new_weight_sum = llmin(1.f, current_weight + sum_weights[POS_WEIGHT]); + + // add in pos for this jointstate modulated by weight + added_pos += jsp->getPosition() * (new_weight_sum - sum_weights[POS_WEIGHT]); + //sum_weights[POS_WEIGHT] = new_weight_sum; + } + + // now do scale + if(current_usage & LLJointState::SCALE) + { + F32 new_weight_sum = llmin(1.f, current_weight + sum_weights[SCALE_WEIGHT]); + + // add in scale for this jointstate modulated by weight + added_scale += jsp->getScale() * (new_weight_sum - sum_weights[SCALE_WEIGHT]); + //sum_weights[SCALE_WEIGHT] = new_weight_sum; + } + + if (current_usage & LLJointState::ROT) + { + F32 new_weight_sum = llmin(1.f, current_weight + sum_weights[ROT_WEIGHT]); + + // add in rotation for this jointstate modulated by weight + added_rot = nlerp((new_weight_sum - sum_weights[ROT_WEIGHT]), added_rot, jsp->getRotation()) * added_rot; + //sum_weights[ROT_WEIGHT] = new_weight_sum; + } + } + else + { + // blend two jointstates together + + // blend position + if(current_usage & LLJointState::POS) + { + if(sum_usage & LLJointState::POS) + { + F32 new_weight_sum = llmin(1.f, current_weight + sum_weights[POS_WEIGHT]); + + // blend positions from both + blended_pos = lerp(mJointStates[joint_state_index]->getPosition(), blended_pos, sum_weights[POS_WEIGHT] / new_weight_sum); + sum_weights[POS_WEIGHT] = new_weight_sum; + } + else + { + // copy position from current + blended_pos = mJointStates[joint_state_index]->getPosition(); + sum_weights[POS_WEIGHT] = current_weight; + } + } + + // now do scale + if(current_usage & LLJointState::SCALE) + { + if(sum_usage & LLJointState::SCALE) + { + F32 new_weight_sum = llmin(1.f, current_weight + sum_weights[SCALE_WEIGHT]); + + // blend scales from both + blended_scale = lerp(mJointStates[joint_state_index]->getScale(), blended_scale, sum_weights[SCALE_WEIGHT] / new_weight_sum); + sum_weights[SCALE_WEIGHT] = new_weight_sum; + } + else + { + // copy scale from current + blended_scale = mJointStates[joint_state_index]->getScale(); + sum_weights[SCALE_WEIGHT] = current_weight; + } + } + + // rotation + if (current_usage & LLJointState::ROT) + { + if(sum_usage & LLJointState::ROT) + { + F32 new_weight_sum = llmin(1.f, current_weight + sum_weights[ROT_WEIGHT]); + + // blend rotations from both + blended_rot = nlerp(sum_weights[ROT_WEIGHT] / new_weight_sum, mJointStates[joint_state_index]->getRotation(), blended_rot); + sum_weights[ROT_WEIGHT] = new_weight_sum; + } + else + { + // copy rotation from current + blended_rot = mJointStates[joint_state_index]->getRotation(); + sum_weights[ROT_WEIGHT] = current_weight; + } + } + + // update resulting usage mask + sum_usage = sum_usage | current_usage; + } + } + + // apply blended transforms + target_joint->setPosition(blended_pos); + target_joint->setScale(blended_scale); + target_joint->setRotation(blended_rot); + + // apply additive transforms + target_joint->setPosition(target_joint->getPosition() + added_pos); + target_joint->setScale(target_joint->getScale() + added_scale); + target_joint->setRotation(added_rot * target_joint->getRotation()); + + if (apply_now) + { + // now clear joint states + for(S32 i = 0; i < JSB_NUM_JOINT_STATES; i++) + { + mJointStates[i] = NULL; + } + } +} + +//----------------------------------------------------------------------------- +// interpolate() +//----------------------------------------------------------------------------- +void LLJointStateBlender::interpolate(F32 u) +{ + // only interpolate if we have a joint state + if (!mJointStates[0]) + { + return; + } + LLJoint* target_joint = mJointStates[0]->getJoint(); + + if (!target_joint) + { + return; + } + + target_joint->setPosition(lerp(target_joint->getPosition(), mJointCache.getPosition(), u)); + target_joint->setScale(lerp(target_joint->getScale(), mJointCache.getScale(), u)); + target_joint->setRotation(nlerp(u, target_joint->getRotation(), mJointCache.getRotation())); +} + +//----------------------------------------------------------------------------- +// clear() +//----------------------------------------------------------------------------- +void LLJointStateBlender::clear() +{ + // now clear joint states + for(S32 i = 0; i < JSB_NUM_JOINT_STATES; i++) + { + mJointStates[i] = NULL; + } +} + +//----------------------------------------------------------------------------- +// resetCachedJoint() +//----------------------------------------------------------------------------- +void LLJointStateBlender::resetCachedJoint() +{ + if (!mJointStates[0]) + { + return; + } + LLJoint* source_joint = mJointStates[0]->getJoint(); + mJointCache.setPosition(source_joint->getPosition()); + mJointCache.setScale(source_joint->getScale()); + mJointCache.setRotation(source_joint->getRotation()); +} + +//----------------------------------------------------------------------------- +// LLPoseBlender +//----------------------------------------------------------------------------- + +LLPoseBlender::LLPoseBlender() +{ +} + +LLPoseBlender::~LLPoseBlender() +{ + mJointStateBlenderPool.deleteAllData(); +} + +//----------------------------------------------------------------------------- +// addMotion() +//----------------------------------------------------------------------------- +BOOL LLPoseBlender::addMotion(LLMotion* motion) +{ + LLPose* pose = motion->getPose(); + + for(LLJointState *jsp = pose->getFirstJointState(); jsp; jsp = pose->getNextJointState()) + { + LLJoint *jointp = jsp->getJoint(); + LLJointStateBlender* joint_blender; + if (!mJointStateBlenderPool.checkData(jointp)) + { + // this is the first time we are animating this joint + // so create new jointblender and add it to our pool + joint_blender = new LLJointStateBlender(); + mJointStateBlenderPool.addData(jointp, joint_blender); + } else + { + joint_blender = mJointStateBlenderPool.getData(jointp); + } + + if (jsp->getPriority() == LLJoint::USE_MOTION_PRIORITY) + { + joint_blender->addJointState(jsp, motion->getPriority(), motion->getBlendType() == LLMotion::ADDITIVE_BLEND); + } + else + { + joint_blender->addJointState(jsp, jsp->getPriority(), motion->getBlendType() == LLMotion::ADDITIVE_BLEND); + } + + // add it to our list of active blenders + if(!mActiveBlenders.checkData(joint_blender)) + { + mActiveBlenders.addData(joint_blender); + } + } + return TRUE; +} + +//----------------------------------------------------------------------------- +// blendAndApply() +//----------------------------------------------------------------------------- +void LLPoseBlender::blendAndApply() +{ + for (LLJointStateBlender* jsbp = mActiveBlenders.getFirstData(); + jsbp; + jsbp = mActiveBlenders.getNextData()) + { + jsbp->blendJointStates(); + } + + // we're done now so there are no more active blenders for this frame + mActiveBlenders.removeAllNodes(); +} + +//----------------------------------------------------------------------------- +// blendAndCache() +//----------------------------------------------------------------------------- +void LLPoseBlender::blendAndCache(BOOL reset_cached_joints) +{ + for (LLJointStateBlender* jsbp = mActiveBlenders.getFirstData(); + jsbp; + jsbp = mActiveBlenders.getNextData()) + { + if (reset_cached_joints) + { + jsbp->resetCachedJoint(); + } + jsbp->blendJointStates(FALSE); + } +} + +//----------------------------------------------------------------------------- +// interpolate() +//----------------------------------------------------------------------------- +void LLPoseBlender::interpolate(F32 u) +{ + for (LLJointStateBlender* jsbp = mActiveBlenders.getFirstData(); + jsbp; + jsbp = mActiveBlenders.getNextData()) + { + jsbp->interpolate(u); + } +} + +//----------------------------------------------------------------------------- +// clearBlenders() +//----------------------------------------------------------------------------- +void LLPoseBlender::clearBlenders() +{ + for (LLJointStateBlender* jsbp = mActiveBlenders.getFirstData(); + jsbp; + jsbp = mActiveBlenders.getNextData()) + { + jsbp->clear(); + } + + mActiveBlenders.removeAllNodes(); +} + diff --git a/indra/llcharacter/llpose.h b/indra/llcharacter/llpose.h new file mode 100644 index 0000000000..e286c14e21 --- /dev/null +++ b/indra/llcharacter/llpose.h @@ -0,0 +1,120 @@ +/** + * @file llpose.h + * @brief Implementation of LLPose class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLPOSE_H +#define LL_LLPOSE_H + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include + +#include "linked_lists.h" +#include "llmap.h" +#include "lljointstate.h" +#include "llassoclist.h" +#include "lljoint.h" +#include + + +//----------------------------------------------------------------------------- +// class LLPose +//----------------------------------------------------------------------------- +class LLPose +{ + friend class LLPoseBlender; +protected: + typedef std::map joint_map; + typedef joint_map::iterator joint_map_iterator; + typedef joint_map::value_type joint_map_value_type; + + joint_map mJointMap; + F32 mWeight; + joint_map_iterator mListIter; +public: + // Iterate through jointStates + LLJointState *getFirstJointState(); + LLJointState *getNextJointState(); + LLJointState *findJointState(LLJoint *joint); + LLJointState *findJointState(const std::string &name); +public: + // Constructor + LLPose() : mWeight(0.f) {} + // Destructor + ~LLPose(); + // add a joint state in this pose + BOOL addJointState(LLJointState *jointState); + // remove a joint state from this pose + BOOL removeJointState(LLJointState *jointState); + // removes all joint states from this pose + BOOL removeAllJointStates(); + // set weight for all joint states in this pose + void setWeight(F32 weight); + // get weight for this pose + F32 getWeight() const; + // returns number of joint states stored in this pose + S32 getNumJointStates() const; +}; + +const S32 JSB_NUM_JOINT_STATES = 4; + +class LLJointStateBlender +{ +protected: + LLJointState* mJointStates[JSB_NUM_JOINT_STATES]; + S32 mPriorities[JSB_NUM_JOINT_STATES]; + BOOL mAdditiveBlends[JSB_NUM_JOINT_STATES]; +public: + LLJointStateBlender(); + ~LLJointStateBlender(); + void blendJointStates(BOOL apply_now = TRUE); + BOOL addJointState(LLJointState *joint_state, S32 priority, BOOL additive_blend); + void interpolate(F32 u); + void clear(); + void resetCachedJoint(); + +public: + LLJoint mJointCache; +}; + +class LLMotion; + +class LLPoseBlender +{ +protected: + LLMap mJointStateBlenderPool; + LLLinkedList mActiveBlenders; + + S32 mNextPoseSlot; + LLPose mBlendedPose; +public: + // Constructor + LLPoseBlender(); + // Destructor + ~LLPoseBlender(); + + // request motion joint states to be added to pose blender joint state records + BOOL addMotion(LLMotion* motion); + + // blend all joint states and apply to skeleton + void blendAndApply(); + + // removes all joint state blenders from last time + void clearBlenders(); + + // blend all joint states and cache results + void blendAndCache(BOOL reset_cached_joints); + + // interpolate all joints towards cached values + void interpolate(F32 u); + + LLPose* getBlendedPose() { return &mBlendedPose; } +}; + +#endif // LL_LLPOSE_H + diff --git a/indra/llcharacter/llstatemachine.cpp b/indra/llcharacter/llstatemachine.cpp new file mode 100644 index 0000000000..d51b2f75aa --- /dev/null +++ b/indra/llcharacter/llstatemachine.cpp @@ -0,0 +1,378 @@ +/** + * @file llstatemachine.cpp + * @brief LLStateMachine implementation file. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llstatemachine.h" +#include "llapr.h" + +#define FSM_PRINT_STATE_TRANSITIONS (0) + +U32 LLUniqueID::sNextID = 0; + +bool operator==(const LLUniqueID &a, const LLUniqueID &b) +{ + return (a.mId == b.mId); +} + +bool operator!=(const LLUniqueID &a, const LLUniqueID &b) +{ + return (a.mId != b.mId); +} + +//----------------------------------------------------------------------------- +// LLStateDiagram +//----------------------------------------------------------------------------- +LLStateDiagram::LLStateDiagram() +{ + mUseDefaultState = FALSE; +} + +LLStateDiagram::~LLStateDiagram() +{ + +} + +// add a state to the state graph +BOOL LLStateDiagram::addState(LLFSMState *state) +{ + mStates[state] = Transitions(); + return TRUE; +} + +// add a directed transition between 2 states +BOOL LLStateDiagram::addTransition(LLFSMState& start_state, LLFSMState& end_state, LLFSMTransition& transition) +{ + StateMap::iterator state_it; + state_it = mStates.find(&start_state); + Transitions* state_transitions = NULL; + if (state_it == mStates.end() ) + { + addState(&start_state); + state_transitions = &mStates[&start_state]; + } + else + { + state_transitions = &state_it->second; + } + state_it = mStates.find(&end_state); + if (state_it == mStates.end() ) + { + addState(&end_state); + } + + Transitions::iterator transition_it = state_transitions->find(&transition); + if (transition_it != state_transitions->end()) + { + llerrs << "LLStateTable::addDirectedTransition() : transition already exists" << llendl; + return FALSE; // transition already exists + } + + (*state_transitions)[&transition] = &end_state; + return TRUE; +} + +// add an undirected transition between 2 states +BOOL LLStateDiagram::addUndirectedTransition(LLFSMState& start_state, LLFSMState& end_state, LLFSMTransition& transition) +{ + BOOL result; + result = addTransition(start_state, end_state, transition); + if (result) + { + result = addTransition(end_state, start_state, transition); + } + return result; +} + +// add a transition that exists for every state +void LLStateDiagram::addDefaultTransition(LLFSMState& end_state, LLFSMTransition& transition) +{ + mDefaultTransitions[&transition] = &end_state; +} + +// process a possible transition, and get the resulting state +LLFSMState* LLStateDiagram::processTransition(LLFSMState& start_state, LLFSMTransition& transition) +{ + // look up transition + //LLFSMState** dest_state = (mStates.getValue(&start_state))->getValue(&transition); + LLFSMState* dest_state = NULL; + StateMap::iterator state_it = mStates.find(&start_state); + if (state_it == mStates.end()) + { + return NULL; + } + Transitions::iterator transition_it = state_it->second.find(&transition); + + // try default transitions if state-specific transition not found + if (transition_it == state_it->second.end()) + { + dest_state = mDefaultTransitions[&transition]; + } + else + { + dest_state = transition_it->second; + } + + // if we have a destination state... + if (NULL != dest_state) + { + // ...return it... + return dest_state; + } + // ... otherwise ... + else + { + // ...look for default state... + if (mUseDefaultState) + { + // ...return it if we have it... + return mDefaultState; + } + else + { + // ...or else we're still in the same state. + return &start_state; + } + } +} + +void LLStateDiagram::setDefaultState(LLFSMState& default_state) +{ + mUseDefaultState = TRUE; + mDefaultState = &default_state; +} + +S32 LLStateDiagram::numDeadendStates() +{ + S32 numDeadends = 0; + StateMap::iterator state_it; + for(state_it = mStates.begin(); state_it != mStates.end(); ++state_it) + { + if (state_it->second.size() == 0) + { + numDeadends++; + } + } + return numDeadends; +} + +BOOL LLStateDiagram::stateIsValid(LLFSMState& state) +{ + if (mStates.find(&state) != mStates.end()) + { + return TRUE; + } + return FALSE; +} + +LLFSMState* LLStateDiagram::getState(U32 state_id) +{ + StateMap::iterator state_it; + for(state_it = mStates.begin(); state_it != mStates.end(); ++state_it) + { + if (state_it->first->getID() == state_id) + { + return state_it->first; + } + } + return NULL; +} + +BOOL LLStateDiagram::saveDotFile(const char* filename) +{ + apr_file_t* dot_file = ll_apr_file_open(filename, LL_APR_W); + + if (!dot_file) + { + llwarns << "LLStateDiagram::saveDotFile() : Couldn't open " << filename << " to save state diagram." << llendl; + return FALSE; + } + apr_file_printf(dot_file, "digraph StateMachine {\n\tsize=\"100,100\";\n\tfontsize=40;\n\tlabel=\"Finite State Machine\";\n\torientation=landscape\n\tratio=.77\n"); + + StateMap::iterator state_it; + for(state_it = mStates.begin(); state_it != mStates.end(); ++state_it) + { + apr_file_printf(dot_file, "\t\"%s\" [fontsize=28,shape=box]\n", state_it->first->getName().c_str()); + } + apr_file_printf(dot_file, "\t\"All States\" [fontsize=30,style=bold,shape=box]\n"); + + Transitions::iterator transitions_it; + for(transitions_it = mDefaultTransitions.begin(); transitions_it != mDefaultTransitions.end(); ++transitions_it) + { + apr_file_printf(dot_file, "\t\"All States\" -> \"%s\" [label = \"%s\",fontsize=24];\n", transitions_it->second->getName().c_str(), + transitions_it->second->getName().c_str()); + } + + if (mDefaultState) + { + apr_file_printf(dot_file, "\t\"All States\" -> \"%s\";\n", mDefaultState->getName().c_str()); + } + + + for(state_it = mStates.begin(); state_it != mStates.end(); ++state_it) + { + LLFSMState *state = state_it->first; + + Transitions::iterator transitions_it; + for(transitions_it = state_it->second.begin(); + transitions_it != state_it->second.end(); + ++transitions_it) + { + std::string state_name = state->getName(); + std::string target_name = transitions_it->second->getName(); + std::string transition_name = transitions_it->first->getName(); + apr_file_printf(dot_file, "\t\"%s\" -> \"%s\" [label = \"%s\",fontsize=24];\n", state->getName().c_str(), + target_name.c_str(), + transition_name.c_str()); + } + } + + apr_file_printf(dot_file, "}\n"); + + apr_file_close(dot_file); + + return TRUE; +} + +std::ostream& operator<<(std::ostream &s, LLStateDiagram &FSM) +{ + if (FSM.mDefaultState) + { + s << "Default State: " << FSM.mDefaultState->getName() << "\n"; + } + + LLStateDiagram::Transitions::iterator transitions_it; + for(transitions_it = FSM.mDefaultTransitions.begin(); + transitions_it != FSM.mDefaultTransitions.end(); + ++transitions_it) + { + s << "Any State -- " << transitions_it->first->getName() + << " --> " << transitions_it->second->getName() << "\n"; + } + + LLStateDiagram::StateMap::iterator state_it; + for(state_it = FSM.mStates.begin(); state_it != FSM.mStates.end(); ++state_it) + { + LLStateDiagram::Transitions::iterator transitions_it; + for(transitions_it = state_it->second.begin(); + transitions_it != state_it->second.end(); + ++transitions_it) + { + s << state_it->first->getName() << " -- " << transitions_it->first->getName() + << " --> " << transitions_it->second->getName() << "\n"; + } + s << "\n"; + } + + return s; +} + +//----------------------------------------------------------------------------- +// LLStateMachine +//----------------------------------------------------------------------------- + +LLStateMachine::LLStateMachine() +{ + // we haven't received a starting state yet + mCurrentState = NULL; + mLastState = NULL; + mStateDiagram = NULL; +} + +LLStateMachine::~LLStateMachine() +{ + +} + +// returns current state +LLFSMState* LLStateMachine::getCurrentState() const +{ + return mCurrentState; +} + +// executes current state +void LLStateMachine::runCurrentState(void *data) +{ + mCurrentState->execute(data); +} + +// set current state +BOOL LLStateMachine::setCurrentState(LLFSMState *initial_state, void* user_data, BOOL skip_entry) +{ + llassert(mStateDiagram); + + if (mStateDiagram->stateIsValid(*initial_state)) + { + mLastState = mCurrentState = initial_state; + if (!skip_entry) + { + initial_state->onEntry(user_data); + } + return TRUE; + } + + return FALSE; +} + +BOOL LLStateMachine::setCurrentState(U32 state_id, void* user_data, BOOL skip_entry) +{ + llassert(mStateDiagram); + + LLFSMState* state = mStateDiagram->getState(state_id); + + if (state) + { + mLastState = mCurrentState = state; + if (!skip_entry) + { + state->onEntry(user_data); + } + return TRUE; + } + + return FALSE; +} + +void LLStateMachine::processTransition(LLFSMTransition& transition, void* user_data) +{ + llassert(mStateDiagram); + + LLFSMState* new_state = mStateDiagram->processTransition(*mCurrentState, transition); + + mLastTransition = &transition; + mLastState = mCurrentState; + + if (*mCurrentState != *new_state) + { + if (mCurrentState && new_state && *mCurrentState != *new_state) + { + mCurrentState->onExit(user_data); + } + if (new_state) + { + if (!mCurrentState || *mCurrentState != *new_state) + { + mCurrentState = new_state; + if (mCurrentState) + { + mCurrentState->onEntry(user_data); + } + } + } +#if FSM_PRINT_STATE_TRANSITIONS + llinfos << "Entering state " << mCurrentState->getName() << + " on transition " << transition.getName() << " from state " << + mLastState->getName() << llendl; +#endif + } +} + +void LLStateMachine::setStateDiagram(LLStateDiagram* diagram) +{ + mStateDiagram = diagram; +} diff --git a/indra/llcharacter/llstatemachine.h b/indra/llcharacter/llstatemachine.h new file mode 100644 index 0000000000..837fc57b9b --- /dev/null +++ b/indra/llcharacter/llstatemachine.h @@ -0,0 +1,130 @@ +/** + * @file llstatemachine.h + * @brief LLStateMachine class header file. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSTATEMACHINE_H +#define LL_LLSTATEMACHINE_H + +#include + +#include "llassoclist.h" +#include "llerror.h" +#include + +class LLUniqueID +{ + friend bool operator==(const LLUniqueID &a, const LLUniqueID &b); + friend bool operator!=(const LLUniqueID &a, const LLUniqueID &b); +protected: + static U32 sNextID; + U32 mId; +public: + LLUniqueID(){mId = sNextID++;} + virtual ~LLUniqueID(){} + U32 getID() {return mId;} +}; + +class LLFSMTransition : public LLUniqueID +{ +public: + LLFSMTransition() : LLUniqueID(){}; + virtual std::string getName(){ return "unnamed"; } +}; + +class LLFSMState : public LLUniqueID +{ +public: + LLFSMState() : LLUniqueID(){}; + virtual void onEntry(void *){}; + virtual void onExit(void *){}; + virtual void execute(void *){}; + virtual std::string getName(){ return "unnamed"; } +}; + +class LLStateDiagram +{ +typedef std::map Transitions; + +friend std::ostream& operator<<(std::ostream &s, LLStateDiagram &FSM); +friend class LLStateMachine; + +protected: + typedef std::map StateMap; + StateMap mStates; + Transitions mDefaultTransitions; + LLFSMState* mDefaultState; + BOOL mUseDefaultState; + +public: + LLStateDiagram(); + virtual ~LLStateDiagram(); + +protected: + // add a state to the state graph, executed implicitly when adding transitions + BOOL addState(LLFSMState *state); + + // add a directed transition between 2 states + BOOL addTransition(LLFSMState& start_state, LLFSMState& end_state, LLFSMTransition& transition); + + // add an undirected transition between 2 states + BOOL addUndirectedTransition(LLFSMState& start_state, LLFSMState& end_state, LLFSMTransition& transition); + + // add a transition that is taken if none other exist + void addDefaultTransition(LLFSMState& end_state, LLFSMTransition& transition); + + // process a possible transition, and get the resulting state + LLFSMState* processTransition(LLFSMState& start_state, LLFSMTransition& transition); + + // add a transition that exists for every state + void setDefaultState(LLFSMState& default_state); + + // return total number of states with no outgoing transitions + S32 numDeadendStates(); + + // does this state exist in the state diagram? + BOOL stateIsValid(LLFSMState& state); + + // get a state pointer by ID + LLFSMState* getState(U32 state_id); + +public: + // save the graph in a DOT file for rendering and visualization + BOOL saveDotFile(const char* filename); +}; + +class LLStateMachine +{ +protected: + LLFSMState* mCurrentState; + LLFSMState* mLastState; + LLFSMTransition* mLastTransition; + LLStateDiagram* mStateDiagram; + +public: + LLStateMachine(); + virtual ~LLStateMachine(); + + // set state diagram + void setStateDiagram(LLStateDiagram* diagram); + + // process this transition + void processTransition(LLFSMTransition &transition, void* user_data); + + // returns current state + LLFSMState* getCurrentState() const; + + // execute current state + void runCurrentState(void *data); + + // set state by state pointer + BOOL setCurrentState(LLFSMState *initial_state, void* user_data, BOOL skip_entry = TRUE); + + // set state by unique ID + BOOL setCurrentState(U32 state_id, void* user_data, BOOL skip_entry = TRUE); +}; + +#endif //_LL_LLSTATEMACHINE_H diff --git a/indra/llcharacter/lltargetingmotion.cpp b/indra/llcharacter/lltargetingmotion.cpp new file mode 100644 index 0000000000..0f3eaa855b --- /dev/null +++ b/indra/llcharacter/lltargetingmotion.cpp @@ -0,0 +1,151 @@ +/** + * @file lltargetingmotion.cpp + * @brief Implementation of LLTargetingMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "lltargetingmotion.h" +#include "llcharacter.h" +#include "v3dmath.h" +#include "llcriticaldamp.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +const F32 TORSO_TARGET_HALF_LIFE = 0.25f; +const F32 MAX_TIME_DELTA = 2.f; //max two seconds a frame for calculating interpolation +const F32 TARGET_PLANE_THRESHOLD_DOT = 0.6f; +const F32 TORSO_ROT_FRACTION = 0.5f; + +//----------------------------------------------------------------------------- +// LLTargetingMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLTargetingMotion::LLTargetingMotion(const LLUUID &id) : LLMotion(id) +{ + mCharacter = NULL; + mName = "targeting"; +} + + +//----------------------------------------------------------------------------- +// ~LLTargetingMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLTargetingMotion::~LLTargetingMotion() +{ +} + +//----------------------------------------------------------------------------- +// LLTargetingMotion::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLTargetingMotion::onInitialize(LLCharacter *character) +{ + // save character for future use + mCharacter = character; + + mPelvisJoint = mCharacter->getJoint("mPelvis"); + mTorsoJoint = mCharacter->getJoint("mTorso"); + mRightHandJoint = mCharacter->getJoint("mWristRight"); + + // make sure character skeleton is copacetic + if (!mPelvisJoint || + !mTorsoJoint || + !mRightHandJoint) + { + llwarns << "Invalid skeleton for targeting motion!" << llendl; + return STATUS_FAILURE; + } + + mTorsoState.setJoint( mTorsoJoint ); + + // add joint states to the pose + mTorsoState.setUsage(LLJointState::ROT); + addJointState( &mTorsoState ); + + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// LLTargetingMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLTargetingMotion::onActivate() +{ + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLTargetingMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLTargetingMotion::onUpdate(F32 time, U8* joint_mask) +{ + F32 slerp_amt = LLCriticalDamp::getInterpolant(TORSO_TARGET_HALF_LIFE); + + LLVector3 target; + LLVector3* lookAtPoint = (LLVector3*)mCharacter->getAnimationData("LookAtPoint"); + + BOOL result = TRUE; + + if (!lookAtPoint) + { + return TRUE; + } + else + { + target = *lookAtPoint; + target.normVec(); + } + + //LLVector3 target_plane_normal = LLVector3(1.f, 0.f, 0.f) * mPelvisJoint->getWorldRotation(); + //LLVector3 torso_dir = LLVector3(1.f, 0.f, 0.f) * (mTorsoJoint->getWorldRotation() * mTorsoState.getRotation()); + + LLVector3 skyward(0.f, 0.f, 1.f); + LLVector3 left(skyward % target); + left.normVec(); + LLVector3 up(target % left); + up.normVec(); + LLQuaternion target_aim_rot(target, left, up); + + LLQuaternion cur_torso_rot = mTorsoJoint->getWorldRotation(); + + LLVector3 right_hand_at = LLVector3(0.f, -1.f, 0.f) * mRightHandJoint->getWorldRotation(); + left.setVec(skyward % right_hand_at); + left.normVec(); + up.setVec(right_hand_at % left); + up.normVec(); + LLQuaternion right_hand_rot(right_hand_at, left, up); + + LLQuaternion new_torso_rot = (cur_torso_rot * ~right_hand_rot) * target_aim_rot; + + // find ideal additive rotation to make torso point in correct direction + new_torso_rot = new_torso_rot * ~cur_torso_rot; + + // slerp from current additive rotation to ideal additive rotation + new_torso_rot = nlerp(slerp_amt, mTorsoState.getRotation(), new_torso_rot); + + // constraint overall torso rotation + LLQuaternion total_rot = new_torso_rot * mTorsoJoint->getRotation(); + total_rot.constrain(F_PI_BY_TWO * 0.8f); + new_torso_rot = total_rot * ~mTorsoJoint->getRotation(); + + mTorsoState.setRotation(new_torso_rot); + + return result; +} + +//----------------------------------------------------------------------------- +// LLTargetingMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLTargetingMotion::onDeactivate() +{ +} + + +// End diff --git a/indra/llcharacter/lltargetingmotion.h b/indra/llcharacter/lltargetingmotion.h new file mode 100644 index 0000000000..708dd374a2 --- /dev/null +++ b/indra/llcharacter/lltargetingmotion.h @@ -0,0 +1,98 @@ +/** + * @file lltargetingmotion.h + * @brief Implementation of LLTargetingMotion class. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTARGETINGMOTION_H +#define LL_LLTARGETINGMOTION_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include "llmotion.h" + +#define TARGETING_EASEIN_DURATION 0.3f +#define TARGETING_EASEOUT_DURATION 0.5f +#define TARGETING_PRIORITY LLJoint::HIGH_PRIORITY +#define MIN_REQUIRED_PIXEL_AREA_TARGETING 1000.f; + + +//----------------------------------------------------------------------------- +// class LLTargetingMotion +//----------------------------------------------------------------------------- +class LLTargetingMotion : + public LLMotion +{ +public: + // Constructor + LLTargetingMotion(const LLUUID &id); + + // Destructor + virtual ~LLTargetingMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLTargetingMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual BOOL getLoop() { return TRUE; } + + // motions must report their total duration + virtual F32 getDuration() { return 0.0; } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { return TARGETING_EASEIN_DURATION; } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { return TARGETING_EASEOUT_DURATION; } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { return TARGETING_PRIORITY; } + + virtual LLMotionBlendType getBlendType() { return ADDITIVE_BLEND; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_TARGETING; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + virtual BOOL onActivate(); + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + virtual BOOL onUpdate(F32 time, U8* joint_mask); + + // called when a motion is deactivated + virtual void onDeactivate(); + +public: + + LLCharacter *mCharacter; + LLJointState mTorsoState; + LLJoint* mPelvisJoint; + LLJoint* mTorsoJoint; + LLJoint* mRightHandJoint; +}; + +#endif // LL_LLTARGETINGMOTION_H + diff --git a/indra/llcharacter/llvisualparam.cpp b/indra/llcharacter/llvisualparam.cpp new file mode 100644 index 0000000000..a6fd9b974f --- /dev/null +++ b/indra/llcharacter/llvisualparam.cpp @@ -0,0 +1,266 @@ +/** + * @file llvisualparam.cpp + * @brief Implementation of LLPolyMesh class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llvisualparam.h" + +//----------------------------------------------------------------------------- +// LLVisualParamInfo() +//----------------------------------------------------------------------------- +LLVisualParamInfo::LLVisualParamInfo() + : + mID( -1 ), + mGroup( VISUAL_PARAM_GROUP_TWEAKABLE ), + mMinWeight( 0.f ), + mMaxWeight( 1.f ), + mDefaultWeight( 0.f ), + mSex( SEX_BOTH ) +{ +} + +//----------------------------------------------------------------------------- +// parseXml() +//----------------------------------------------------------------------------- +BOOL LLVisualParamInfo::parseXml(LLXmlTreeNode *node) +{ + // attribute: id + static LLStdStringHandle id_string = LLXmlTree::addAttributeString("id"); + node->getFastAttributeS32( id_string, mID ); + + // attribute: group + U32 group = 0; + static LLStdStringHandle group_string = LLXmlTree::addAttributeString("group"); + if( node->getFastAttributeU32( group_string, group ) ) + { + if( group < NUM_VISUAL_PARAM_GROUPS ) + { + mGroup = (EVisualParamGroup)group; + } + } + + // attribute: value_min, value_max + static LLStdStringHandle value_min_string = LLXmlTree::addAttributeString("value_min"); + static LLStdStringHandle value_max_string = LLXmlTree::addAttributeString("value_max"); + node->getFastAttributeF32( value_min_string, mMinWeight ); + node->getFastAttributeF32( value_max_string, mMaxWeight ); + + // attribute: value_default + F32 default_weight = 0; + static LLStdStringHandle value_default_string = LLXmlTree::addAttributeString("value_default"); + if( node->getFastAttributeF32( value_default_string, default_weight ) ) + { + mDefaultWeight = llclamp( default_weight, mMinWeight, mMaxWeight ); + if( default_weight != mDefaultWeight ) + { + llwarns << "value_default attribute is out of range in node " << mName << " " << default_weight << llendl; + } + } + + // attribute: sex + LLString sex = "both"; + static LLStdStringHandle sex_string = LLXmlTree::addAttributeString("sex"); + node->getFastAttributeString( sex_string, sex ); // optional + if( sex == "both" ) + { + mSex = SEX_BOTH; + } + else if( sex == "male" ) + { + mSex = SEX_MALE; + } + else if( sex == "female" ) + { + mSex = SEX_FEMALE; + } + else + { + llwarns << "Avatar file: has invalid sex attribute: " << sex << llendl; + return FALSE; + } + + // attribute: name + static LLStdStringHandle name_string = LLXmlTree::addAttributeString("name"); + if( !node->getFastAttributeString( name_string, mName ) ) + { + llwarns << "Avatar file: is missing name attribute" << llendl; + return FALSE; + } + + // attribute: label + static LLStdStringHandle label_string = LLXmlTree::addAttributeString("label"); + if( !node->getFastAttributeString( label_string, mDisplayName ) ) + { + mDisplayName = mName; + } + + // JC - make sure the display name includes the capitalization in the XML file, + // not the lowercased version. + LLString::toLower(mName); + + // attribute: label_min + static LLStdStringHandle label_min_string = LLXmlTree::addAttributeString("label_min"); + if( !node->getFastAttributeString( label_min_string, mMinName ) ) + { + mMinName = "Less"; + } + + // attribute: label_max + static LLStdStringHandle label_max_string = LLXmlTree::addAttributeString("label_max"); + if( !node->getFastAttributeString( label_max_string, mMaxName ) ) + { + mMaxName = "More"; + } + + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLVisualParam() +//----------------------------------------------------------------------------- +LLVisualParam::LLVisualParam() + : + mCurWeight( 0.f ), + mLastWeight( 0.f ), + mNext( NULL ), + mTargetWeight( 0.f ), + mIsAnimating( FALSE ), + mID( -1 ), + mInfo( 0 ) +{ +} + +//----------------------------------------------------------------------------- +// ~LLVisualParam() +//----------------------------------------------------------------------------- +LLVisualParam::~LLVisualParam() +{ + delete mNext; +} + +/* +//============================================================================= +// These virtual functions should always be overridden, +// but are included here for use as templates +//============================================================================= + +//----------------------------------------------------------------------------- +// setInfo() +//----------------------------------------------------------------------------- + +BOOL LLVisualParam::setInfo(LLVisualParamInfo *info) +{ + llassert(mInfo == NULL); + if (info->mID < 0) + return FALSE; + mInfo = info; + mID = info->mID; + setWeight(getDefaultWeight(), FALSE ); + return TRUE; +} + +//----------------------------------------------------------------------------- +// parseData() +//----------------------------------------------------------------------------- +BOOL LLVisualParam::parseData(LLXmlTreeNode *node) +{ + LLVisualParamInfo *info = new LLVisualParamInfo; + + info->parseXml(node); + if (!setInfo(info)) + return FALSE; + + return TRUE; +} +*/ + +//----------------------------------------------------------------------------- +// setWeight() +//----------------------------------------------------------------------------- +void LLVisualParam::setWeight(F32 weight, BOOL set_by_user) +{ + if (mIsAnimating) + { + //RN: allow overshoot + mCurWeight = weight; + } + else if (mInfo) + { + mCurWeight = llclamp(weight, mInfo->mMinWeight, mInfo->mMaxWeight); + } + else + { + mCurWeight = weight; + } + + if (mNext) + { + mNext->setWeight(weight, set_by_user); + } +} + +//----------------------------------------------------------------------------- +// setAnimationTarget() +//----------------------------------------------------------------------------- +void LLVisualParam::setAnimationTarget(F32 target_value, BOOL set_by_user) +{ + if (getGroup() == VISUAL_PARAM_GROUP_TWEAKABLE) + { + if (mInfo) + { + mTargetWeight = llclamp(target_value, mInfo->mMinWeight, mInfo->mMaxWeight); + } + else + { + mTargetWeight = target_value; + } + } + mIsAnimating = TRUE; + + if (mNext) + { + mNext->setAnimationTarget(target_value, set_by_user); + } +} + +//----------------------------------------------------------------------------- +// setNextParam() +//----------------------------------------------------------------------------- +void LLVisualParam::setNextParam( LLVisualParam *next ) +{ + llassert(!mNext); + + mNext = next; +} + +//----------------------------------------------------------------------------- +// animate() +//----------------------------------------------------------------------------- +void LLVisualParam::animate( F32 delta, BOOL set_by_user ) +{ + if (mIsAnimating) + { + F32 new_weight = ((mTargetWeight - mCurWeight) * delta) + mCurWeight; + setWeight(new_weight, set_by_user); + } +} + +//----------------------------------------------------------------------------- +// stopAnimating() +//----------------------------------------------------------------------------- +void LLVisualParam::stopAnimating(BOOL set_by_user) +{ + if (mIsAnimating && getGroup() == VISUAL_PARAM_GROUP_TWEAKABLE) + { + mIsAnimating = FALSE; + setWeight(mTargetWeight, set_by_user); + } +} diff --git a/indra/llcharacter/llvisualparam.h b/indra/llcharacter/llvisualparam.h new file mode 100644 index 0000000000..2a8d03d431 --- /dev/null +++ b/indra/llcharacter/llvisualparam.h @@ -0,0 +1,131 @@ +/** + * @file llvisualparam.h + * @brief Implementation of LLPolyMesh class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLVisualParam_H +#define LL_LLVisualParam_H + +#include "v3math.h" +#include "llstring.h" +#include "llxmltree.h" + +class LLPolyMesh; +class LLXmlTreeNode; + +enum ESex +{ + SEX_FEMALE = 0x01, + SEX_MALE = 0x02, + SEX_BOTH = 0x03 // values chosen to allow use as a bit field. +}; + +enum EVisualParamGroup +{ + VISUAL_PARAM_GROUP_TWEAKABLE, + VISUAL_PARAM_GROUP_ANIMATABLE, + NUM_VISUAL_PARAM_GROUPS +}; + +const S32 MAX_TRANSMITTED_VISUAL_PARAMS = 255; + +//----------------------------------------------------------------------------- +// LLVisualParamInfo +// Contains shared data for VisualParams +//----------------------------------------------------------------------------- +class LLVisualParamInfo +{ + friend class LLVisualParam; +public: + LLVisualParamInfo(); + virtual ~LLVisualParamInfo() {}; + + virtual BOOL parseXml(LLXmlTreeNode *node); + +protected: + S32 mID; // ID associated with VisualParam + + LLString mName; // name (for internal purposes) + LLString mDisplayName; // name displayed to the user + LLString mMinName; // name associated with minimum value + LLString mMaxName; // name associated with maximum value + EVisualParamGroup mGroup; // morph group for separating UI controls + F32 mMinWeight; // minimum weight that can be assigned to this morph target + F32 mMaxWeight; // maximum weight that can be assigned to this morph target + F32 mDefaultWeight; + ESex mSex; // Which gender(s) this param applies to. +}; + +//----------------------------------------------------------------------------- +// LLVisualParam +// VIRTUAL CLASS +// An interface class for a generalized parametric modification of the avatar mesh +// Contains data that is specific to each Avatar +//----------------------------------------------------------------------------- +class LLVisualParam +{ +public: + LLVisualParam(); + virtual ~LLVisualParam(); + + // Special: These functions are overridden by child classes + // (They can not be virtual because they use specific derived Info classes) + LLVisualParamInfo* getInfo() const { return mInfo; } + // This sets mInfo and calls initialization functions + BOOL setInfo(LLVisualParamInfo *info); + + // Virtual functions + // Pure virtuals + //virtual BOOL parseData( LLXmlTreeNode *node ) = 0; + virtual void apply( ESex avatar_sex ) = 0; + // Default functions + virtual void setWeight(F32 weight, BOOL set_by_user); + virtual void setAnimationTarget( F32 target_value, BOOL set_by_user ); + virtual void animate(F32 delta, BOOL set_by_user); + virtual void stopAnimating(BOOL set_by_user); + + // Interface methods + const S32 getID() { return mID; } + void setID(S32 id) { llassert(!mInfo); mID = id; } + + const LLString& getName() const { return mInfo->mName; } + const LLString& getDisplayName() const { return mInfo->mDisplayName; } + const LLString& getMaxDisplayName() const { return mInfo->mMaxName; } + const LLString& getMinDisplayName() const { return mInfo->mMinName; } + + void setDisplayName(const char* s) { mInfo->mDisplayName = s; } + void setMaxDisplayName(const char* s) { mInfo->mMaxName = s; } + void setMinDisplayName(const char* s) { mInfo->mMinName = s; } + + const EVisualParamGroup getGroup() { return mInfo->mGroup; } + F32 getMinWeight() { return mInfo->mMinWeight; } + F32 getMaxWeight() { return mInfo->mMaxWeight; } + F32 getDefaultWeight() { return mInfo->mDefaultWeight; } + ESex getSex() { return mInfo->mSex; } + + F32 getWeight() { return mIsAnimating ? mTargetWeight : mCurWeight; } + F32 getCurrentWeight() { return mCurWeight; } + F32 getLastWeight() { return mLastWeight; } + BOOL isAnimating() { return mIsAnimating; } + + LLVisualParam* getNextParam() { return mNext; } + void setNextParam( LLVisualParam *next ); + + virtual void setAnimating(BOOL is_animating) { mIsAnimating = is_animating; } + BOOL getAnimating() { return mIsAnimating; } + +protected: + F32 mCurWeight; // current weight + F32 mLastWeight; // last weight + LLVisualParam* mNext; // next param in a shared chain + F32 mTargetWeight; // interpolation target + BOOL mIsAnimating; // this value has been given an interpolation target + + S32 mID; // id for storing weight/morphtarget compares compactly + LLVisualParamInfo *mInfo; +}; + +#endif // LL_LLVisualParam_H diff --git a/indra/llcommon/bitpack.cpp b/indra/llcommon/bitpack.cpp new file mode 100644 index 0000000000..4acd533600 --- /dev/null +++ b/indra/llcommon/bitpack.cpp @@ -0,0 +1,148 @@ +/** + * @file bitpack.cpp + * @brief Convert data to packed bit stream + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "bitpack.h" + +#if 0 +#include +#include +#include "stdtypes.h" + +U8 gLoad, gUnLoad; +U32 gLoadSize, gUnLoadSize, gTotalBits; + +const U32 gMaxDataBits = 8; + +///////////////////////////////////////////////////////////////////////////////////////// +#if 0 +void bit_pack(U8 *outbase, U32 *outptr, U8 *total_data, U32 total_dsize) +{ + U32 max_data_bits = gMaxDataBits; + U32 load_size = gLoadSize, total_bits = gTotalBits; + U32 dsize; + + U8 data; + U8 load = gLoad; + + while (total_dsize > 0) + { + if (total_dsize > max_data_bits) + { + dsize = max_data_bits; + total_dsize -= max_data_bits; + } + else + { + dsize = total_dsize; + total_dsize = 0; + } + + data = *total_data++; + + data <<= (max_data_bits - dsize); + while (dsize > 0) + { + if (load_size == max_data_bits) + { + *(outbase + (*outptr)++) = load; + load_size = 0; + load = 0x00; + } + load <<= 1; + load |= (data >> (max_data_bits - 1)); + data <<= 1; + load_size++; + total_bits++; + dsize--; + } + } + + gLoad = load; + gLoadSize = load_size; + gTotalBits = total_bits; +} +#endif + +///////////////////////////////////////////////////////////////////////////////////////// + +void bit_pack_reset() +{ + gLoad = 0x0; + gLoadSize = 0; + gTotalBits = 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void bit_pack_flush(U8 *outbase, U32 *outptr) +{ + if (gLoadSize) + { + gTotalBits += gLoadSize; + gLoad <<= (gMaxDataBits - gLoadSize); + outbase[(*outptr)++] = gLoad; + gLoadSize = 0; + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +#if 0 +void bit_unpack(U8 *total_retval, U32 total_dsize, U8 *indata, U32 *inptr) +{ + U32 max_data_bits = gMaxDataBits; + U32 unload_size = gUnLoadSize; + U32 dsize; + U8 *retval; + U8 unload = gUnLoad; + + while (total_dsize > 0) + { + if (total_dsize > max_data_bits) + { + dsize = max_data_bits; + total_dsize -= max_data_bits; + } + else + { + dsize = total_dsize; + total_dsize = 0; + } + + retval = total_data++; + *retval = 0x00; + while (dsize > 0) + { + if (unload_size == 0) + { + unload = indata[(*inptr)++]; + unload_size = max_data_bits; + } + *retval <<= 1; + *retval |= (unload >> (max_data_bits - 1)); + unload_size--; + unload <<= 1; + dsize--; + } + } + + gUnLoad = unload; + gUnLoadSize = unload_size; +} +#endif + + +/////////////////////////////////////////////////////////////////////////////////////// + +void bit_unpack_reset() +{ + gUnLoad = 0; + gUnLoadSize = 0; +} +#endif diff --git a/indra/llcommon/bitpack.h b/indra/llcommon/bitpack.h new file mode 100644 index 0000000000..2303aad8ea --- /dev/null +++ b/indra/llcommon/bitpack.h @@ -0,0 +1,190 @@ +/** + * @file bitpack.h + * @brief Convert data to packed bit stream + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_BITPACK_H +#define LL_BITPACK_H + +#include "llerror.h" + +const U32 MAX_DATA_BITS = 8; + + +class LLBitPack +{ +public: + LLBitPack(U8 *buffer, U32 max_size) : mBuffer(buffer), mBufferSize(0), mLoad(0), mLoadSize(0), mTotalBits(0), mMaxSize(max_size) + { + } + + ~LLBitPack() + { + } + + void resetBitPacking() + { + mLoad = 0; + mLoadSize = 0; + mTotalBits = 0; + mBufferSize = 0; + } + + U32 bitPack(U8 *total_data, U32 total_dsize) + { + U32 dsize; + U8 data; + + while (total_dsize > 0) + { + if (total_dsize > MAX_DATA_BITS) + { + dsize = MAX_DATA_BITS; + total_dsize -= MAX_DATA_BITS; + } + else + { + dsize = total_dsize; + total_dsize = 0; + } + + data = *total_data++; + + data <<= (MAX_DATA_BITS - dsize); + while (dsize > 0) + { + if (mLoadSize == MAX_DATA_BITS) + { + *(mBuffer + mBufferSize++) = mLoad; + if (mBufferSize > mMaxSize) + { + llerror("mBufferSize exceeding mMaxSize!", 0); + } + mLoadSize = 0; + mLoad = 0x00; + } + mLoad <<= 1; + mLoad |= (data >> (MAX_DATA_BITS - 1)); + data <<= 1; + mLoadSize++; + mTotalBits++; + dsize--; + } + } + return mBufferSize; + } + + U32 bitCopy(U8 *total_data, U32 total_dsize) + { + U32 dsize; + U8 data; + + while (total_dsize > 0) + { + if (total_dsize > MAX_DATA_BITS) + { + dsize = MAX_DATA_BITS; + total_dsize -= MAX_DATA_BITS; + } + else + { + dsize = total_dsize; + total_dsize = 0; + } + + data = *total_data++; + + while (dsize > 0) + { + if (mLoadSize == MAX_DATA_BITS) + { + *(mBuffer + mBufferSize++) = mLoad; + if (mBufferSize > mMaxSize) + { + llerror("mBufferSize exceeding mMaxSize!", 0); + } + mLoadSize = 0; + mLoad = 0x00; + } + mLoad <<= 1; + mLoad |= (data >> (MAX_DATA_BITS - 1)); + data <<= 1; + mLoadSize++; + mTotalBits++; + dsize--; + } + } + return mBufferSize; + } + + U32 bitUnpack(U8 *total_retval, U32 total_dsize) + { + U32 dsize; + U8 *retval; + + while (total_dsize > 0) + { + if (total_dsize > MAX_DATA_BITS) + { + dsize = MAX_DATA_BITS; + total_dsize -= MAX_DATA_BITS; + } + else + { + dsize = total_dsize; + total_dsize = 0; + } + + retval = total_retval++; + *retval = 0x00; + while (dsize > 0) + { + if (mLoadSize == 0) + { +#ifdef _DEBUG + if (mBufferSize > mMaxSize) + { + llerrs << "mBufferSize exceeding mMaxSize" << llendl; + llerrs << mBufferSize << " > " << mMaxSize << llendl; + } +#endif + mLoad = *(mBuffer + mBufferSize++); + mLoadSize = MAX_DATA_BITS; + } + *retval <<= 1; + *retval |= (mLoad >> (MAX_DATA_BITS - 1)); + mLoadSize--; + mLoad <<= 1; + dsize--; + } + } + return mBufferSize; + } + + U32 flushBitPack() + { + if (mLoadSize) + { + mLoad <<= (MAX_DATA_BITS - mLoadSize); + *(mBuffer + mBufferSize++) = mLoad; + if (mBufferSize > mMaxSize) + { + llerror("mBufferSize exceeding mMaxSize!", 0); + } + mLoadSize = 0; + } + return mBufferSize; + } + + U8 *mBuffer; + U32 mBufferSize; + U8 mLoad; + U32 mLoadSize; + U32 mTotalBits; + U32 mMaxSize; +}; + +#endif diff --git a/indra/llcommon/ctype_workaround.h b/indra/llcommon/ctype_workaround.h new file mode 100644 index 0000000000..8c52b0290c --- /dev/null +++ b/indra/llcommon/ctype_workaround.h @@ -0,0 +1,36 @@ +/** + * @file ctype_workaround.h + * @brief The workaround is to create some legacy symbols that point + * to the correct symbols, which avoids link errors. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef _CTYPE_WORKAROUND_H_ +#define _CTYPE_WORKAROUND_H_ + +/** + * the CTYPE_WORKAROUND is needed for linux dev stations that don't + * have the broken libc6 packages needed by our out-of-date static + * libs (such as libcrypto and libcurl). + * + * -- Leviathan 20060113 +*/ + +#include + +__const unsigned short int *__ctype_b; +__const __int32_t *__ctype_tolower; +__const __int32_t *__ctype_toupper; + +// call this function at the beginning of main() +void ctype_workaround() +{ + __ctype_b = *(__ctype_b_loc()); + __ctype_toupper = *(__ctype_toupper_loc()); + __ctype_tolower = *(__ctype_tolower_loc()); +} + +#endif + diff --git a/indra/llcommon/doublelinkedlist.h b/indra/llcommon/doublelinkedlist.h new file mode 100644 index 0000000000..2546d621d0 --- /dev/null +++ b/indra/llcommon/doublelinkedlist.h @@ -0,0 +1,1379 @@ +/** + * @file doublelinkedlist.h + * @brief Provides a standard doubly linked list for fun and profit. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_DOUBLELINKEDLIST_H +#define LL_DOUBLELINKEDLIST_H + +#include "llerror.h" +#include "llrand.h" + +// node that actually contains the data +template class LLDoubleLinkedNode +{ +public: + DATA_TYPE *mDatap; + LLDoubleLinkedNode *mNextp; + LLDoubleLinkedNode *mPrevp; + + +public: + // assign the mDatap pointer + LLDoubleLinkedNode(DATA_TYPE *data); + + // destructor does not, by default, destroy associated data + // however, the mDatap must be NULL to ensure that we aren't causing memory leaks + ~LLDoubleLinkedNode(); + + // delete associated data and NULL out pointer + void deleteData(); + + // remove associated data and NULL out pointer + void removeData(); +}; + + +const U32 LLDOUBLE_LINKED_LIST_STATE_STACK_DEPTH = 4; + +template class LLDoubleLinkedList +{ +private: + LLDoubleLinkedNode mHead; // head node + LLDoubleLinkedNode mTail; // tail node + LLDoubleLinkedNode *mQueuep; // The node in the batter's box + LLDoubleLinkedNode *mCurrentp; // The node we're talking about + + // The state stack allows nested exploration of the LLDoubleLinkedList + // but should be used with great care + LLDoubleLinkedNode *mQueuepStack[LLDOUBLE_LINKED_LIST_STATE_STACK_DEPTH]; + LLDoubleLinkedNode *mCurrentpStack[LLDOUBLE_LINKED_LIST_STATE_STACK_DEPTH]; + U32 mStateStackDepth; + U32 mCount; + + // mInsertBefore is a pointer to a user-set function that returns + // TRUE if "first" should be located before "second" + // NOTE: mInsertBefore() should never return TRUE when ("first" == "second") + // or never-ending loops can occur + BOOL (*mInsertBefore)(DATA_TYPE *first, DATA_TYPE *second); + +public: + LLDoubleLinkedList(); + + // destructor destroys list and nodes, but not data in nodes + ~LLDoubleLinkedList(); + + // put data into a node and stick it at the front of the list + // set mCurrentp to mQueuep + void addData(DATA_TYPE *data); + + // put data into a node and stick it at the end of the list + // set mCurrentp to mQueuep + void addDataAtEnd(DATA_TYPE *data); + + S32 getLength() const; + // search the list starting at mHead.mNextp and remove the link with mDatap == data + // set mCurrentp to mQueuep + // return TRUE if found, FALSE if not found + BOOL removeData(const DATA_TYPE *data); + + // search the list starting at mHead.mNextp and delete the link with mDatap == data + // set mCurrentp to mQueuep + // return TRUE if found, FALSE if not found + BOOL deleteData(DATA_TYPE *data); + + // remove all nodes from the list and delete the associated data + void deleteAllData(); + + // remove all nodes from the list but do not delete data + void removeAllNodes(); + + BOOL isEmpty(); + + // check to see if data is in list + // set mCurrentp and mQueuep to the target of search if found, otherwise set mCurrentp to mQueuep + // return TRUE if found, FALSE if not found + BOOL checkData(const DATA_TYPE *data); + + // NOTE: This next two funtions are only included here + // for those too familiar with the LLLinkedList template class. + // They are depreciated. resetList() is unecessary while + // getCurrentData() is identical to getNextData() and has + // a misleading name. + // + // The recommended way to loop through a list is as follows: + // + // datap = list.getFirstData(); + // while (datap) + // { + // /* do stuff */ + // datap = list.getNextData(); + // } + + // place mQueuep on mHead node + void resetList(); + + // return the data currently pointed to, + // set mCurrentp to that node and bump mQueuep down the list + // NOTE: this function is identical to getNextData() + DATA_TYPE *getCurrentData(); + + + // reset the list and return the data currently pointed to, + // set mCurrentp to that node and bump mQueuep down the list + DATA_TYPE *getFirstData(); + + + // reset the list and return the data at position n, set mCurentp + // to that node and bump mQueuep down the list + // Note: n=0 will behave like getFirstData() + DATA_TYPE *getNthData(U32 n); + + // reset the list and return the last data in it, + // set mCurrentp to that node and bump mQueuep up the list + DATA_TYPE *getLastData(); + + // return data in mQueuep, + // set mCurrentp mQueuep and bump mQueuep down the list + DATA_TYPE *getNextData(); + + // return the data in mQueuep, + // set mCurrentp to mQueuep and bump mQueuep up the list + DATA_TYPE *getPreviousData(); + + // remove the Node at mCurrentp + // set mCurrentp to mQueuep + void removeCurrentData(); + + // delete the Node at mCurrentp + // set mCurrentp to mQueuep + void deleteCurrentData(); + + // remove the Node at mCurrentp and insert it into newlist + // set mCurrentp to mQueuep + void moveCurrentData(LLDoubleLinkedList *newlist); + + // insert the node in front of mCurrentp + // set mCurrentp to mQueuep + void insertNode(LLDoubleLinkedNode *node); + + // insert the data in front of mCurrentp + // set mCurrentp to mQueuep + void insertData(DATA_TYPE *data); + + // if mCurrentp has a previous node then : + // * swaps mCurrentp with its previous + // * set mCurrentp to mQueuep + // (convenient for forward bubble-sort) + // otherwise does nothing + void swapCurrentWithPrevious(); + + // if mCurrentp has a next node then : + // * swaps mCurrentp with its next + // * set mCurrentp to mQueuep + // (convenient for backwards bubble-sort) + // otherwise does nothing + void swapCurrentWithNext(); + + // move mCurrentp to the front of the list + // set mCurrentp to mQueuep + void moveCurrentToFront(); + + // move mCurrentp to the end of the list + // set mCurrentp to mQueuep + void moveCurrentToEnd(); + + // set mInsertBefore + void setInsertBefore(BOOL (*insert_before)(DATA_TYPE *first, DATA_TYPE *second)); + + // add data in front of first node for which mInsertBefore(datap, node->mDatap) returns TRUE + // set mCurrentp to mQueuep + BOOL addDataSorted(DATA_TYPE *datap); + + // sort the list using bubble-sort + // Yes, this is a different name than the same function in LLLinkedList. + // When it comes time for a name consolidation hopefully this one will win. + BOOL bubbleSort(); + + // does a single bubble sort pass on the list + BOOL lazyBubbleSort(); + + // returns TRUE if state successfully pushed (state stack not full) + BOOL pushState(); + + // returns TRUE if state successfully popped (state stack not empty) + BOOL popState(); + + // empties the state stack + void clearStateStack(); + + // randomly move the the links in the list for debug or (Discordian) purposes + // sets mCurrentp and mQueuep to top of list + void scramble(); + +private: + // add node to beginning of list + // set mCurrentp to mQueuep + void addNode(LLDoubleLinkedNode *node); + + // add node to end of list + // set mCurrentp to mQueuep + void addNodeAtEnd(LLDoubleLinkedNode *node); +}; + +//#endif + +//////////////////////////////////////////////////////////////////////////////////////////// + +// doublelinkedlist.cpp +// LLDoubleLinkedList template class implementation file. +// Provides a standard doubly linked list for fun and profit. +// +// Copyright 2001, Linden Research, Inc. + +//#include "llerror.h" +//#include "doublelinkedlist.h" + +////////////////////////////////////////////////////////////////////////////////////////// +// LLDoubleLinkedNode +////////////////////////////////////////////////////////////////////////////////////////// + + +// assign the mDatap pointer +template +LLDoubleLinkedNode::LLDoubleLinkedNode(DATA_TYPE *data) : + mDatap(data), mNextp(NULL), mPrevp(NULL) +{ +} + + +// destructor does not, by default, destroy associated data +// however, the mDatap must be NULL to ensure that we aren't causing memory leaks +template +LLDoubleLinkedNode::~LLDoubleLinkedNode() +{ + if (mDatap) + { + llerror("Attempting to call LLDoubleLinkedNode destructor with a non-null mDatap!", 1); + } +} + + +// delete associated data and NULL out pointer +template +void LLDoubleLinkedNode::deleteData() +{ + delete mDatap; + mDatap = NULL; +} + + +template +void LLDoubleLinkedNode::removeData() +{ + mDatap = NULL; +} + + +////////////////////////////////////////////////////////////////////////////////////// +// LLDoubleLinkedList +////////////////////////////////////////////////////////////////////////////////////// + +// <------- up ------- +// +// mCurrentp +// mQueuep | +// | | +// | | +// .------. .------. .------. .------. +// | |---->| |---->| |----->| |-----> NULL +// NULL <-----| |<----| |<----| |<-----| | +// _'------' '------' '------' '------:_ +// .------. /| | | |\ .------. +// NULL <-----|mHead |/ | mQueuep \|mTail |-----> NULL +// | | mCurrentp | | +// '------' '------' +// -------- down ---------> + +template +LLDoubleLinkedList::LLDoubleLinkedList() +: mHead(NULL), mTail(NULL), mQueuep(NULL) +{ + mCurrentp = mHead.mNextp; + mQueuep = mHead.mNextp; + mStateStackDepth = 0; + mCount = 0; + mInsertBefore = NULL; +} + + +// destructor destroys list and nodes, but not data in nodes +template +LLDoubleLinkedList::~LLDoubleLinkedList() +{ + removeAllNodes(); +} + + +// put data into a node and stick it at the front of the list +// doesn't change mCurrentp nor mQueuep +template +void LLDoubleLinkedList::addData(DATA_TYPE *data) +{ + // don't allow NULL to be passed to addData + if (!data) + { + llerror("NULL pointer passed to LLDoubleLinkedList::addData()", 0); + } + + // make the new node + LLDoubleLinkedNode *temp = new LLDoubleLinkedNode (data); + + // add the node to the front of the list + temp->mPrevp = NULL; + temp->mNextp = mHead.mNextp; + mHead.mNextp = temp; + + // if there's something in the list, fix its back pointer + if (temp->mNextp) + { + temp->mNextp->mPrevp = temp; + } + // otherwise, fix the tail of the list + else + { + mTail.mPrevp = temp; + } + + mCount++; +} + + +// put data into a node and stick it at the end of the list +template +void LLDoubleLinkedList::addDataAtEnd(DATA_TYPE *data) +{ + // don't allow NULL to be passed to addData + if (!data) + { + llerror("NULL pointer passed to LLDoubleLinkedList::addData()", 0); + } + + // make the new node + LLDoubleLinkedNode *nodep = new LLDoubleLinkedNode(data); + + addNodeAtEnd(nodep); + mCount++; +} + + +// search the list starting at mHead.mNextp and remove the link with mDatap == data +// set mCurrentp to mQueuep, or NULL if mQueuep points to node with mDatap == data +// return TRUE if found, FALSE if not found +template +BOOL LLDoubleLinkedList::removeData(const DATA_TYPE *data) +{ + BOOL b_found = FALSE; + // don't allow NULL to be passed to addData + if (!data) + { + llerror("NULL pointer passed to LLDoubleLinkedList::removeData()", 0); + } + + mCurrentp = mHead.mNextp; + + while (mCurrentp) + { + if (mCurrentp->mDatap == data) + { + b_found = TRUE; + + // if there is a next one, fix it + if (mCurrentp->mNextp) + { + mCurrentp->mNextp->mPrevp = mCurrentp->mPrevp; + } + else // we are at end of list + { + mTail.mPrevp = mCurrentp->mPrevp; + } + + // if there is a previous one, fix it + if (mCurrentp->mPrevp) + { + mCurrentp->mPrevp->mNextp = mCurrentp->mNextp; + } + else // we are at beginning of list + { + mHead.mNextp = mCurrentp->mNextp; + } + + // remove the node + mCurrentp->removeData(); + delete mCurrentp; + mCount--; + break; + } + mCurrentp = mCurrentp->mNextp; + } + + // reset the list back to where it was + if (mCurrentp == mQueuep) + { + mCurrentp = mQueuep = NULL; + } + else + { + mCurrentp = mQueuep; + } + + return b_found; +} + + +// search the list starting at mHead.mNextp and delete the link with mDatap == data +// set mCurrentp to mQueuep, or NULL if mQueuep points to node with mDatap == data +// return TRUE if found, FALSE if not found +template +BOOL LLDoubleLinkedList::deleteData(DATA_TYPE *data) +{ + BOOL b_found = FALSE; + // don't allow NULL to be passed to addData + if (!data) + { + llerror("NULL pointer passed to LLDoubleLinkedList::deleteData()", 0); + } + + mCurrentp = mHead.mNextp; + + while (mCurrentp) + { + if (mCurrentp->mDatap == data) + { + b_found = TRUE; + + // if there is a next one, fix it + if (mCurrentp->mNextp) + { + mCurrentp->mNextp->mPrevp = mCurrentp->mPrevp; + } + else // we are at end of list + { + mTail.mPrevp = mCurrentp->mPrevp; + } + + // if there is a previous one, fix it + if (mCurrentp->mPrevp) + { + mCurrentp->mPrevp->mNextp = mCurrentp->mNextp; + } + else // we are at beginning of list + { + mHead.mNextp = mCurrentp->mNextp; + } + + // remove the node + mCurrentp->deleteData(); + delete mCurrentp; + mCount--; + break; + } + mCurrentp = mCurrentp->mNextp; + } + + // reset the list back to where it was + if (mCurrentp == mQueuep) + { + mCurrentp = mQueuep = NULL; + } + else + { + mCurrentp = mQueuep; + } + + return b_found; +} + + +// remove all nodes from the list and delete the associated data +template +void LLDoubleLinkedList::deleteAllData() +{ + mCurrentp = mHead.mNextp; + + while (mCurrentp) + { + mQueuep = mCurrentp->mNextp; + mCurrentp->deleteData(); + delete mCurrentp; + mCurrentp = mQueuep; + } + + // reset mHead and mQueuep + mHead.mNextp = NULL; + mTail.mPrevp = NULL; + mCurrentp = mHead.mNextp; + mQueuep = mHead.mNextp; + mStateStackDepth = 0; + mCount = 0; +} + + +// remove all nodes from the list but do not delete associated data +template +void LLDoubleLinkedList::removeAllNodes() +{ + mCurrentp = mHead.mNextp; + + while (mCurrentp) + { + mQueuep = mCurrentp->mNextp; + mCurrentp->removeData(); + delete mCurrentp; + mCurrentp = mQueuep; + } + + // reset mHead and mCurrentp + mHead.mNextp = NULL; + mTail.mPrevp = NULL; + mCurrentp = mHead.mNextp; + mQueuep = mHead.mNextp; + mStateStackDepth = 0; + mCount = 0; +} + +template +S32 LLDoubleLinkedList::getLength() const +{ +// U32 length = 0; +// for (LLDoubleLinkedNode* temp = mHead.mNextp; temp != NULL; temp = temp->mNextp) +// { +// length++; +// } + return mCount; +} + +// check to see if data is in list +// set mCurrentp and mQueuep to the target of search if found, otherwise set mCurrentp to mQueuep +// return TRUE if found, FALSE if not found +template +BOOL LLDoubleLinkedList::checkData(const DATA_TYPE *data) +{ + mCurrentp = mHead.mNextp; + + while (mCurrentp) + { + if (mCurrentp->mDatap == data) + { + mQueuep = mCurrentp; + return TRUE; + } + mCurrentp = mCurrentp->mNextp; + } + + mCurrentp = mQueuep; + return FALSE; +} + +// NOTE: This next two funtions are only included here +// for those too familiar with the LLLinkedList template class. +// They are depreciated. resetList() is unecessary while +// getCurrentData() is identical to getNextData() and has +// a misleading name. +// +// The recommended way to loop through a list is as follows: +// +// datap = list.getFirstData(); +// while (datap) +// { +// /* do stuff */ +// datap = list.getNextData(); +// } + + // place mCurrentp and mQueuep on first node + template + void LLDoubleLinkedList::resetList() + { + mCurrentp = mHead.mNextp; + mQueuep = mHead.mNextp; + mStateStackDepth = 0; + } + + + // return the data currently pointed to, + // set mCurrentp to that node and bump mQueuep down the list + template + DATA_TYPE* LLDoubleLinkedList::getCurrentData() + { + if (mQueuep) + { + mCurrentp = mQueuep; + mQueuep = mQueuep->mNextp; + return mCurrentp->mDatap; + } + else + { + return NULL; + } + } + + +// reset the list and return the data currently pointed to, +// set mCurrentp to that node and bump mQueuep down the list +template +DATA_TYPE* LLDoubleLinkedList::getFirstData() +{ + mQueuep = mHead.mNextp; + mCurrentp = mQueuep; + if (mQueuep) + { + mQueuep = mQueuep->mNextp; + return mCurrentp->mDatap; + } + else + { + return NULL; + } +} + + +// reset the list and return the data at position n, set mCurentp +// to that node and bump mQueuep down the list +// Note: n=0 will behave like getFirstData() +template +DATA_TYPE* LLDoubleLinkedList::getNthData(U32 n) +{ + mCurrentp = mHead.mNextp; + + if (mCurrentp) + { + for (U32 i=0; imNextp; + if (!mCurrentp) + { + break; + } + } + } + + if (mCurrentp) + { + // bump mQueuep down the list + mQueuep = mCurrentp->mNextp; + return mCurrentp->mDatap; + } + else + { + mQueuep = NULL; + return NULL; + } +} + + +// reset the list and return the last data in it, +// set mCurrentp to that node and bump mQueuep up the list +template +DATA_TYPE* LLDoubleLinkedList::getLastData() +{ + mQueuep = mTail.mPrevp; + mCurrentp = mQueuep; + if (mQueuep) + { + mQueuep = mQueuep->mPrevp; + return mCurrentp->mDatap; + } + else + { + return NULL; + } +} + + +// return the data in mQueuep, +// set mCurrentp to mQueuep and bump mQueuep down the list +template +DATA_TYPE* LLDoubleLinkedList::getNextData() +{ + if (mQueuep) + { + mCurrentp = mQueuep; + mQueuep = mQueuep->mNextp; + return mCurrentp->mDatap; + } + else + { + return NULL; + } +} + + +// return the data in mQueuep, +// set mCurrentp to mQueuep and bump mQueuep up the list +template +DATA_TYPE* LLDoubleLinkedList::getPreviousData() +{ + if (mQueuep) + { + mCurrentp = mQueuep; + mQueuep = mQueuep->mPrevp; + return mCurrentp->mDatap; + } + else + { + return NULL; + } +} + + +// remove the Node at mCurrentp +// set mCurrentp to mQueuep, or NULL if (mCurrentp == mQueuep) +template +void LLDoubleLinkedList::removeCurrentData() +{ + if (mCurrentp) + { + // if there is a next one, fix it + if (mCurrentp->mNextp) + { + mCurrentp->mNextp->mPrevp = mCurrentp->mPrevp; + } + else // otherwise we are at end of list + { + mTail.mPrevp = mCurrentp->mPrevp; + } + + // if there is a previous one, fix it + if (mCurrentp->mPrevp) + { + mCurrentp->mPrevp->mNextp = mCurrentp->mNextp; + } + else // otherwise we are at beginning of list + { + mHead.mNextp = mCurrentp->mNextp; + } + + // remove the node + mCurrentp->removeData(); + delete mCurrentp; + mCount--; + + // check for redundant pointing + if (mCurrentp == mQueuep) + { + mCurrentp = mQueuep = NULL; + } + else + { + mCurrentp = mQueuep; + } + } +} + + +// delete the Node at mCurrentp +// set mCurrentp to mQueuep, or NULL if (mCurrentp == mQueuep) +template +void LLDoubleLinkedList::deleteCurrentData() +{ + if (mCurrentp) + { + // remove the node + // if there is a next one, fix it + if (mCurrentp->mNextp) + { + mCurrentp->mNextp->mPrevp = mCurrentp->mPrevp; + } + else // otherwise we are at end of list + { + mTail.mPrevp = mCurrentp->mPrevp; + } + + // if there is a previous one, fix it + if (mCurrentp->mPrevp) + { + mCurrentp->mPrevp->mNextp = mCurrentp->mNextp; + } + else // otherwise we are at beginning of list + { + mHead.mNextp = mCurrentp->mNextp; + } + + // remove the LLDoubleLinkedNode + mCurrentp->deleteData(); + delete mCurrentp; + mCount--; + + // check for redundant pointing + if (mCurrentp == mQueuep) + { + mCurrentp = mQueuep = NULL; + } + else + { + mCurrentp = mQueuep; + } + } +} + + +// remove the Node at mCurrentp and insert it into newlist +// set mCurrentp to mQueuep, or NULL if (mCurrentp == mQueuep) +template +void LLDoubleLinkedList::moveCurrentData(LLDoubleLinkedList *newlist) +{ + if (mCurrentp) + { + // remove the node + // if there is a next one, fix it + if (mCurrentp->mNextp) + { + mCurrentp->mNextp->mPrevp = mCurrentp->mPrevp; + } + else // otherwise we are at end of list + { + mTail.mPrevp = mCurrentp->mPrevp; + } + + // if there is a previous one, fix it + if (mCurrentp->mPrevp) + { + mCurrentp->mPrevp->mNextp = mCurrentp->mNextp; + } + else // otherwise we are at beginning of list + { + mHead.mNextp = mCurrentp->mNextp; + } + + // move the node to the new list + newlist->addNode(mCurrentp); + + // check for redundant pointing + if (mCurrentp == mQueuep) + { + mCurrentp = mQueuep = NULL; + } + else + { + mCurrentp = mQueuep; + } + } +} + + +// Inserts the node previous to mCurrentp +// set mCurrentp to mQueuep +template +void LLDoubleLinkedList::insertNode(LLDoubleLinkedNode *nodep) +{ + // don't allow pointer to NULL to be passed + if (!nodep) + { + llerror("NULL pointer passed to LLDoubleLinkedList::insertNode()", 0); + } + if (!nodep->mDatap) + { + llerror("NULL data pointer passed to LLDoubleLinkedList::insertNode()", 0); + } + + if (mCurrentp) + { + if (mCurrentp->mPrevp) + { + nodep->mPrevp = mCurrentp->mPrevp; + nodep->mNextp = mCurrentp; + mCurrentp->mPrevp->mNextp = nodep; + mCurrentp->mPrevp = nodep; + } + else // at beginning of list + { + nodep->mPrevp = NULL; + nodep->mNextp = mCurrentp; + mHead.mNextp = nodep; + mCurrentp->mPrevp = nodep; + } + mCurrentp = mQueuep; + } + else // add to front of list + { + addNode(nodep); + } +} + + +// insert the data in front of mCurrentp +// set mCurrentp to mQueuep +template +void LLDoubleLinkedList::insertData(DATA_TYPE *data) +{ + if (!data) + { + llerror("NULL data pointer passed to LLDoubleLinkedList::insertNode()", 0); + } + LLDoubleLinkedNode *node = new LLDoubleLinkedNode(data); + insertNode(node); + mCount++; +} + + +// if mCurrentp has a previous node then : +// * swaps mCurrentp with its previous +// * set mCurrentp to mQueuep +// otherwise does nothing +template +void LLDoubleLinkedList::swapCurrentWithPrevious() +{ + if (mCurrentp) + { + if (mCurrentp->mPrevp) + { + // Pull mCurrentp out of list + mCurrentp->mPrevp->mNextp = mCurrentp->mNextp; + if (mCurrentp->mNextp) + { + mCurrentp->mNextp->mPrevp = mCurrentp->mPrevp; + } + else // mCurrentp was at end of list + { + mTail.mPrevp = mCurrentp->mPrevp; + } + + // Fix mCurrentp's pointers + mCurrentp->mNextp = mCurrentp->mPrevp; + mCurrentp->mPrevp = mCurrentp->mNextp->mPrevp; + mCurrentp->mNextp->mPrevp = mCurrentp; + + if (mCurrentp->mPrevp) + { + // Fix the backward pointer of mCurrentp's new previous + mCurrentp->mPrevp->mNextp = mCurrentp; + } + else // mCurrentp is now at beginning of list + { + mHead.mNextp = mCurrentp; + } + + // Set the list back to the way it was + mCurrentp = mQueuep; + } + } +} + + +// if mCurrentp has a next node then : +// * swaps mCurrentp with its next +// * set mCurrentp to mQueuep +// otherwise does nothing +template +void LLDoubleLinkedList::swapCurrentWithNext() +{ + if (mCurrentp) + { + if (mCurrentp->mNextp) + { + // Pull mCurrentp out of list + mCurrentp->mNextp->mPrevp = mCurrentp->mPrevp; + if (mCurrentp->mPrevp) + { + mCurrentp->mPrevp->mNextp = mCurrentp->mNextp; + } + else // mCurrentp was at beginning of list + { + mHead.mNextp = mCurrentp->mNextp; + } + + // Fix mCurrentp's pointers + mCurrentp->mPrevp = mCurrentp->mNextp; + mCurrentp->mNextp = mCurrentp->mPrevp->mNextp; + mCurrentp->mPrevp->mNextp = mCurrentp; + + if (mCurrentp->mNextp) + { + // Fix the back pointer of mCurrentp's new next + mCurrentp->mNextp->mPrevp = mCurrentp; + } + else // mCurrentp is now at end of list + { + mTail.mPrevp = mCurrentp; + } + + // Set the list back to the way it was + mCurrentp = mQueuep; + } + } +} + +// move mCurrentp to the front of the list +// set mCurrentp to mQueuep +template +void LLDoubleLinkedList::moveCurrentToFront() +{ + if (mCurrentp) + { + // if there is a previous one, fix it + if (mCurrentp->mPrevp) + { + mCurrentp->mPrevp->mNextp = mCurrentp->mNextp; + } + else // otherwise we are at beginning of list + { + // check for redundant pointing + if (mCurrentp == mQueuep) + { + mCurrentp = mQueuep = NULL; + } + else + { + mCurrentp = mQueuep; + } + return; + } + + // if there is a next one, fix it + if (mCurrentp->mNextp) + { + mCurrentp->mNextp->mPrevp = mCurrentp->mPrevp; + } + else // otherwise we are at end of list + { + mTail.mPrevp = mCurrentp->mPrevp; + } + + // add mCurrentp to beginning of list + mCurrentp->mNextp = mHead.mNextp; + mHead.mNextp->mPrevp = mCurrentp; // mHead.mNextp MUST be valid, + // or the list had only one node + // and we would have returned already + mCurrentp->mPrevp = NULL; + mHead.mNextp = mCurrentp; + + // check for redundant pointing + if (mCurrentp == mQueuep) + { + mCurrentp = mQueuep = NULL; + } + else + { + mCurrentp = mQueuep; + } + } + +} + +// move mCurrentp to the end of the list +// set mCurrentp to mQueuep +template +void LLDoubleLinkedList::moveCurrentToEnd() +{ + if (mCurrentp) + { + // if there is a next one, fix it + if (mCurrentp->mNextp) + { + mCurrentp->mNextp->mPrevp = mCurrentp->mPrevp; + } + else // otherwise we are at end of list and we're done + { + // check for redundant pointing + if (mCurrentp == mQueuep) + { + mCurrentp = mQueuep = NULL; + } + else + { + mCurrentp = mQueuep; + } + return; + } + + // if there is a previous one, fix it + if (mCurrentp->mPrevp) + { + mCurrentp->mPrevp->mNextp = mCurrentp->mNextp; + } + else // otherwise we are at beginning of list + { + mHead.mNextp = mCurrentp->mNextp; + } + + // add mCurrentp to end of list + mCurrentp->mPrevp = mTail.mPrevp; + mTail.mPrevp->mNextp = mCurrentp; // mTail.mPrevp MUST be valid, + // or the list had only one node + // and we would have returned already + mCurrentp->mNextp = NULL; + mTail.mPrevp = mCurrentp; + + // check for redundant pointing + if (mCurrentp == mQueuep) + { + mCurrentp = mQueuep = NULL; + } + else + { + mCurrentp = mQueuep; + } + } +} + + +template +void LLDoubleLinkedList::setInsertBefore(BOOL (*insert_before)(DATA_TYPE *first, DATA_TYPE *second) ) +{ + mInsertBefore = insert_before; +} + + +// add data in front of the first node for which mInsertBefore(datap, node->mDatap) returns TRUE +// set mCurrentp to mQueuep +template +BOOL LLDoubleLinkedList::addDataSorted(DATA_TYPE *datap) +{ + // don't allow NULL to be passed to addData() + if (!datap) + { + llerror("NULL pointer passed to LLDoubleLinkedList::addDataSorted()", 0); + } + + // has mInsertBefore not been set? + if (!mInsertBefore) + { + addData(datap); + return FALSE; + } + + // is the list empty? + if (!mHead.mNextp) + { + addData(datap); + return TRUE; + } + + // Note: this step has been added so that the behavior of LLDoubleLinkedList + // is as rigorous as the LLLinkedList class about adding duplicate nodes. + // Duplicate nodes can cause a problem when sorting if mInsertBefore(foo, foo) + // returns TRUE. However, if mInsertBefore(foo, foo) returns FALSE, then there + // shouldn't be any reason to exclude duplicate nodes (as we do here). + if (checkData(datap)) + { + return FALSE; + } + + mCurrentp = mHead.mNextp; + while (mCurrentp) + { + // check to see if datap is already in the list + if (datap == mCurrentp->mDatap) + { + return FALSE; + } + else if (mInsertBefore(datap, mCurrentp->mDatap)) + { + insertData(datap); + return TRUE; + } + mCurrentp = mCurrentp->mNextp; + } + + addDataAtEnd(datap); + return TRUE; +} + + +// bubble-sort until sorted and return TRUE if anything was sorted +// leaves mQueuep pointing at last node that was swapped with its mNextp +// +// NOTE: if you find this function looping for really long times, then you +// probably need to check your implementation of mInsertBefore(a,b) and make +// sure it does not return TRUE when (a == b)! +template +BOOL LLDoubleLinkedList::bubbleSort() +{ + BOOL b_swapped = FALSE; + U32 count = 0; + while (lazyBubbleSort()) + { + b_swapped = TRUE; + if (count++ > 0x7FFFFFFF) + { + llwarning("LLDoubleLinkedList::bubbleSort() : too many passes...", 1); + llwarning(" make sure the mInsertBefore(a, b) does not return TRUE for a == b", 1); + break; + } + } + return b_swapped; +} + + +// do a single bubble-sort pass and return TRUE if anything was sorted +// leaves mQueuep pointing at last node that was swapped with its mNextp +template +BOOL LLDoubleLinkedList::lazyBubbleSort() +{ + // has mInsertBefore been set? + if (!mInsertBefore) + { + return FALSE; + } + + // is list empty? + mCurrentp = mHead.mNextp; + if (!mCurrentp) + { + return FALSE; + } + + BOOL b_swapped = FALSE; + + // the sort will exit after 0x7FFFFFFF nodes or the end of the list, whichever is first + S32 length = 0x7FFFFFFF; + S32 count = 0; + + while (mCurrentp && mCurrentp->mNextp && countmNextp->mDatap, mCurrentp->mDatap)) + { + b_swapped = TRUE; + mQueuep = mCurrentp; + swapCurrentWithNext(); // sets mCurrentp to mQueuep + } + count++; + mCurrentp = mCurrentp->mNextp; + } + + return b_swapped; +} + + +template +BOOL LLDoubleLinkedList::pushState() +{ + if (mStateStackDepth < LLDOUBLE_LINKED_LIST_STATE_STACK_DEPTH) + { + *(mQueuepStack + mStateStackDepth) = mQueuep; + *(mCurrentpStack + mStateStackDepth) = mCurrentp; + mStateStackDepth++; + return TRUE; + } + return FALSE; +} + + +template +BOOL LLDoubleLinkedList::popState() +{ + if (mStateStackDepth > 0) + { + mStateStackDepth--; + mQueuep = *(mQueuepStack + mStateStackDepth); + mCurrentp = *(mCurrentpStack + mStateStackDepth); + return TRUE; + } + return FALSE; +} + + +template +void LLDoubleLinkedList::clearStateStack() +{ + mStateStackDepth = 0; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// private members +////////////////////////////////////////////////////////////////////////////////////////// + +// add node to beginning of list +// set mCurrentp to mQueuep +template +void LLDoubleLinkedList::addNode(LLDoubleLinkedNode *nodep) +{ + // add the node to the front of the list + nodep->mPrevp = NULL; + nodep->mNextp = mHead.mNextp; + mHead.mNextp = nodep; + + // if there's something in the list, fix its back pointer + if (nodep->mNextp) + { + nodep->mNextp->mPrevp = nodep; + } + else // otherwise fix the tail node + { + mTail.mPrevp = nodep; + } + + mCurrentp = mQueuep; +} + + +// add node to end of list +// set mCurrentp to mQueuep +template +void LLDoubleLinkedList::addNodeAtEnd(LLDoubleLinkedNode *node) +{ + // add the node to the end of the list + node->mNextp = NULL; + node->mPrevp = mTail.mPrevp; + mTail.mPrevp = node; + + // if there's something in the list, fix its back pointer + if (node->mPrevp) + { + node->mPrevp->mNextp = node; + } + else // otherwise fix the head node + { + mHead.mNextp = node; + } + + mCurrentp = mQueuep; +} + + +// randomly move nodes in the list for DEBUG (or Discordian) purposes +// sets mCurrentp and mQueuep to top of list +template +void LLDoubleLinkedList::scramble() +{ + S32 random_number; + DATA_TYPE *datap = getFirstData(); + while(datap) + { + random_number = gLindenLabRandomNumber.llrand() % 5; + + if (0 == random_number) + { + removeCurrentData(); + addData(datap); + } + else if (1 == random_number) + { + removeCurrentData(); + addDataAtEnd(datap); + } + else if (2 == random_number) + { + swapCurrentWithPrevious(); + } + else if (3 == random_number) + { + swapCurrentWithNext(); + } + datap = getNextData(); + } + mQueuep = mHead.mNextp; + mCurrentp = mQueuep; +} + +template +BOOL LLDoubleLinkedList::isEmpty() +{ + return (mCount == 0); +} + + +#endif diff --git a/indra/llcommon/imageids.h b/indra/llcommon/imageids.h new file mode 100644 index 0000000000..8147183bec --- /dev/null +++ b/indra/llcommon/imageids.h @@ -0,0 +1,79 @@ +/** + * @file imageids.h + * @brief Temporary holder for image IDs + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_IMAGEIDS_H +#define LL_IMAGEIDS_H + +#include "lluuid.h" + +// +// USE OF THIS FILE IS DEPRECATED +// +// Please use viewerart.ini and the standard +// art import path. // indicates if file is only + // on dataserver, or also + // pre-cached on viewer + +// Grass Images +//const LLUUID IMG_GRASS1 ("990c4086-46ce-49bd-8cae-afcc23a08f4e"); // dataserver +//const LLUUID IMG_GRASS2 ("869e2dcf-21b9-402d-a36d-9a23365cf723"); // dataserver +//const LLUUID IMG_GRASS3 ("8f97e7a7-f664-4967-9e8f-8d9e8039c1b7"); // dataserver + +//const LLUUID IMG_GRASS4 ("8a05131d-35b7-4812-bcfc-a989b0f954ef"); // dataserver + +//const LLUUID IMG_GRASS5 ("7d092acb-c69a-4122-b09b-f285e009b185"); // dataserver + +const LLUUID IMG_CLEAR ("11ee27f5-43c0-414e-afd5-d7f5688c351f"); // VIEWER +const LLUUID IMG_SMOKE ("b4ba225c-373f-446d-9f7e-6cb7b5cf9b3d"); // VIEWER + +const LLUUID IMG_DEFAULT ("b4ba225c-373f-446d-9f7e-6cb7b5cf9b3d"); // VIEWER + +//const LLUUID IMG_SAND ("0ff70ead-4562-45f9-9e8a-52b1a3286868"); // VIEWER 1.5k +//const LLUUID IMG_GRASS ("5ab48dd5-05d0-4f1a-ace6-efd4e2fb3508"); // VIEWER 1.2k +//const LLUUID IMG_ROCK ("402f8b24-5f9d-4905-b5f8-37baff603e88"); // VIEWER 1.2k +//const LLUUID IMG_ROCKFACE ("9c88539c-fd04-46b8-bea2-ddf1bcffe3bd"); // VIEWER 1.2k +const LLUUID IMG_SUN ("cce0f112-878f-4586-a2e2-a8f104bba271"); // dataserver +const LLUUID IMG_MOON ("d07f6eed-b96a-47cd-b51d-400ad4a1c428"); // dataserver +const LLUUID IMG_CLOUD_POOF ("fc4b9f0b-d008-45c6-96a4-01dd947ac621"); // dataserver +const LLUUID IMG_SHOT ("35f217a3-f618-49cf-bbca-c86d486551a9"); // dataserver +const LLUUID IMG_SPARK ("d2e75ac1-d0fb-4532-820e-a20034ac814d"); // dataserver +const LLUUID IMG_FIRE ("aca40aa8-44cf-44ca-a0fa-93e1a2986f82"); // dataserver +//const LLUUID IMG_WATER ("e510b068-d20d-4612-a08d-fde4d5c15789"); // VIEWER +const LLUUID IMG_FACE_SELECT ("a85ac674-cb75-4af6-9499-df7c5aaf7a28"); // face selector + +//const LLUUID IMG_SHADOW ("5e1de0a8-f9f8-4237-9396-d221126a7c4a"); // dataserver +//const LLUUID IMG_AVATARSHADOW ("c7d8bbf3-21ee-4f6e-9b20-3cf18425af1d"); // dataserver +//const LLUUID IMG_BOXSHADOW ("8d86b8cc-4889-408a-8b72-c1961bae53d7"); // dataserver +//const LLUUID IMG_EYE ("5e3551ae-9971-4814-af99-5117591e937b"); // dataserver +//const LLUUID IMG_BLUE_FLAME ("d8b62059-7b31-4511-a479-1fe45117948f"); // dataserver + +const LLUUID IMG_DEFAULT_AVATAR ("c228d1cf-4b5d-4ba8-84f4-899a0796aa97"); // dataserver +//const LLUUID IMG_ENERGY_BEAM ("09e7bc54-11b9-442a-ae3d-f52e599e466a"); // dataserver +//const LLUUID IMG_ENERGY_BEAM2 ("de651394-f926-48db-b666-e49d83af1bbc"); // dataserver + +//const LLUUID IMG_BRICK_PATH ("a9d0019b-3783-4c7f-959c-322d301918bc"); // dataserver + +const LLUUID IMG_EXPLOSION ("68edcf47-ccd7-45b8-9f90-1649d7f12806"); // On dataserver +const LLUUID IMG_EXPLOSION_2 ("21ce046c-83fe-430a-b629-c7660ac78d7c"); // On dataserver +const LLUUID IMG_EXPLOSION_3 ("fedea30a-1be8-47a6-bc06-337a04a39c4b"); // On dataserver +const LLUUID IMG_EXPLOSION_4 ("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); // On dataserver +//const LLUUID IMG_EXPLOSION_5 ("60f2dec7-675b-4950-b614-85b907d552ea"); // On dataserver +//const LLUUID IMG_SPLASH_SPRITE ("8a101f63-fe45-49e7-9f8a-e64817daa475"); // On dataserver +const LLUUID IMG_SMOKE_POOF ("1e63e323-5fe0-452e-92f8-b98bd0f764e3"); // On dataserver + +const LLUUID IMG_BIG_EXPLOSION_1 ("5e47a0dc-97bf-44e0-8b40-de06718cee9d"); // On dataserver +const LLUUID IMG_BIG_EXPLOSION_2 ("9c8eca51-53d5-42a7-bb58-cef070395db8"); // On dataserver +//const LLUUID IMG_BLUE_BLOOD ("8bc2e3f8-097e-4c87-b417-b0d699d07189"); // On dataserver + +const LLUUID IMG_BLOOM1 ("3c59f7fe-9dc8-47f9-8aaf-a9dd1fbc3bef"); +//const LLUUID IMG_BLOOM2 ("9fb76e81-eca0-4b6a-96e1-a6c5a685150b"); +//const LLUUID IMG_BLOOM3 ("fb1fecba-9585-415b-ad15-6e6e3d6c5479"); + +const LLUUID IMG_PTT_SPEAKER ("89e9fc7c-0b16-457d-be4f-136270759c4d"); // On cache + +#endif diff --git a/indra/llcommon/indra_constants.h b/indra/llcommon/indra_constants.h new file mode 100644 index 0000000000..b8df3cbfe7 --- /dev/null +++ b/indra/llcommon/indra_constants.h @@ -0,0 +1,338 @@ +/** + * @file indra_constants.h + * @brief some useful short term constants for Indra + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_INDRA_CONSTANTS_H +#define LL_INDRA_CONSTANTS_H + +#include "lluuid.h" + +// Viewer object cache version, change if object update +// format changes. JC +const U32 INDRA_OBJECT_CACHE_VERSION = 11; + +// At 45 Hz collisions seem stable and objects seem +// to settle down at a reasonable rate. +// JC 3/18/2003 +const F32 HAVOK_TIMESTEP = 1.f / 45.f; + +const F32 COLLISION_TOLERANCE = 0.1f; + +// Time constants +const U32 HOURS_PER_LINDEN_DAY = 4; +const U32 DAYS_PER_LINDEN_YEAR = 11; + +const U32 SEC_PER_LINDEN_DAY = HOURS_PER_LINDEN_DAY * 60 * 60; +const U32 SEC_PER_LINDEN_YEAR = DAYS_PER_LINDEN_YEAR * SEC_PER_LINDEN_DAY; + +const F32 REGION_WIDTH_METERS = 256.f; +const S32 REGION_WIDTH_UNITS = 256; +const U32 REGION_WIDTH_U32 = 256; + +// Bits for simulator performance query flags +enum LAND_STAT_FLAGS +{ + STAT_FILTER_BY_PARCEL = 0x00000001, + STAT_FILTER_BY_OWNER = 0x00000002, + STAT_FILTER_BY_OBJECT = 0x00000004, + STAT_REQUEST_LAST_ENTRY = 0x80000000, +}; + +enum LAND_STAT_REPORT_TYPE +{ + STAT_REPORT_TOP_SCRIPTS = 0, + STAT_REPORT_TOP_COLLIDERS +}; + +const U32 STAT_FILTER_MASK = 0x1FFFFFFF; + +// Default maximum number of tasks/prims per region. +const U32 MAX_TASKS_PER_REGION = 15000; + +const F32 MIN_AGENT_DEPTH = 0.30f; +const F32 DEFAULT_AGENT_DEPTH = 0.45f; +const F32 MAX_AGENT_DEPTH = 0.60f; + +const F32 MIN_AGENT_WIDTH = 0.40f; +const F32 DEFAULT_AGENT_WIDTH = 0.60f; +const F32 MAX_AGENT_WIDTH = 0.80f; + +const F32 MIN_AGENT_HEIGHT = 1.3f - 2.0f * COLLISION_TOLERANCE; +const F32 DEFAULT_AGENT_HEIGHT = 1.9f; +const F32 MAX_AGENT_HEIGHT = 2.65f - 2.0f * COLLISION_TOLERANCE; + +// For linked sets +const S32 MAX_CHILDREN_PER_TASK = 255; +const S32 MAX_CHILDREN_PER_PHYSICAL_TASK = 31; + +const S32 MAX_JOINTS_PER_OBJECT = 1; // limiting to 1 until Havok 2.x + +const char* const DEFAULT_DMZ_SPACE_SERVER = "192.168.0.140"; +const char* const DEFAULT_DMZ_USER_SERVER = "192.168.0.140"; +const char* const DEFAULT_DMZ_DATA_SERVER = "192.168.0.140"; +const char* const DEFAULT_DMZ_ASSET_SERVER = "http://asset.dmz.lindenlab.com:80"; + +const char* const DEFAULT_AGNI_SPACE_SERVER = "63.211.139.100"; +const char* const DEFAULT_AGNI_USER_SERVER = "63.211.139.100"; +const char* const DEFAULT_AGNI_DATA_SERVER = "63.211.139.100"; +const char* const DEFAULT_AGNI_ASSET_SERVER = "http://asset.agni.lindenlab.com:80"; + +// Information about what ports are for what services is in the wiki Name Space Ports page +const char* const DEFAULT_LOCAL_ASSET_SERVER = "http://localhost:12041/asset/tmp"; +const char* const LOCAL_ASSET_URL_FORMAT = "http://%s:12041/asset"; + +const U32 DEFAULT_LAUNCHER_PORT = 12029; +const U32 DEFAULT_BIGBOARD_PORT = 12030; +const U32 DEFAULT_QUERYSIM_PORT = 12031; +const U32 DEFAULT_DATA_SERVER_PORT = 12032; +const U32 DEFAULT_SPACE_SERVER_PORT = 12033; +const U32 DEFAULT_VIEWER_PORT = 12034; +const U32 DEFAULT_SIMULATOR_PORT = 12035; +const U32 DEFAULT_USER_SERVER_PORT = 12036; +const U32 DEFAULT_RPC_SERVER_PORT = 12037; +const U32 DEFAULT_LOG_DATA_SERVER_PORT = 12039; +const U32 DEFAULT_BACKBONE_PORT = 12040; +const U32 DEFAULT_LOCAL_ASSET_PORT = 12041; +const U32 DEFAULT_BACKBONE_CAP_PORT = 12042; + +// For automatic port discovery when running multiple viewers on one host +const U32 PORT_DISCOVERY_RANGE_MIN = 13000; +const U32 PORT_DISCOVERY_RANGE_MAX = PORT_DISCOVERY_RANGE_MIN + 50; + +const char LAND_LAYER_CODE = 'L'; +const char WATER_LAYER_CODE = 'W'; +const char WIND_LAYER_CODE = '7'; +const char CLOUD_LAYER_CODE = '8'; + +// keys +// Bit masks for various keyboard modifier keys. +const MASK MASK_NONE = 0x0000; +const MASK MASK_CONTROL = 0x0001; +const MASK MASK_ALT = 0x0002; +const MASK MASK_SHIFT = 0x0004; + +// Special keys go into >128 +const KEY KEY_SPECIAL = 0x80; // special keys start here +const KEY KEY_RETURN = 0x81; +const KEY KEY_LEFT = 0x82; +const KEY KEY_RIGHT = 0x83; +const KEY KEY_UP = 0x84; +const KEY KEY_DOWN = 0x85; +const KEY KEY_ESCAPE = 0x86; +const KEY KEY_BACKSPACE =0x87; +const KEY KEY_DELETE = 0x88; +const KEY KEY_SHIFT = 0x89; +const KEY KEY_CONTROL = 0x8A; +const KEY KEY_ALT = 0x8B; +const KEY KEY_HOME = 0x8C; +const KEY KEY_END = 0x8D; +const KEY KEY_PAGE_UP = 0x8E; +const KEY KEY_PAGE_DOWN = 0x8F; +const KEY KEY_HYPHEN = 0x90; +const KEY KEY_EQUALS = 0x91; +const KEY KEY_INSERT = 0x92; +const KEY KEY_CAPSLOCK = 0x93; +const KEY KEY_TAB = 0x94; +const KEY KEY_ADD = 0x95; +const KEY KEY_SUBTRACT =0x96; +const KEY KEY_MULTIPLY =0x97; +const KEY KEY_DIVIDE = 0x98; +const KEY KEY_F1 = 0xA1; +const KEY KEY_F2 = 0xA2; +const KEY KEY_F3 = 0xA3; +const KEY KEY_F4 = 0xA4; +const KEY KEY_F5 = 0xA5; +const KEY KEY_F6 = 0xA6; +const KEY KEY_F7 = 0xA7; +const KEY KEY_F8 = 0xA8; +const KEY KEY_F9 = 0xA9; +const KEY KEY_F10 = 0xAA; +const KEY KEY_F11 = 0xAB; +const KEY KEY_F12 = 0xAC; + +const KEY KEY_PAD_UP = 0xC0; +const KEY KEY_PAD_DOWN = 0xC1; +const KEY KEY_PAD_LEFT = 0xC2; +const KEY KEY_PAD_RIGHT = 0xC3; +const KEY KEY_PAD_HOME = 0xC4; +const KEY KEY_PAD_END = 0xC5; +const KEY KEY_PAD_PGUP = 0xC6; +const KEY KEY_PAD_PGDN = 0xC7; +const KEY KEY_PAD_CENTER = 0xC8; // the 5 in the middle +const KEY KEY_PAD_INS = 0xC9; +const KEY KEY_PAD_DEL = 0xCA; +const KEY KEY_PAD_RETURN = 0xCB; +const KEY KEY_PAD_ADD = 0xCC; // not used +const KEY KEY_PAD_SUBTRACT = 0xCD; // not used +const KEY KEY_PAD_MULTIPLY = 0xCE; // not used +const KEY KEY_PAD_DIVIDE = 0xCF; // not used + +const KEY KEY_BUTTON0 = 0xD0; +const KEY KEY_BUTTON1 = 0xD1; +const KEY KEY_BUTTON2 = 0xD2; +const KEY KEY_BUTTON3 = 0xD3; +const KEY KEY_BUTTON4 = 0xD4; +const KEY KEY_BUTTON5 = 0xD5; +const KEY KEY_BUTTON6 = 0xD6; +const KEY KEY_BUTTON7 = 0xD7; +const KEY KEY_BUTTON8 = 0xD8; +const KEY KEY_BUTTON9 = 0xD9; +const KEY KEY_BUTTON10 = 0xDA; +const KEY KEY_BUTTON11 = 0xDB; +const KEY KEY_BUTTON12 = 0xDC; +const KEY KEY_BUTTON13 = 0xDD; +const KEY KEY_BUTTON14 = 0xDE; +const KEY KEY_BUTTON15 = 0xDF; + +const KEY KEY_NONE = 0xFF; // not sent from keyboard. For internal use only. + +const S32 KEY_COUNT = 256; + + +const F32 DEFAULT_WATER_HEIGHT = 20.0f; + +// Maturity ratings for simulators +const U8 SIM_ACCESS_MIN = 0; +const U8 SIM_ACCESS_TRIAL = 7; +const U8 SIM_ACCESS_PG = 13; +const U8 SIM_ACCESS_MATURE = 21; +const U8 SIM_ACCESS_DOWN = 254; +const U8 SIM_ACCESS_MAX = SIM_ACCESS_MATURE; + +// group constants +const S32 MAX_AGENT_GROUPS = 25; + +// god levels +const U8 GOD_MAINTENANCE = 250; +const U8 GOD_FULL = 200; +const U8 GOD_LIAISON = 150; +const U8 GOD_CUSTOMER_SERVICE = 100; +const U8 GOD_LIKE = 1; +const U8 GOD_NOT = 0; + +// "agent id" for things that should be done to ALL agents +const LLUUID LL_UUID_ALL_AGENTS("44e87126-e794-4ded-05b3-7c42da3d5cdb"); + +// Governor Linden's agent id. +const LLUUID GOVERNOR_LINDEN_ID("3d6181b0-6a4b-97ef-18d8-722652995cf1"); +const LLUUID REALESTATE_LINDEN_ID("3d6181b0-6a4b-97ef-18d8-722652995cf1"); +// Maintenance's group id. +const LLUUID MAINTENANCE_GROUP_ID("dc7b21cd-3c89-fcaa-31c8-25f9ffd224cd"); + +// Flags for kick message +const U32 KICK_FLAGS_DEFAULT = 0x0; +const U32 KICK_FLAGS_FREEZE = 1 << 0; +const U32 KICK_FLAGS_UNFREEZE = 1 << 1; + +const U8 UPD_NONE = 0x00; +const U8 UPD_POSITION = 0x01; +const U8 UPD_ROTATION = 0x02; +const U8 UPD_SCALE = 0x04; +const U8 UPD_LINKED_SETS = 0x08; +const U8 UPD_UNIFORM = 0x10; // used with UPD_SCALE + +// Agent Update Flags (U8) +const U8 AU_FLAGS_NONE = 0x00; +const U8 AU_FLAGS_HIDETITLE = 0x01; + +// start location constants +const U32 START_LOCATION_ID_LAST = 0; +const U32 START_LOCATION_ID_HOME = 1; +const U32 START_LOCATION_ID_DIRECT = 2; // for direct teleport +const U32 START_LOCATION_ID_PARCEL = 3; // for teleports to a parcel +const U32 START_LOCATION_ID_TELEHUB = 4; // for teleports to a spawnpoint +const U32 START_LOCATION_ID_URL = 5; +const U32 START_LOCATION_ID_COUNT = 6; + +// group constants +const U32 GROUP_MIN_SIZE = 2; + +// radius within which a chat message is fully audible +const F32 CHAT_WHISPER_RADIUS = 10.f; +const F32 CHAT_NORMAL_RADIUS = 20.f; +const F32 CHAT_SHOUT_RADIUS = 100.f; +const F32 CHAT_MAX_RADIUS = CHAT_SHOUT_RADIUS; +const F32 CHAT_MAX_RADIUS_BY_TWO = CHAT_MAX_RADIUS / 2.f; + +// this times above gives barely audible radius +const F32 CHAT_BARELY_AUDIBLE_FACTOR = 2.0f; + +// distance in front of speaking agent the sphere is centered +const F32 CHAT_WHISPER_OFFSET = 5.f; +const F32 CHAT_NORMAL_OFFSET = 10.f; +const F32 CHAT_SHOUT_OFFSET = 50.f; + +// first clean starts at 3 AM +const S32 SANDBOX_FIRST_CLEAN_HOUR = 3; +// clean every hours +const S32 SANDBOX_CLEAN_FREQ = 12; + +const F32 WIND_SCALE_HACK = 2.0f; // hack to make wind speeds more realistic + +enum ETerrainBrushType +{ + // the valid brush numbers cannot be reordered, because they + // are used in the binary LSL format as arguments to llModifyLand() + E_LANDBRUSH_LEVEL = 0, + E_LANDBRUSH_RAISE = 1, + E_LANDBRUSH_LOWER = 2, + E_LANDBRUSH_SMOOTH = 3, + E_LANDBRUSH_NOISE = 4, + E_LANDBRUSH_REVERT = 5, + E_LANDBRUSH_INVALID = 6 +}; + +// media commands +const U32 PARCEL_MEDIA_COMMAND_STOP = 0; +const U32 PARCEL_MEDIA_COMMAND_PAUSE = 1; +const U32 PARCEL_MEDIA_COMMAND_PLAY = 2; +const U32 PARCEL_MEDIA_COMMAND_LOOP = 3; +const U32 PARCEL_MEDIA_COMMAND_TEXTURE = 4; +const U32 PARCEL_MEDIA_COMMAND_URL = 5; +const U32 PARCEL_MEDIA_COMMAND_TIME = 6; +const U32 PARCEL_MEDIA_COMMAND_AGENT = 7; +const U32 PARCEL_MEDIA_COMMAND_UNLOAD = 8; +const U32 PARCEL_MEDIA_COMMAND_AUTO_ALIGN = 9; + +// map item types +const U32 MAP_ITEM_TELEHUB = 0x01; +const U32 MAP_ITEM_PG_EVENT = 0x02; +const U32 MAP_ITEM_MATURE_EVENT = 0x03; +const U32 MAP_ITEM_POPULAR = 0x04; +const U32 MAP_ITEM_AGENT_COUNT = 0x05; +const U32 MAP_ITEM_AGENT_LOCATIONS = 0x06; +const U32 MAP_ITEM_LAND_FOR_SALE = 0x07; +const U32 MAP_ITEM_CLASSIFIED = 0x08; + +// Crash reporter behavior +const char* const CRASH_SETTINGS_FILE = "crash_settings.xml"; +const char* const CRASH_BEHAVIOR_SETTING = "CrashBehavior"; +const S32 CRASH_BEHAVIOR_ASK = 0; +const S32 CRASH_BEHAVIOR_ALWAYS_SEND = 1; +const S32 CRASH_BEHAVIOR_NEVER_SEND = 2; + +// Export/Import return values +const S32 EXPORT_SUCCESS = 0; +const S32 EXPORT_ERROR_PERMISSIONS = -1; +const S32 EXPORT_ERROR_UNKNOWN = -2; + +// This is how long the sim will try to teleport you before giving up. +const F32 TELEPORT_EXPIRY = 15.0f; +// Additional time (in seconds) to wait per attachment +const F32 TELEPORT_EXPIRY_PER_ATTACHMENT = 3.f; + +// The maximum size of an object extra parameters binary (packed) block +#define MAX_OBJECT_PARAMS_SIZE 1024 + +const S32 CHAT_CHANNEL_DEBUG = S32_MAX; + +// PLEASE don't add constants here. Every dev will have to do +// a complete rebuild. Try to find another shared header file, +// like llregionflags.h, lllslconstants.h, llagentconstants.h, +// or create a new one. JC + +#endif diff --git a/indra/llcommon/linden_common.h b/indra/llcommon/linden_common.h new file mode 100644 index 0000000000..199d380809 --- /dev/null +++ b/indra/llcommon/linden_common.h @@ -0,0 +1,53 @@ +/** + * @file linden_common.h + * @brief Includes common headers that are always safe to include + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LINDEN_COMMON_H +#define LL_LINDEN_COMMON_H + +#include "llpreprocessor.h" + +#include +#include +#include +#include + +// Work around stupid Microsoft STL warning +#ifdef LL_WINDOWS +#pragma warning (disable : 4702) // warning C4702: unreachable code +#endif // LL_WINDOWS + +#include +#include +#include +#include +#include "llfile.h" + +#if LL_WINDOWS +// Limit Windows API to small and manageable set. +// If you get undefined symbols, find the appropriate +// Windows header file and include that in your .cpp file. +// Please don't take this out -- it helps with library +// compile times. JC +#define WIN32_LEAN_AND_MEAN +#include +#include +#endif // LL_WINDOWS + +#include "stdtypes.h" +#include "lldefs.h" +#include "llerror.h" +#include "llstring.h" +#include "lltimer.h" +#include "llfasttimer.h" +#include "llsys.h" + +#ifdef LL_WINDOWS +#pragma warning (3 : 4702) // we like level 3, not 4 +#endif // LL_WINDOWS + +#endif // not LL_LINDEN_COMMON_H diff --git a/indra/llcommon/linked_lists.h b/indra/llcommon/linked_lists.h new file mode 100644 index 0000000000..279b742e9f --- /dev/null +++ b/indra/llcommon/linked_lists.h @@ -0,0 +1,919 @@ +/** + * @file linked_lists.h + * @brief LLLinkedList class header amd implementation file. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LINKED_LISTS_H +#define LL_LINKED_LISTS_H + +/** + * Provides a standard doubly linked list for fun and profit + * Utilizes a neat trick off of Flipcode where the back pointer is a + * pointer to a pointer, allowing easier transfer of nodes between lists, &c + * And a template class, of course + */ + +#include "llerror.h" + + +template class LLLinkedList +{ +public: + friend class LLLinkNode; + // External interface + + // basic constructor + LLLinkedList() : mHead(NULL), mCurrentp(NULL), mInsertBefore(NULL) + { + mCurrentp = mHead.mNextp; + mCurrentOperatingp = mHead.mNextp; + mCount = 0; + } + + // basic constructor + LLLinkedList(BOOL (*insert_before)(DATA_TYPE *data_new, DATA_TYPE *data_tested)) : mHead(NULL), mCurrentp(NULL), mInsertBefore(insert_before) + { + mCurrentp = mHead.mNextp; + mCurrentOperatingp = mHead.mNextp; + mCount = 0; + } + + // destructor destroys list and nodes, but not data in nodes + ~LLLinkedList() + { + removeAllNodes(); + } + + // set mInsertBefore + void setInsertBefore(BOOL (*insert_before)(DATA_TYPE *data_new, DATA_TYPE *data_tested)) + { + mInsertBefore = insert_before; + } + + // + // WARNING!!!!!!! + // addData and addDataSorted are NOT O(1) operations, but O(n) because they check + // for existence of the data in the linked list first. Why, I don't know - djs + // If you don't care about dupes, use addDataNoCheck + // + + // put data into a node and stick it at the front of the list + inline BOOL addData(DATA_TYPE *data); + + // put data into a node and sort into list by mInsertBefore() + // calls normal add if mInsertBefore isn't set + inline BOOL addDataSorted(DATA_TYPE *data); + + inline BOOL addDataNoCheck(DATA_TYPE *data); + + // bubbleSortList + // does an improved bubble sort of the list . . . works best with almost sorted data + // does nothing if mInsertBefore isn't set + // Nota Bene: Swaps are accomplished by swapping data pointers + inline void bubbleSortList(); + + // put data into a node and stick it at the end of the list + inline BOOL addDataAtEnd(DATA_TYPE *data); + + // returns number of items in the list + inline S32 getLength() const; + + inline BOOL isEmpty(); + + // search the list starting at mHead.mNextp and remove the link with mDatap == data + // leave mCurrentp and mCurrentOperatingp on the next entry + // return TRUE if found, FALSE if not found + inline BOOL removeData(DATA_TYPE *data); + + // search the list starting at mHead.mNextp and delete the link with mDatap == data + // leave mCurrentp and mCurrentOperatingp on the next entry + // return TRUE if found, FALSE if not found + inline BOOL deleteData(DATA_TYPE *data); + + // remove all nodes from the list and delete the associated data + inline void deleteAllData(); + + // remove all nodes from the list but do not delete data + inline void removeAllNodes(); + + // check to see if data is in list + // if TRUE then mCurrentp and mCurrentOperatingp point to data + inline BOOL checkData(DATA_TYPE *data); + + // place mCurrentp on first node + inline void resetList(); + + // return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp + inline DATA_TYPE *getCurrentData(); + + // same as getCurrentData() but a more intuitive name for the operation + inline DATA_TYPE *getNextData(); + + // reset the list and return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp + inline DATA_TYPE *getFirstData(); + + // reset the list and return the data at position n, set mCurentOperatingp to that node and bump mCurrentp + // Note: n is zero-based + inline DATA_TYPE *getNthData( U32 n); + + // reset the list and return the last data in it, set mCurentOperatingp to that node and bump mCurrentp + inline DATA_TYPE *getLastData(); + + // remove the Node at mCurentOperatingp + // leave mCurrentp and mCurentOperatingp on the next entry + inline void removeCurrentData(); + + // remove the Node at mCurentOperatingp and add it to newlist + // leave mCurrentp and mCurentOperatingp on the next entry + void moveCurrentData(LLLinkedList *newlist, BOOL b_sort); + + BOOL moveData(DATA_TYPE *data, LLLinkedList *newlist, BOOL b_sort); + + // delete the Node at mCurentOperatingp + // leave mCurrentp anf mCurentOperatingp on the next entry + void deleteCurrentData(); + +private: + // node that actually contains the data + class LLLinkNode + { + public: + // assign the mDatap pointer + LLLinkNode(DATA_TYPE *data) : mDatap(data), mNextp(NULL), mPrevpp(NULL) + { + } + + // destructor does not, by default, destroy associated data + // however, the mDatap must be NULL to ensure that we aren't causing memory leaks + ~LLLinkNode() + { + if (mDatap) + { + llerror("Attempting to call LLLinkNode destructor with a non-null mDatap!", 1); + } + } + + // delete associated data and NULL out pointer + void deleteData() + { + delete mDatap; + mDatap = NULL; + } + + // NULL out pointer + void removeData() + { + mDatap = NULL; + } + + DATA_TYPE *mDatap; + LLLinkNode *mNextp; + LLLinkNode **mPrevpp; + }; + + // add a node at the front of the list + void addData(LLLinkNode *node) + { + // don't allow NULL to be passed to addData + if (!node) + { + llerror("NULL pointer passed to LLLinkedList::addData", 0); + } + + // add the node to the front of the list + node->mPrevpp = &mHead.mNextp; + node->mNextp = mHead.mNextp; + + // if there's something in the list, fix its back pointer + if (node->mNextp) + { + node->mNextp->mPrevpp = &node->mNextp; + } + + mHead.mNextp = node; + } + + LLLinkNode mHead; // fake head node. . . makes pointer operations faster and easier + LLLinkNode *mCurrentp; // mCurrentp is the Node that getCurrentData returns + LLLinkNode *mCurrentOperatingp; // this is the node that the various mumbleCurrentData functions act on + BOOL (*mInsertBefore)(DATA_TYPE *data_new, DATA_TYPE *data_tested); // user function set to allow sorted lists + U32 mCount; +}; + +template +BOOL LLLinkedList::addData(DATA_TYPE *data) +{ + // don't allow NULL to be passed to addData + if (!data) + { + llerror("NULL pointer passed to LLLinkedList::addData", 0); + } + + LLLinkNode *tcurr = mCurrentp; + LLLinkNode *tcurrop = mCurrentOperatingp; + + if ( checkData(data)) + { + mCurrentp = tcurr; + mCurrentOperatingp = tcurrop; + return FALSE; + } + + // make the new node + LLLinkNode *temp = new LLLinkNode(data); + + // add the node to the front of the list + temp->mPrevpp = &mHead.mNextp; + temp->mNextp = mHead.mNextp; + + // if there's something in the list, fix its back pointer + if (temp->mNextp) + { + temp->mNextp->mPrevpp = &temp->mNextp; + } + + mHead.mNextp = temp; + mCurrentp = tcurr; + mCurrentOperatingp = tcurrop; + mCount++; + return TRUE; +} + + +template +BOOL LLLinkedList::addDataNoCheck(DATA_TYPE *data) +{ + // don't allow NULL to be passed to addData + if (!data) + { + llerror("NULL pointer passed to LLLinkedList::addData", 0); + } + + LLLinkNode *tcurr = mCurrentp; + LLLinkNode *tcurrop = mCurrentOperatingp; + + // make the new node + LLLinkNode *temp = new LLLinkNode(data); + + // add the node to the front of the list + temp->mPrevpp = &mHead.mNextp; + temp->mNextp = mHead.mNextp; + + // if there's something in the list, fix its back pointer + if (temp->mNextp) + { + temp->mNextp->mPrevpp = &temp->mNextp; + } + + mHead.mNextp = temp; + mCurrentp = tcurr; + mCurrentOperatingp = tcurrop; + mCount++; + return TRUE; +} + + +template +BOOL LLLinkedList::addDataSorted(DATA_TYPE *data) +{ + LLLinkNode *tcurr = mCurrentp; + LLLinkNode *tcurrop = mCurrentOperatingp; + // don't allow NULL to be passed to addData + if (!data) + { + llerror("NULL pointer passed to LLLinkedList::addDataSorted", 0); + } + + if (checkData(data)) + { + // restore + mCurrentp = tcurr; + mCurrentOperatingp = tcurrop; + return FALSE; + } + + // mInsertBefore not set? + if (!mInsertBefore) + { + addData(data); + // restore + mCurrentp = tcurr; + mCurrentOperatingp = tcurrop; + return FALSE; + } + + // empty list? + if (!mHead.mNextp) + { + addData(data); + // restore + mCurrentp = tcurr; + mCurrentOperatingp = tcurrop; + return TRUE; + } + + // make the new node + LLLinkNode *temp = new LLLinkNode(data); + + // walk the list until mInsertBefore returns true + mCurrentp = mHead.mNextp; + while (mCurrentp->mNextp) + { + if (mInsertBefore(data, mCurrentp->mDatap)) + { + // insert before the current one + temp->mPrevpp = mCurrentp->mPrevpp; + temp->mNextp = mCurrentp; + *(temp->mPrevpp) = temp; + mCurrentp->mPrevpp = &temp->mNextp; + // restore + mCurrentp = tcurr; + mCurrentOperatingp = tcurrop; + mCount++; + return TRUE; + } + else + { + mCurrentp = mCurrentp->mNextp; + } + } + + // on the last element, add before? + if (mInsertBefore(data, mCurrentp->mDatap)) + { + // insert before the current one + temp->mPrevpp = mCurrentp->mPrevpp; + temp->mNextp = mCurrentp; + *(temp->mPrevpp) = temp; + mCurrentp->mPrevpp = &temp->mNextp; + // restore + mCurrentp = tcurr; + mCurrentOperatingp = tcurrop; + } + else // insert after + { + temp->mPrevpp = &mCurrentp->mNextp; + temp->mNextp = NULL; + mCurrentp->mNextp = temp; + + // restore + mCurrentp = tcurr; + mCurrentOperatingp = tcurrop; + } + mCount++; + return TRUE; +} + +template +void LLLinkedList::bubbleSortList() +{ + // mInsertBefore not set + if (!mInsertBefore) + { + return; + } + + LLLinkNode *tcurr = mCurrentp; + LLLinkNode *tcurrop = mCurrentOperatingp; + + BOOL b_swapped = FALSE; + DATA_TYPE *temp; + + // Nota Bene: This will break if more than 0x7FFFFFFF members in list! + S32 length = 0x7FFFFFFF; + S32 count = 0; + do + { + b_swapped = FALSE; + mCurrentp = mHead.mNextp; + count = 0; + while ( (count + 1 < length) + &&(mCurrentp)) + { + if (mCurrentp->mNextp) + { + if (!mInsertBefore(mCurrentp->mDatap, mCurrentp->mNextp->mDatap)) + { + // swap data pointers! + temp = mCurrentp->mDatap; + mCurrentp->mDatap = mCurrentp->mNextp->mDatap; + mCurrentp->mNextp->mDatap = temp; + b_swapped = TRUE; + } + } + else + { + break; + } + count++; + mCurrentp = mCurrentp->mNextp; + } + length = count; + } while (b_swapped); + + // restore + mCurrentp = tcurr; + mCurrentOperatingp = tcurrop; +} + + +template +BOOL LLLinkedList::addDataAtEnd(DATA_TYPE *data) +{ + LLLinkNode *tcurr = mCurrentp; + LLLinkNode *tcurrop = mCurrentOperatingp; + + // don't allow NULL to be passed to addData + if (!data) + { + llerror("NULL pointer passed to LLLinkedList::addData", 0); + } + + if (checkData(data)) + { + mCurrentp = tcurr; + mCurrentOperatingp = tcurrop; + return FALSE; + } + + // make the new node + LLLinkNode *temp = new LLLinkNode(data); + + // add the node to the end of the list + + // if empty, add to the front and be done with it + if (!mHead.mNextp) + { + temp->mPrevpp = &mHead.mNextp; + temp->mNextp = NULL; + mHead.mNextp = temp; + } + else + { + // otherwise, walk to the end of the list + mCurrentp = mHead.mNextp; + while (mCurrentp->mNextp) + { + mCurrentp = mCurrentp->mNextp; + } + temp->mPrevpp = &mCurrentp->mNextp; + temp->mNextp = NULL; + mCurrentp->mNextp = temp; + } + + // restore + mCurrentp = tcurr; + mCurrentOperatingp = tcurrop; + mCount++; + return TRUE; +} + + +// returns number of items in the list +template +S32 LLLinkedList::getLength() const +{ +// S32 length = 0; +// for (LLLinkNode* temp = mHead.mNextp; temp != NULL; temp = temp->mNextp) +// { +// length++; +// } + return mCount; +} + + +template +BOOL LLLinkedList::isEmpty() +{ + return (mCount == 0); +} + + +// search the list starting at mHead.mNextp and remove the link with mDatap == data +// leave mCurrentp and mCurrentOperatingp on the next entry +// return TRUE if found, FALSE if not found +template +BOOL LLLinkedList::removeData(DATA_TYPE *data) +{ + BOOL b_found = FALSE; + // don't allow NULL to be passed to addData + if (!data) + { + llerror("NULL pointer passed to LLLinkedList::removeData", 0); + } + + LLLinkNode *tcurr = mCurrentp; + LLLinkNode *tcurrop = mCurrentOperatingp; + + mCurrentp = mHead.mNextp; + mCurrentOperatingp = mHead.mNextp; + + while (mCurrentOperatingp) + { + if (mCurrentOperatingp->mDatap == data) + { + b_found = TRUE; + + // remove the node + + // if there is a next one, fix it + if (mCurrentOperatingp->mNextp) + { + mCurrentOperatingp->mNextp->mPrevpp = mCurrentOperatingp->mPrevpp; + } + *(mCurrentOperatingp->mPrevpp) = mCurrentOperatingp->mNextp; + + // remove the LLLinkNode + + // if we were on the one we want to delete, bump the cached copies + if (mCurrentOperatingp == tcurrop) + { + tcurrop = tcurr = mCurrentOperatingp->mNextp; + } + else if (mCurrentOperatingp == tcurr) + { + tcurrop = tcurr = mCurrentOperatingp->mNextp; + } + + mCurrentp = mCurrentOperatingp->mNextp; + + mCurrentOperatingp->removeData(); + delete mCurrentOperatingp; + mCurrentOperatingp = mCurrentp; + mCount--; + break; + } + mCurrentOperatingp = mCurrentOperatingp->mNextp; + } + // restore + mCurrentp = tcurr; + mCurrentOperatingp = tcurrop; + return b_found; +} + +// search the list starting at mHead.mNextp and delete the link with mDatap == data +// leave mCurrentp and mCurrentOperatingp on the next entry +// return TRUE if found, FALSE if not found +template +BOOL LLLinkedList::deleteData(DATA_TYPE *data) +{ + BOOL b_found = FALSE; + // don't allow NULL to be passed to addData + if (!data) + { + llerror("NULL pointer passed to LLLinkedList::removeData", 0); + } + + LLLinkNode *tcurr = mCurrentp; + LLLinkNode *tcurrop = mCurrentOperatingp; + + mCurrentp = mHead.mNextp; + mCurrentOperatingp = mHead.mNextp; + + while (mCurrentOperatingp) + { + if (mCurrentOperatingp->mDatap == data) + { + b_found = TRUE; + + // remove the node + // if there is a next one, fix it + if (mCurrentOperatingp->mNextp) + { + mCurrentOperatingp->mNextp->mPrevpp = mCurrentOperatingp->mPrevpp; + } + *(mCurrentOperatingp->mPrevpp) = mCurrentOperatingp->mNextp; + + // delete the LLLinkNode + // if we were on the one we want to delete, bump the cached copies + if (mCurrentOperatingp == tcurrop) + { + tcurrop = tcurr = mCurrentOperatingp->mNextp; + } + + // and delete the associated data + llassert(mCurrentOperatingp); + mCurrentp = mCurrentOperatingp->mNextp; + mCurrentOperatingp->deleteData(); + delete mCurrentOperatingp; + mCurrentOperatingp = mCurrentp; + mCount--; + break; + } + mCurrentOperatingp = mCurrentOperatingp->mNextp; + } + // restore + mCurrentp = tcurr; + mCurrentOperatingp = tcurrop; + return b_found; +} + + // remove all nodes from the list and delete the associated data +template +void LLLinkedList::deleteAllData() +{ + LLLinkNode *temp; + // reset mCurrentp + mCurrentp = mHead.mNextp; + + while (mCurrentp) + { + temp = mCurrentp->mNextp; + mCurrentp->deleteData(); + delete mCurrentp; + mCurrentp = temp; + } + + // reset mHead and mCurrentp + mHead.mNextp = NULL; + mCurrentp = mHead.mNextp; + mCurrentOperatingp = mHead.mNextp; + mCount = 0; +} + +// remove all nodes from the list but do not delete data +template +void LLLinkedList::removeAllNodes() +{ + LLLinkNode *temp; + // reset mCurrentp + mCurrentp = mHead.mNextp; + + while (mCurrentp) + { + temp = mCurrentp->mNextp; + mCurrentp->removeData(); + delete mCurrentp; + mCurrentp = temp; + } + + // reset mHead and mCurrentp + mHead.mNextp = NULL; + mCurrentp = mHead.mNextp; + mCurrentOperatingp = mHead.mNextp; + mCount = 0; +} + +// check to see if data is in list +// if TRUE then mCurrentp and mCurrentOperatingp point to data +template +BOOL LLLinkedList::checkData(DATA_TYPE *data) +{ + // reset mCurrentp + mCurrentp = mHead.mNextp; + + while (mCurrentp) + { + if (mCurrentp->mDatap == data) + { + mCurrentOperatingp = mCurrentp; + return TRUE; + } + mCurrentp = mCurrentp->mNextp; + } + mCurrentOperatingp = mCurrentp; + return FALSE; +} + +// place mCurrentp on first node +template +void LLLinkedList::resetList() +{ + mCurrentp = mHead.mNextp; + mCurrentOperatingp = mHead.mNextp; +} + +// return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp +template +DATA_TYPE *LLLinkedList::getCurrentData() +{ + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mNextp; + return mCurrentOperatingp->mDatap; + } + else + { + return NULL; + } +} + +// same as getCurrentData() but a more intuitive name for the operation +template +DATA_TYPE *LLLinkedList::getNextData() +{ + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mNextp; + return mCurrentOperatingp->mDatap; + } + else + { + return NULL; + } +} + +// reset the list and return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp +template +DATA_TYPE *LLLinkedList::getFirstData() +{ + mCurrentp = mHead.mNextp; + mCurrentOperatingp = mHead.mNextp; + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mNextp; + return mCurrentOperatingp->mDatap; + } + else + { + return NULL; + } +} + +// Note: n is zero-based +template +DATA_TYPE *LLLinkedList::getNthData( U32 n ) +{ + mCurrentOperatingp = mHead.mNextp; + + // if empty, return NULL + if (!mCurrentOperatingp) + { + return NULL; + } + + for( U32 i = 0; i < n; i++ ) + { + mCurrentOperatingp = mCurrentOperatingp->mNextp; + if( !mCurrentOperatingp ) + { + return NULL; + } + } + + mCurrentp = mCurrentOperatingp->mNextp; + return mCurrentOperatingp->mDatap; +} + + +// reset the list and return the last data in it, set mCurentOperatingp to that node and bump mCurrentp +template +DATA_TYPE *LLLinkedList::getLastData() +{ + mCurrentOperatingp = mHead.mNextp; + + // if empty, return NULL + if (!mCurrentOperatingp) + return NULL; + + // walk until we're pointing at the last entry + while (mCurrentOperatingp->mNextp) + { + mCurrentOperatingp = mCurrentOperatingp->mNextp; + } + mCurrentp = mCurrentOperatingp->mNextp; + return mCurrentOperatingp->mDatap; +} + +// remove the Node at mCurentOperatingp +// leave mCurrentp and mCurentOperatingp on the next entry +// return TRUE if found, FALSE if not found +template +void LLLinkedList::removeCurrentData() +{ + if (mCurrentOperatingp) + { + // remove the node + // if there is a next one, fix it + if (mCurrentOperatingp->mNextp) + { + mCurrentOperatingp->mNextp->mPrevpp = mCurrentOperatingp->mPrevpp; + } + *(mCurrentOperatingp->mPrevpp) = mCurrentOperatingp->mNextp; + + // remove the LLLinkNode + mCurrentp = mCurrentOperatingp->mNextp; + + mCurrentOperatingp->removeData(); + delete mCurrentOperatingp; + mCount--; + mCurrentOperatingp = mCurrentp; + } +} + +// remove the Node at mCurentOperatingp and add it to newlist +// leave mCurrentp and mCurentOperatingp on the next entry +// return TRUE if found, FALSE if not found +template +void LLLinkedList::moveCurrentData(LLLinkedList *newlist, BOOL b_sort) +{ + if (mCurrentOperatingp) + { + // remove the node + // if there is a next one, fix it + if (mCurrentOperatingp->mNextp) + { + mCurrentOperatingp->mNextp->mPrevpp = mCurrentOperatingp->mPrevpp; + } + *(mCurrentOperatingp->mPrevpp) = mCurrentOperatingp->mNextp; + + // remove the LLLinkNode + mCurrentp = mCurrentOperatingp->mNextp; + // move the node to the new list + newlist->addData(mCurrentOperatingp); + if (b_sort) + bubbleSortList(); + mCurrentOperatingp = mCurrentp; + } +} + +template +BOOL LLLinkedList::moveData(DATA_TYPE *data, LLLinkedList *newlist, BOOL b_sort) +{ + BOOL b_found = FALSE; + // don't allow NULL to be passed to addData + if (!data) + { + llerror("NULL pointer passed to LLLinkedList::removeData", 0); + } + + LLLinkNode *tcurr = mCurrentp; + LLLinkNode *tcurrop = mCurrentOperatingp; + + mCurrentp = mHead.mNextp; + mCurrentOperatingp = mHead.mNextp; + + while (mCurrentOperatingp) + { + if (mCurrentOperatingp->mDatap == data) + { + b_found = TRUE; + + // remove the node + + // if there is a next one, fix it + if (mCurrentOperatingp->mNextp) + { + mCurrentOperatingp->mNextp->mPrevpp = mCurrentOperatingp->mPrevpp; + } + *(mCurrentOperatingp->mPrevpp) = mCurrentOperatingp->mNextp; + + // if we were on the one we want to delete, bump the cached copies + if ( (mCurrentOperatingp == tcurrop) + ||(mCurrentOperatingp == tcurr)) + { + tcurrop = tcurr = mCurrentOperatingp->mNextp; + } + + // remove the LLLinkNode + mCurrentp = mCurrentOperatingp->mNextp; + // move the node to the new list + newlist->addData(mCurrentOperatingp); + if (b_sort) + newlist->bubbleSortList(); + mCurrentOperatingp = mCurrentp; + break; + } + mCurrentOperatingp = mCurrentOperatingp->mNextp; + } + // restore + mCurrentp = tcurr; + mCurrentOperatingp = tcurrop; + return b_found; +} + +// delete the Node at mCurentOperatingp +// leave mCurrentp anf mCurentOperatingp on the next entry +// return TRUE if found, FALSE if not found +template +void LLLinkedList::deleteCurrentData() +{ + if (mCurrentOperatingp) + { + // remove the node + // if there is a next one, fix it + if (mCurrentOperatingp->mNextp) + { + mCurrentOperatingp->mNextp->mPrevpp = mCurrentOperatingp->mPrevpp; + } + *(mCurrentOperatingp->mPrevpp) = mCurrentOperatingp->mNextp; + + // remove the LLLinkNode + mCurrentp = mCurrentOperatingp->mNextp; + + mCurrentOperatingp->deleteData(); + if (mCurrentOperatingp->mDatap) + llerror("This is impossible!", 0); + delete mCurrentOperatingp; + mCurrentOperatingp = mCurrentp; + mCount--; + } +} + +#endif diff --git a/indra/llcommon/llagentconstants.h b/indra/llcommon/llagentconstants.h new file mode 100644 index 0000000000..66b4b564bf --- /dev/null +++ b/indra/llcommon/llagentconstants.h @@ -0,0 +1,141 @@ +/** + * @file llagentconstants.h + * @author James Cook, Andrew Meadows, Richard Nelson + * @brief Shared constants through the system for agents. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLAGENTCONSTANTS_H +#define LL_LLAGENTCONSTANTS_H + +const U32 CONTROL_AT_POS_INDEX = 0; +const U32 CONTROL_AT_NEG_INDEX = 1; +const U32 CONTROL_LEFT_POS_INDEX = 2; +const U32 CONTROL_LEFT_NEG_INDEX = 3; +const U32 CONTROL_UP_POS_INDEX = 4; +const U32 CONTROL_UP_NEG_INDEX = 5; +const U32 CONTROL_PITCH_POS_INDEX = 6; +const U32 CONTROL_PITCH_NEG_INDEX = 7; +const U32 CONTROL_YAW_POS_INDEX = 8; +const U32 CONTROL_YAW_NEG_INDEX = 9; +const U32 CONTROL_FAST_AT_INDEX = 10; +const U32 CONTROL_FAST_LEFT_INDEX = 11; +const U32 CONTROL_FAST_UP_INDEX = 12; +const U32 CONTROL_FLY_INDEX = 13; +const U32 CONTROL_STOP_INDEX = 14; +const U32 CONTROL_FINISH_ANIM_INDEX = 15; +const U32 CONTROL_STAND_UP_INDEX = 16; +const U32 CONTROL_SIT_ON_GROUND_INDEX = 17; +const U32 CONTROL_MOUSELOOK_INDEX = 18; +const U32 CONTROL_NUDGE_AT_POS_INDEX = 19; +const U32 CONTROL_NUDGE_AT_NEG_INDEX = 20; +const U32 CONTROL_NUDGE_LEFT_POS_INDEX = 21; +const U32 CONTROL_NUDGE_LEFT_NEG_INDEX = 22; +const U32 CONTROL_NUDGE_UP_POS_INDEX = 23; +const U32 CONTROL_NUDGE_UP_NEG_INDEX = 24; +const U32 CONTROL_TURN_LEFT_INDEX = 25; +const U32 CONTROL_TURN_RIGHT_INDEX = 26; +const U32 CONTROL_AWAY_INDEX = 27; +const U32 CONTROL_LBUTTON_DOWN_INDEX = 28; +const U32 CONTROL_LBUTTON_UP_INDEX = 29; +const U32 CONTROL_ML_LBUTTON_DOWN_INDEX = 30; +const U32 CONTROL_ML_LBUTTON_UP_INDEX = 31; +const U32 TOTAL_CONTROLS = 32; + +const U32 AGENT_CONTROL_AT_POS = 0x1 << CONTROL_AT_POS_INDEX; +const U32 AGENT_CONTROL_AT_NEG = 0x1 << CONTROL_AT_NEG_INDEX; +const U32 AGENT_CONTROL_LEFT_POS = 0x1 << CONTROL_LEFT_POS_INDEX; +const U32 AGENT_CONTROL_LEFT_NEG = 0x1 << CONTROL_LEFT_NEG_INDEX; +const U32 AGENT_CONTROL_UP_POS = 0x1 << CONTROL_UP_POS_INDEX; +const U32 AGENT_CONTROL_UP_NEG = 0x1 << CONTROL_UP_NEG_INDEX; +const U32 AGENT_CONTROL_PITCH_POS = 0x1 << CONTROL_PITCH_POS_INDEX; +const U32 AGENT_CONTROL_PITCH_NEG = 0x1 << CONTROL_PITCH_NEG_INDEX; +const U32 AGENT_CONTROL_YAW_POS = 0x1 << CONTROL_YAW_POS_INDEX; +const U32 AGENT_CONTROL_YAW_NEG = 0x1 << CONTROL_YAW_NEG_INDEX; + +const U32 AGENT_CONTROL_FAST_AT = 0x1 << CONTROL_FAST_AT_INDEX; +const U32 AGENT_CONTROL_FAST_LEFT = 0x1 << CONTROL_FAST_LEFT_INDEX; +const U32 AGENT_CONTROL_FAST_UP = 0x1 << CONTROL_FAST_UP_INDEX; + +const U32 AGENT_CONTROL_FLY = 0x1 << CONTROL_FLY_INDEX; +const U32 AGENT_CONTROL_STOP = 0x1 << CONTROL_STOP_INDEX; +const U32 AGENT_CONTROL_FINISH_ANIM = 0x1 << CONTROL_FINISH_ANIM_INDEX; +const U32 AGENT_CONTROL_STAND_UP = 0x1 << CONTROL_STAND_UP_INDEX; +const U32 AGENT_CONTROL_SIT_ON_GROUND = 0x1 << CONTROL_SIT_ON_GROUND_INDEX; +const U32 AGENT_CONTROL_MOUSELOOK = 0x1 << CONTROL_MOUSELOOK_INDEX; + +const U32 AGENT_CONTROL_NUDGE_AT_POS = 0x1 << CONTROL_NUDGE_AT_POS_INDEX; +const U32 AGENT_CONTROL_NUDGE_AT_NEG = 0x1 << CONTROL_NUDGE_AT_NEG_INDEX; +const U32 AGENT_CONTROL_NUDGE_LEFT_POS = 0x1 << CONTROL_NUDGE_LEFT_POS_INDEX; +const U32 AGENT_CONTROL_NUDGE_LEFT_NEG = 0x1 << CONTROL_NUDGE_LEFT_NEG_INDEX; +const U32 AGENT_CONTROL_NUDGE_UP_POS = 0x1 << CONTROL_NUDGE_UP_POS_INDEX; +const U32 AGENT_CONTROL_NUDGE_UP_NEG = 0x1 << CONTROL_NUDGE_UP_NEG_INDEX; +const U32 AGENT_CONTROL_TURN_LEFT = 0x1 << CONTROL_TURN_LEFT_INDEX; +const U32 AGENT_CONTROL_TURN_RIGHT = 0x1 << CONTROL_TURN_RIGHT_INDEX; + +const U32 AGENT_CONTROL_AWAY = 0x1 << CONTROL_AWAY_INDEX; + +const U32 AGENT_CONTROL_LBUTTON_DOWN = 0x1 << CONTROL_LBUTTON_DOWN_INDEX; +const U32 AGENT_CONTROL_LBUTTON_UP = 0x1 << CONTROL_LBUTTON_UP_INDEX; +const U32 AGENT_CONTROL_ML_LBUTTON_DOWN = 0x1 << CONTROL_ML_LBUTTON_DOWN_INDEX; +const U32 AGENT_CONTROL_ML_LBUTTON_UP = ((U32)0x1) << CONTROL_ML_LBUTTON_UP_INDEX; + +const U32 AGENT_CONTROL_AT = AGENT_CONTROL_AT_POS + | AGENT_CONTROL_AT_NEG + | AGENT_CONTROL_NUDGE_AT_POS + | AGENT_CONTROL_NUDGE_AT_NEG; + +const U32 AGENT_CONTROL_LEFT = AGENT_CONTROL_LEFT_POS + | AGENT_CONTROL_LEFT_NEG + | AGENT_CONTROL_NUDGE_LEFT_POS + | AGENT_CONTROL_NUDGE_LEFT_NEG; + +const U32 AGENT_CONTROL_UP = AGENT_CONTROL_UP_POS + | AGENT_CONTROL_UP_NEG + | AGENT_CONTROL_NUDGE_UP_POS + | AGENT_CONTROL_NUDGE_UP_NEG; + +const U32 AGENT_CONTROL_HORIZONTAL = AGENT_CONTROL_AT + | AGENT_CONTROL_LEFT; + +const U32 AGENT_CONTROL_NOT_USED_BY_LSL = AGENT_CONTROL_FLY + | AGENT_CONTROL_STOP + | AGENT_CONTROL_FINISH_ANIM + | AGENT_CONTROL_STAND_UP + | AGENT_CONTROL_SIT_ON_GROUND + | AGENT_CONTROL_MOUSELOOK + | AGENT_CONTROL_AWAY; + +const U32 AGENT_CONTROL_MOVEMENT = AGENT_CONTROL_AT + | AGENT_CONTROL_LEFT + | AGENT_CONTROL_UP; + +const U32 AGENT_CONTROL_ROTATION = AGENT_CONTROL_PITCH_POS + | AGENT_CONTROL_PITCH_NEG + | AGENT_CONTROL_YAW_POS + | AGENT_CONTROL_YAW_NEG; + +const U32 AGENT_CONTROL_NUDGE = AGENT_CONTROL_NUDGE_AT_POS + | AGENT_CONTROL_NUDGE_AT_NEG + | AGENT_CONTROL_NUDGE_LEFT_POS + | AGENT_CONTROL_NUDGE_LEFT_NEG; + + +// move these up so that we can hide them in "State" for object updates +// (for now) +const U32 AGENT_ATTACH_OFFSET = 4; +const U32 AGENT_ATTACH_MASK = 0xf << AGENT_ATTACH_OFFSET; +const U32 AGENT_ATTACH_CLEAR = 0x00; + +// RN: this method swaps the upper and lower nibbles to maintain backward +// compatibility with old objects that only used the upper nibble +#define ATTACHMENT_ID_FROM_STATE(state) ((S32)((((U8)state & AGENT_ATTACH_MASK) >> 4) | (((U8)state & ~AGENT_ATTACH_MASK) << 4))) + +// test state for use in testing grabbing the camera +const U32 AGENT_CAMERA_OBJECT = 0x1 << 3; + +const F32 MAX_ATTACHMENT_DIST = 3.5f; // meters? + +#endif diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp new file mode 100644 index 0000000000..50648a2d30 --- /dev/null +++ b/indra/llcommon/llapp.cpp @@ -0,0 +1,616 @@ +/** + * @file llapp.cpp + * @brief Implementation of the LLApp class. + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llapp.h" + +#include "llcommon.h" +#include "llapr.h" +#include "llerrorthread.h" +#include "llframetimer.h" +#include "llmemory.h" + +// +// Signal handling +// +// Windows uses structured exceptions, so it's handled a bit differently. +// +#if LL_WINDOWS +LONG WINAPI default_windows_exception_handler(struct _EXCEPTION_POINTERS *exception_infop); +#else +#include // for fork() +void setup_signals(); +void default_unix_signal_handler(int signum, siginfo_t *info, void *); +const S32 LL_SMACKDOWN_SIGNAL = SIGUSR1; +#endif + +// the static application instance +LLApp* LLApp::sApplication = NULL; + +// Local flag for whether or not to do logging in signal handlers. +//static +BOOL LLApp::sLogInSignal = FALSE; + +// static +LLApp::EAppStatus LLApp::sStatus = LLApp::APP_STATUS_STOPPED; // Keeps track of application status +LLAppErrorHandler LLApp::sErrorHandler = NULL; +BOOL LLApp::sErrorThreadRunning = FALSE; +#if !LL_WINDOWS +LLApp::child_map LLApp::sChildMap; +LLAtomicU32* LLApp::sSigChildCount = NULL; +LLAppChildCallback LLApp::sDefaultChildCallback = NULL; +#endif + + +LLApp::LLApp() : mThreadErrorp(NULL) +{ + // Set our status to running + setStatus(APP_STATUS_RUNNING); + + LLCommon::initClass(); + +#if !LL_WINDOWS + // This must be initialized before the error handler. + sSigChildCount = new LLAtomicU32(0); +#endif + + // Setup error handling + setupErrorHandling(); + + // initialize the options structure. We need to make this an array + // because the structured data will not auto-allocate if we + // reference an invalid location with the [] operator. + mOptions = LLSD::emptyArray(); + LLSD sd; + for(int i = 0; i < PRIORITY_COUNT; ++i) + { + mOptions.append(sd); + } + + // Make sure we clean up APR when we exit + // Don't need to do this if we're cleaning up APR in the destructor + //atexit(ll_cleanup_apr); + + // Set the application to this instance. + sApplication = this; +} + + +LLApp::~LLApp() +{ +#if !LL_WINDOWS + delete sSigChildCount; + sSigChildCount = NULL; +#endif + setStopped(); + // HACK: wait for the error thread to clean itself + ms_sleep(20); + if (mThreadErrorp) + { + delete mThreadErrorp; + mThreadErrorp = NULL; + } + + LLCommon::cleanupClass(); +} + +// static +LLApp* LLApp::instance() +{ + return sApplication; +} + + +LLSD LLApp::getOption(const std::string& name) const +{ + LLSD rv; + LLSD::array_const_iterator iter = mOptions.beginArray(); + LLSD::array_const_iterator end = mOptions.endArray(); + for(; iter != end; ++iter) + { + rv = (*iter)[name]; + if(rv.isDefined()) break; + } + return rv; +} + +bool LLApp::parseCommandOptions(int argc, char** argv) +{ + LLSD commands; + std::string name; + std::string value; + for(int ii = 1; ii < argc; ++ii) + { + if(argv[ii][0] != '-') + { + llinfos << "Did not find option identifier while parsing token: " + << argv[ii] << llendl; + return false; + } + int offset = 1; + if(argv[ii][1] == '-') ++offset; + name.assign(&argv[ii][offset]); + if(((ii+1) >= argc) || (argv[ii+1][0] == '-')) + { + // we found another option after this one or we have + // reached the end. simply record that this option was + // found and continue. + commands[name] = true; + continue; + } + ++ii; + value.assign(argv[ii]); + commands[name] = value; + } + setOptionData(PRIORITY_COMMAND_LINE, commands); + return true; +} + +bool LLApp::setOptionData(OptionPriority level, LLSD data) +{ + if((level < 0) + || (level >= PRIORITY_COUNT) + || (data.type() != LLSD::TypeMap)) + { + return false; + } + mOptions[level] = data; + return true; +} + +LLSD LLApp::getOptionData(OptionPriority level) +{ + if((level < 0) || (level >= PRIORITY_COUNT)) + { + return LLSD(); + } + return mOptions[level]; +} + +void LLApp::stepFrame() +{ + // Update the static frame timer. + LLFrameTimer::updateFrameTime(); + + // Run ready runnables + mRunner.run(); +} + + +void LLApp::setupErrorHandling() +{ + // Error handling is done by starting up an error handling thread, which just sleeps and + // occasionally checks to see if the app is in an error state, and sees if it needs to be run. + +#if LL_WINDOWS + // Windows doesn't have the same signal handling mechanisms as UNIX, thus APR doesn't provide + // a signal handling thread implementation. + // What we do is install an unhandled exception handler, which will try to do the right thing + // in the case of an error (generate a minidump) + + // Disable this until the viewer gets ported so server crashes can be JIT debugged. + //LPTOP_LEVEL_EXCEPTION_FILTER prev_filter; + //prev_filter = SetUnhandledExceptionFilter(default_windows_exception_handler); +#else + // + // Start up signal handling. + // + // There are two different classes of signals. Synchronous signals are delivered to a specific + // thread, asynchronous signals can be delivered to any thread (in theory) + // + + setup_signals(); + +#endif + + // + // Start the error handling thread, which is responsible for taking action + // when the app goes into the APP_STATUS_ERROR state + // + llinfos << "LLApp::setupErrorHandling - Starting error thread" << llendl; + mThreadErrorp = new LLErrorThread(); + mThreadErrorp->setUserData((void *) this); + mThreadErrorp->start(); +} + + +void LLApp::setErrorHandler(LLAppErrorHandler handler) +{ + LLApp::sErrorHandler = handler; +} + +// static +void LLApp::runErrorHandler() +{ + if (LLApp::sErrorHandler) + { + LLApp::sErrorHandler(); + } + + //llinfos << "App status now STOPPED" << llendl; + LLApp::setStopped(); +} + + +// static +void LLApp::setStatus(EAppStatus status) +{ + sStatus = status; +} + + +// static +void LLApp::setError() +{ + setStatus(APP_STATUS_ERROR); +} + + +// static +void LLApp::setQuitting() +{ + if (!isExiting()) + { + // If we're already exiting, we don't want to reset our state back to quitting. + llinfos << "Setting app state to QUITTING" << llendl; + setStatus(APP_STATUS_QUITTING); + } +} + + +// static +void LLApp::setStopped() +{ + setStatus(APP_STATUS_STOPPED); +} + + +// static +bool LLApp::isStopped() +{ + return (APP_STATUS_STOPPED == sStatus); +} + + +// static +bool LLApp::isRunning() +{ + return (APP_STATUS_RUNNING == sStatus); +} + + +// static +bool LLApp::isError() +{ + return (APP_STATUS_ERROR == sStatus); +} + + +// static +bool LLApp::isQuitting() +{ + return (APP_STATUS_QUITTING == sStatus); +} + +bool LLApp::isExiting() +{ + return isQuitting() || isError(); +} + +#if !LL_WINDOWS +// static +U32 LLApp::getSigChildCount() +{ + if (sSigChildCount) + { + return U32(*sSigChildCount); + } + return 0; +} + +// static +void LLApp::incSigChildCount() +{ + if (sSigChildCount) + { + (*sSigChildCount)++; + } +} + +#endif + + +// static +int LLApp::getPid() +{ +#if LL_WINDOWS + return 0; +#else + return getpid(); +#endif +} + +#if LL_WINDOWS +LONG WINAPI default_windows_exception_handler(struct _EXCEPTION_POINTERS *exception_infop) +{ + // Translate the signals/exceptions into cross-platform stuff + // Windows implementation + + // Make sure the user sees something to indicate that the app crashed. + LONG retval; + + if (LLApp::isError()) + { + llwarns << "Got another fatal signal while in the error handler, die now!" << llendl; + retval = EXCEPTION_EXECUTE_HANDLER; + return retval; + } + + // Flag status to error, so thread_error starts its work + LLApp::setError(); + + // Block in the exception handler until the app has stopped + // This is pretty sketchy, but appears to work just fine + while (!LLApp::isStopped()) + { + ms_sleep(10); + } + + // + // Generate a minidump if we can. + // + // FIXME: This needs to be ported over form the viewer-specific LLWinDebug class + + // + // At this point, we always want to exit the app. There's no graceful + // recovery for an unhandled exception. + // + // Just kill the process. + retval = EXCEPTION_EXECUTE_HANDLER; + return retval; +} + +#else //!LL_WINDOWS +void LLApp::setChildCallback(pid_t pid, LLAppChildCallback callback) +{ + LLChildInfo child_info; + child_info.mCallback = callback; + LLApp::sChildMap[pid] = child_info; +} + +void LLApp::setDefaultChildCallback(LLAppChildCallback callback) +{ + LLApp::sDefaultChildCallback = callback; +} + +pid_t LLApp::fork() +{ + pid_t pid = ::fork(); + if( pid < 0 ) + { + int system_error = errno; + llwarns << "Unable to fork! Operating system error code: " + << system_error << llendl; + } + else if (pid == 0) + { + // Sleep a bit to allow the parent to set up child callbacks. + ms_sleep(10); + + // We need to disable signal handling, because we don't have a + // signal handling thread anymore. + setupErrorHandling(); + } + else + { + llinfos << "Forked child process " << pid << llendl; + } + return pid; +} + +void setup_signals() +{ + // + // Set up signal handlers that may result in program termination + // + struct sigaction act; + act.sa_sigaction = default_unix_signal_handler; + sigemptyset( &act.sa_mask ); + act.sa_flags = SA_SIGINFO; + + // Synchronous signals + sigaction(SIGABRT, &act, NULL); + sigaction(SIGALRM, &act, NULL); + sigaction(SIGBUS, &act, NULL); + sigaction(SIGFPE, &act, NULL); + sigaction(SIGHUP, &act, NULL); + sigaction(SIGILL, &act, NULL); + sigaction(SIGPIPE, &act, NULL); + sigaction(SIGSEGV, &act, NULL); + sigaction(SIGSYS, &act, NULL); + + // Asynchronous signals that are normally ignored + sigaction(SIGCHLD, &act, NULL); + sigaction(SIGUSR2, &act, NULL); + + // Asynchronous signals that result in attempted graceful exit + sigaction(SIGHUP, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGINT, &act, NULL); + + // Asynchronous signals that result in core + sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL); + sigaction(SIGQUIT, &act, NULL); +} + +void clear_signals() +{ + struct sigaction act; + act.sa_handler = SIG_DFL; + sigemptyset( &act.sa_mask ); + act.sa_flags = SA_SIGINFO; + + // Synchronous signals + sigaction(SIGABRT, &act, NULL); + sigaction(SIGALRM, &act, NULL); + sigaction(SIGBUS, &act, NULL); + sigaction(SIGFPE, &act, NULL); + sigaction(SIGHUP, &act, NULL); + sigaction(SIGILL, &act, NULL); + sigaction(SIGPIPE, &act, NULL); + sigaction(SIGSEGV, &act, NULL); + sigaction(SIGSYS, &act, NULL); + + // Asynchronous signals that are normally ignored + sigaction(SIGCHLD, &act, NULL); + + // Asynchronous signals that result in attempted graceful exit + sigaction(SIGHUP, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGINT, &act, NULL); + + // Asynchronous signals that result in core + sigaction(SIGUSR2, &act, NULL); + sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL); + sigaction(SIGQUIT, &act, NULL); +} + + + +void default_unix_signal_handler(int signum, siginfo_t *info, void *) +{ + // Unix implementation of synchronous signal handler + // This runs in the thread that threw the signal. + // We do the somewhat sketchy operation of blocking in here until the error handler + // has gracefully stopped the app. + + if (LLApp::sLogInSignal) + { + llinfos << "Signal handler - Got signal " << signum << " - " << apr_signal_description_get(signum) << llendl; + } + + + switch (signum) + { + case SIGALRM: + case SIGUSR2: + // We don't care about these signals, ignore them + if (LLApp::sLogInSignal) + { + llinfos << "Signal handler - Ignoring this signal" << llendl; + } + return; + case SIGCHLD: + if (LLApp::sLogInSignal) + { + llinfos << "Signal handler - Got SIGCHLD from " << info->si_pid << llendl; + } + // Check result code for all child procs for which we've registered callbacks + // THIS WILL NOT WORK IF SIGCHLD IS SENT w/o killing the child (Go, launcher!) + // FIXME: Now that we're using SIGACTION, we can actually implement the launcher behavior to determine + // who sent the SIGCHLD even if it doesn't result in child termination + if (LLApp::sChildMap.count(info->si_pid)) + { + LLApp::sChildMap[info->si_pid].mGotSigChild = TRUE; + } + + LLApp::incSigChildCount(); + + return; + case SIGABRT: + // Abort just results in termination of the app, no funky error handling. + if (LLApp::sLogInSignal) + { + llwarns << "Signal handler - Got SIGABRT, terminating" << llendl; + } + clear_signals(); + raise(signum); + return; + case LL_SMACKDOWN_SIGNAL: // Smackdown treated just like any other app termination, for now + if (LLApp::sLogInSignal) + { + llwarns << "Signal handler - Handling smackdown signal!" << llendl; + } + else + { + // Don't log anything, even errors - this is because this signal could happen anywhere. + gErrorStream.setLevel(LLErrorStream::NONE); + } + + // Change the signal that we reraise to SIGABRT, so we generate a core dump. + signum = SIGABRT; + case SIGPIPE: + case SIGBUS: + case SIGSEGV: + case SIGQUIT: + if (LLApp::sLogInSignal) + { + llwarns << "Signal handler - Handling fatal signal!" << llendl; + } + if (LLApp::isError()) + { + // Received second fatal signal while handling first, just die right now + // Set the signal handlers back to default before handling the signal - this makes the next signal wipe out the app. + clear_signals(); + + if (LLApp::sLogInSignal) + { + llwarns << "Signal handler - Got another fatal signal while in the error handler, die now!" << llendl; + } + raise(signum); + return; + } + + if (LLApp::sLogInSignal) + { + llwarns << "Signal handler - Flagging error status and waiting for shutdown" << llendl; + } + // Flag status to ERROR, so thread_error does its work. + LLApp::setError(); + // Block in the signal handler until somebody says that we're done. + while (LLApp::sErrorThreadRunning && !LLApp::isStopped()) + { + ms_sleep(10); + } + + if (LLApp::sLogInSignal) + { + llwarns << "Signal handler - App is stopped, reraising signal" << llendl; + } + clear_signals(); + raise(signum); + return; + case SIGINT: + case SIGHUP: + case SIGTERM: + if (LLApp::sLogInSignal) + { + llwarns << "Signal handler - Got SIGINT, HUP, or TERM, exiting gracefully" << llendl; + } + // Graceful exit + // Just set our state to quitting, not error + if (LLApp::isQuitting() || LLApp::isError()) + { + // We're already trying to die, just ignore this signal + if (LLApp::sLogInSignal) + { + llinfos << "Signal handler - Already trying to quit, ignoring signal!" << llendl; + } + return; + } + LLApp::setQuitting(); + return; + default: + if (LLApp::sLogInSignal) + { + llwarns << "Signal handler - Unhandled signal, ignoring!" << llendl; + } + } +} + +#endif // !WINDOWS diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h new file mode 100644 index 0000000000..da5662c54d --- /dev/null +++ b/indra/llcommon/llapp.h @@ -0,0 +1,259 @@ +/** + * @file llapp.h + * @brief Declaration of the LLApp class. + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLAPP_H +#define LL_LLAPP_H + +#include +#include "llapr.h" +#include "llrun.h" +#include "llsd.h" + +// Forward declarations +class LLErrorThread; +class LLApp; + + +typedef void (*LLAppErrorHandler)(); +typedef void (*LLAppChildCallback)(int pid, bool exited, int status); + +#if !LL_WINDOWS +extern const S32 LL_SMACKDOWN_SIGNAL; + +// Clear all of the signal handlers (which we want to do for the child process when we fork +void clear_signals(); + +class LLChildInfo +{ +public: + LLChildInfo() : mGotSigChild(FALSE), mCallback(NULL) {} + BOOL mGotSigChild; + LLAppChildCallback mCallback; +}; +#endif + +class LLApp +{ + friend class LLErrorThread; +public: + typedef enum e_app_status + { + APP_STATUS_RUNNING, // The application is currently running - the default status + APP_STATUS_QUITTING, // The application is currently quitting - threads should listen for this and clean up + APP_STATUS_STOPPED, // The application is no longer running - tells the error thread it can exit + APP_STATUS_ERROR // The application had a fatal error occur - tells the error thread to run + } EAppStatus; + + + LLApp(); + virtual ~LLApp(); + + /** + * @brief Return the static app instance if one was created. + */ + static LLApp* instance(); + + /** @name Runtime options */ + //@{ + /** + * @brief Enumeration to specify option priorities in highest to + * lowest order. + */ + enum OptionPriority + { + PRIORITY_RUNTIME_OVERRIDE, + PRIORITY_COMMAND_LINE, + PRIORITY_SPECIFIC_CONFIGURATION, + PRIORITY_GENERAL_CONFIGURATION, + PRIORITY_DEFAULT, + PRIORITY_COUNT + }; + + /** + * @brief Get the application option at the highest priority. + * + * If the return value is undefined, the option does not exist. + * @param name The name of the option. + * @return Returns the option data. + */ + LLSD getOption(const std::string& name) const; + + /** + * @brief Parse command line options and insert them into + * application command line options. + * + * The name inserted into the option will have leading option + * identifiers (a minus or double minus) stripped. All options + * with values will be stored as a string, while all options + * without values will be stored as true. + * @param argc The argc passed into main(). + * @param argv The argv passed into main(). + * @return Returns true if the parse succeeded. + */ + bool parseCommandOptions(int argc, char** argv); + + /** + * @brief Set the options at the specified priority. + * + * This function completely replaces the options at the priority + * level with the data specified. This function will make sure + * level and data might be valid before doing the replace. + * @param level The priority level of the data. + * @param data The data to set. + * @return Returns true if the option was set. + */ + bool setOptionData(OptionPriority level, LLSD data); + + /** + * @brief Get the option data at the specified priority. + * + * This method is probably not so useful except when merging + * information. + * @param level The priority level of the data. + * @return Returns The data (if any) at the level priority. + */ + LLSD getOptionData(OptionPriority level); + //@} + + + + // + // Main application logic + // + virtual bool init() = 0; // Override to do application initialization + + // + // cleanup() + // + // It's currently assumed that the cleanup() method will only get + // called from the main thread or the error handling thread, as it will + // likely do thread shutdown, among other things. + // + virtual bool cleanup() = 0; // Override to do application cleanup + + // + // mainLoop() + // + // Runs the application main loop. It's assumed that when you exit + // this method, the application is in one of the cleanup states, either QUITTING or ERROR + // + virtual bool mainLoop() = 0; // Override for the application main loop. Needs to at least gracefully notice the QUITTING state and exit. + + + // + // Application status + // + static void setQuitting(); // Set status to QUITTING, the app is now shutting down + static void setStopped(); // Set status to STOPPED, the app is done running and should exit + static void setError(); // Set status to ERROR, the error handler should run + static bool isStopped(); + static bool isRunning(); + static bool isQuitting(); + static bool isError(); + static bool isExiting(); // Either quitting or error (app is exiting, cleanly or not) +#if !LL_WINDOWS + static U32 getSigChildCount(); + static void incSigChildCount(); +#endif + static int getPid(); + + // + // Error handling methods + // + void setErrorHandler(LLAppErrorHandler handler); + +#if !LL_WINDOWS + // + // Child process handling (Unix only for now) + // + // Set a callback to be run on exit of a child process + // WARNING! This callback is run from the signal handler due to the extreme crappiness of + // Linux threading requiring waitpid() to be called from the thread that spawned the process. + // At some point I will make this more behaved, but I'm not going to fix this right now - djs + void setChildCallback(pid_t pid, LLAppChildCallback callback); + + // The child callback to run if no specific handler is set + void setDefaultChildCallback(LLAppChildCallback callback); + + // Fork and do the proper signal handling/error handling mojo + // WARNING: You need to make sure your signal handling callback is correct after + // you fork, because not all threads are duplicated when you fork! + pid_t fork(); +#endif + + /** + * @brief Get a reference to the application runner + * + * Please use the runner with caution. Since the Runner usage + * pattern is not yet clear, this method just gives access to it + * to add and remove runnables. + * @return Returns the application runner. Do not save the + * pointer past the caller's stack frame. + */ + LLRunner& getRunner() { return mRunner; } + +public: + typedef std::map string_map; + string_map mOptionMap; // Contains all command-line options and arguments in a map + +protected: + + static void setStatus(EAppStatus status); // Use this to change the application status. + static EAppStatus sStatus; // Reflects current application status + static BOOL sErrorThreadRunning; // Set while the error thread is running + +#if !LL_WINDOWS + static LLAtomicU32* sSigChildCount; // Number of SIGCHLDs received. + typedef std::map child_map; // Map key is a PID + static child_map sChildMap; + static LLAppChildCallback sDefaultChildCallback; +#endif + + /** + * @brief This method is called once a frame to do once a frame tasks. + */ + void stepFrame(); + +private: + void setupErrorHandling(); // Do platform-specific error-handling setup (signals, structured exceptions) + + static void runErrorHandler(); + + // FIXME: On Windows, we need a routine to reset the structured exception handler when some evil driver has taken it over for their own purposes + + typedef int(*signal_handler_func)(int signum); + static LLAppErrorHandler sErrorHandler; + + // Default application threads + LLErrorThread* mThreadErrorp; // Waits for app to go to status ERROR, then runs the error callback + + // This is the application level runnable scheduler. + LLRunner mRunner; + + /** @name Runtime option implementation */ + //@{ + + // The application options. + LLSD mOptions; + + //@} + +private: + // the static application instance if it was created. + static LLApp* sApplication; + + +#if !LL_WINDOWS + friend void default_unix_signal_handler(int signum, siginfo_t *info, void *); +#endif + +public: + static BOOL sLogInSignal; +}; + +#endif // LL_LLAPP_H diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp new file mode 100644 index 0000000000..a7fc6a40a7 --- /dev/null +++ b/indra/llcommon/llapr.cpp @@ -0,0 +1,201 @@ +/** + * @file llapr.cpp + * @author Phoenix + * @date 2004-11-28 + * @brief Helper functions for using the apache portable runtime library. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llapr.h" + +apr_pool_t *gAPRPoolp = NULL; // Global APR memory pool +apr_thread_mutex_t *gLogMutexp = NULL; + + +void ll_init_apr() +{ + if (!gAPRPoolp) + { + // Initialize APR and create the global pool + apr_initialize(); + apr_pool_create(&gAPRPoolp, NULL); + + // Initialize the logging mutex + apr_thread_mutex_create(&gLogMutexp, APR_THREAD_MUTEX_DEFAULT, gAPRPoolp); + } +} + + +void ll_cleanup_apr() +{ + llinfos << "Cleaning up APR" << llendl; + + if (gLogMutexp) + { + // Clean up the logging mutex + + // All other threads NEED to be done before we clean up APR, so this is okay. + apr_thread_mutex_destroy(gLogMutexp); + gLogMutexp = NULL; + } + if (gAPRPoolp) + { + apr_pool_destroy(gAPRPoolp); + gAPRPoolp = NULL; + } + apr_terminate(); +} + +// +// LLScopedLock +// +LLScopedLock::LLScopedLock(apr_thread_mutex_t* mutex) : mMutex(mutex) +{ + if(mutex) + { + if(ll_apr_warn_status(apr_thread_mutex_lock(mMutex))) + { + mLocked = false; + } + else + { + mLocked = true; + } + } + else + { + mLocked = false; + } +} + +LLScopedLock::~LLScopedLock() +{ + unlock(); +} + +void LLScopedLock::unlock() +{ + if(mLocked) + { + if(!ll_apr_warn_status(apr_thread_mutex_unlock(mMutex))) + { + mLocked = false; + } + } +} + +// +// Misc functions +// +bool ll_apr_warn_status(apr_status_t status) +{ + if(APR_SUCCESS == status) return false; + char buf[MAX_STRING]; /* Flawfinder: ignore */ + llwarns << "APR: " << apr_strerror(status, buf, MAX_STRING) << llendl; + return true; +} + +void ll_apr_assert_status(apr_status_t status) +{ + llassert(ll_apr_warn_status(status) == false); +} + +apr_file_t* ll_apr_file_open(const LLString& filename, apr_int32_t flags, S32* sizep) +{ + apr_file_t* apr_file; + apr_status_t s; + s = apr_file_open(&apr_file, filename.c_str(), flags, APR_OS_DEFAULT, gAPRPoolp); + if (s != APR_SUCCESS) + { + if (sizep) + { + *sizep = 0; + } + return NULL; + } + + if (sizep) + { + S32 file_size = 0; + apr_off_t offset = 0; + if (apr_file_seek(apr_file, APR_END, &offset) == APR_SUCCESS) + { + file_size = (S32)offset; + offset = 0; + apr_file_seek(apr_file, APR_SET, &offset); + } + *sizep = file_size; + } + + return apr_file; +} + +S32 ll_apr_file_read(apr_file_t* apr_file, void *buf, S32 nbytes) +{ + apr_size_t sz = nbytes; + apr_status_t s = apr_file_read(apr_file, buf, &sz); + if (s != APR_SUCCESS) + { + return 0; + } + else + { + return (S32)sz; + } +} + + +S32 ll_apr_file_write(apr_file_t* apr_file, const void *buf, S32 nbytes) +{ + apr_size_t sz = nbytes; + apr_status_t s = apr_file_write(apr_file, buf, &sz); + if (s != APR_SUCCESS) + { + return 0; + } + else + { + return (S32)sz; + } +} + +S32 ll_apr_file_seek(apr_file_t* apr_file, apr_seek_where_t where, S32 offset) +{ + apr_off_t apr_offset = offset; + apr_status_t s = apr_file_seek(apr_file, where, &apr_offset); + if (s != APR_SUCCESS) + { + return -1; + } + else + { + return (S32)apr_offset; + } +} + +bool ll_apr_file_remove(const LLString& filename) +{ + apr_status_t s; + s = apr_file_remove(filename.c_str(), gAPRPoolp); + if (s != APR_SUCCESS) + { + llwarns << "ll_apr_file_remove failed on file: " << filename << llendl; + return false; + } + return true; +} + +bool ll_apr_file_rename(const LLString& filename, const LLString& newname) +{ + apr_status_t s; + s = apr_file_rename(filename.c_str(), newname.c_str(), gAPRPoolp); + if (s != APR_SUCCESS) + { + llwarns << "ll_apr_file_rename failed on file: " << filename << llendl; + return false; + } + return true; +} diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h new file mode 100644 index 0000000000..1e9e944eef --- /dev/null +++ b/indra/llcommon/llapr.h @@ -0,0 +1,129 @@ +/** + * @file llapr.h + * @author Phoenix + * @date 2004-11-28 + * @brief Helper functions for using the apache portable runtime library. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLAPR_H +#define LL_LLAPR_H + +#if LL_LINUX +#include // Need PATH_MAX in APR headers... +#endif + +#include "boost/noncopyable.hpp" + +#include "apr-1/apr_thread_proc.h" +#include "apr-1/apr_thread_mutex.h" +#include "apr-1/apr_getopt.h" +#include "apr-1/apr_signal.h" +#include "apr-1/apr_atomic.h" +#include "llstring.h" + + +/** + * @brief initialize the common apr constructs -- apr itself, the + * global pool, and a mutex. + */ +void ll_init_apr(); + +/** + * @brief Cleanup those common apr constructs. + */ +void ll_cleanup_apr(); + +/** + * @class LLScopedLock + * @brief Small class to help lock and unlock mutexes. + * + * This class is used to have a stack level lock once you already have + * an apr mutex handy. The constructor handles the lock, and the + * destructor handles the unlock. Instances of this class are + * not thread safe. + */ +class LLScopedLock : private boost::noncopyable +{ +public: + /** + * @brief Constructor which accepts a mutex, and locks it. + * + * @param mutex An allocated APR mutex. If you pass in NULL, + * this wrapper will not lock. + */ + LLScopedLock(apr_thread_mutex_t* mutex); + + /** + * @brief Destructor which unlocks the mutex if still locked. + */ + ~LLScopedLock(); + + /** + * @brief Check lock. + */ + bool isLocked() const { return mLocked; } + + /** + * @brief This method unlocks the mutex. + */ + void unlock(); + +protected: + bool mLocked; + apr_thread_mutex_t* mMutex; +}; + +template class LLAtomic32 +{ +public: + LLAtomic32() {}; + LLAtomic32(Type x) {apr_atomic_set32(&mData, apr_uint32_t(x)); }; + ~LLAtomic32() {}; + + operator const Type() { apr_uint32_t data = apr_atomic_read32(&mData); return Type(data); } + Type operator =(const Type& x) { apr_atomic_set32(&mData, apr_uint32_t(x)); return Type(mData); } + void operator -=(Type x) { apr_atomic_sub32(&mData, apr_uint32_t(x)); } + void operator +=(Type x) { apr_atomic_add32(&mData, apr_uint32_t(x)); } + Type operator ++(int) { return apr_atomic_inc32(&mData); } // Type++ + Type operator --(int) { return apr_atomic_dec32(&mData); } // Type-- + +private: + apr_uint32_t mData; +}; + +typedef LLAtomic32 LLAtomicU32; +typedef LLAtomic32 LLAtomicS32; + +// File IO convenience functions. +// Returns NULL if the file fails to openm sets *sizep to file size of not NULL +// abbreviated flags +#define LL_APR_R (APR_READ) // "r" +#define LL_APR_W (APR_CREATE|APR_TRUNCATE|APR_WRITE) // "w" +#define LL_APR_RB (APR_READ|APR_BINARY) // "rb" +#define LL_APR_WB (APR_CREATE|APR_TRUNCATE|APR_WRITE|APR_BINARY) // "wb" +#define LL_APR_RPB (APR_READ|APR_WRITE|APR_BINARY) // "r+b" +#define LL_APR_WPB (APR_CREATE|APR_TRUNCATE|APR_READ|APR_WRITE|APR_BINARY) // "w+b" +apr_file_t* ll_apr_file_open(const LLString& filename, apr_int32_t flags, S32* sizep = NULL); +// Returns actual offset, -1 if seek fails +S32 ll_apr_file_seek(apr_file_t* apr_file, apr_seek_where_t where, S32 offset); +// Returns bytes read/written, 0 if read/write fails +S32 ll_apr_file_read(apr_file_t* apr_file, void* buf, S32 nbytes); +S32 ll_apr_file_write(apr_file_t* apr_file, const void* buf, S32 nbytes); +bool ll_apr_file_remove(const LLString& filename); +bool ll_apr_file_rename(const LLString& filename, const LLString& newname); + +/** + * @brief Function which approprately logs error or remains quiet on + * APR_SUCCESS. + * @return Returns true if status is an error condition. + */ +bool ll_apr_warn_status(apr_status_t status); + +void ll_apr_assert_status(apr_status_t status); + +extern "C" apr_pool_t* gAPRPoolp; // Global APR memory pool + +#endif // LL_LLAPR_H diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp new file mode 100644 index 0000000000..f6d9f166e1 --- /dev/null +++ b/indra/llcommon/llassettype.cpp @@ -0,0 +1,219 @@ +/** + * @file llassettype.cpp + * @brief Implementatino of LLAssetType functionality. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include + +#include "llassettype.h" +#include "lltimer.h" + +// I added lookups for exact text of asset type enums in addition to the ones below, so shoot me. -Steve + +struct asset_info_t +{ + LLAssetType::EType type; + const char* desc; +}; + +asset_info_t asset_types[] = +{ + { LLAssetType::AT_TEXTURE, "TEXTURE" }, + { LLAssetType::AT_SOUND, "SOUND" }, + { LLAssetType::AT_CALLINGCARD, "CALLINGCARD" }, + { LLAssetType::AT_LANDMARK, "LANDMARK" }, + { LLAssetType::AT_SCRIPT, "SCRIPT" }, + { LLAssetType::AT_CLOTHING, "CLOTHING" }, + { LLAssetType::AT_OBJECT, "OBJECT" }, + { LLAssetType::AT_NOTECARD, "NOTECARD" }, + { LLAssetType::AT_CATEGORY, "CATEGORY" }, + { LLAssetType::AT_ROOT_CATEGORY, "ROOT_CATEGORY" }, + { LLAssetType::AT_LSL_TEXT, "LSL_TEXT" }, + { LLAssetType::AT_LSL_BYTECODE, "LSL_BYTECODE" }, + { LLAssetType::AT_TEXTURE_TGA, "TEXTURE_TGA" }, + { LLAssetType::AT_BODYPART, "BODYPART" }, + { LLAssetType::AT_TRASH, "TRASH" }, + { LLAssetType::AT_SNAPSHOT_CATEGORY, "SNAPSHOT_CATEGORY" }, + { LLAssetType::AT_LOST_AND_FOUND, "LOST_AND_FOUND" }, + { LLAssetType::AT_SOUND_WAV, "SOUND_WAV" }, + { LLAssetType::AT_IMAGE_TGA, "IMAGE_TGA" }, + { LLAssetType::AT_IMAGE_JPEG, "IMAGE_JPEG" }, + { LLAssetType::AT_ANIMATION, "ANIMATION" }, + { LLAssetType::AT_GESTURE, "GESTURE" }, + { LLAssetType::AT_SIMSTATE, "SIMSTATE" }, + { LLAssetType::AT_NONE, "NONE" }, +}; + +LLAssetType::EType LLAssetType::getType(const LLString& sin) +{ + LLString s = sin; + LLString::toUpper(s); + for (S32 idx = 0; ;idx++) + { + asset_info_t* info = asset_types + idx; + if (info->type == LLAssetType::AT_NONE) + break; + if (s == info->desc) + return info->type; + } + return LLAssetType::AT_NONE; +} + +LLString LLAssetType::getDesc(LLAssetType::EType type) +{ + for (S32 idx = 0; ;idx++) + { + asset_info_t* info = asset_types + idx; + if (type == info->type) + return LLString(info->desc); + if (info->type == LLAssetType::AT_NONE) + break; + } + return LLString("BAD TYPE"); +} + +//============================================================================ + +// The asset type names are limited to 8 characters. +// static +const char* LLAssetType::mAssetTypeNames[LLAssetType::AT_COUNT] = +{ + "texture", + "sound", + "callcard", + "landmark", + "script", + "clothing", + "object", + "notecard", + "category", + "root", + "lsltext", + "lslbyte", + "txtr_tga",// Intentionally spelled this way. Limited to eight characters. + "bodypart", + "trash", + "snapshot", + "lstndfnd", + "snd_wav", + "img_tga", + "jpeg", + "animatn", + "gesture", + "simstate", +}; + +// This table is meant for decoding to human readable form. Put any +// and as many printable characters you want in each one. +// See also llinventory.cpp INVENTORY_TYPE_HUMAN_NAMES +const char* LLAssetType::mAssetTypeHumanNames[LLAssetType::AT_COUNT] = +{ + "texture", + "sound", + "calling card", + "landmark", + "legacy script", + "clothing", + "object", + "note card", + "folder", + "root", + "lsl2 script", + "lsl bytecode", + "tga texture", + "body part", + "trash", + "snapshot", + "lost and found", + "sound", + "targa image", + "jpeg image", + "animation", + "gesture", + "simstate", +}; + +///---------------------------------------------------------------------------- +/// class LLAssetType +///---------------------------------------------------------------------------- + +// static +const char* LLAssetType::lookup( LLAssetType::EType type ) +{ + if( (type >= 0) && (type < AT_COUNT )) + { + return mAssetTypeNames[ S32( type ) ]; + } + else + { + return "-1"; + } +} + +// static +LLAssetType::EType LLAssetType::lookup( const char* name ) +{ + for( S32 i = 0; i < AT_COUNT; i++ ) + { + if( 0 == strcmp(name, mAssetTypeNames[i]) ) + { + // match + return (EType)i; + } + } + return AT_NONE; +} + +// static +const char* LLAssetType::lookupHumanReadable(LLAssetType::EType type) +{ + if( (type >= 0) && (type < AT_COUNT )) + { + return mAssetTypeHumanNames[S32(type)]; + } + else + { + return NULL; + } +} + +EDragAndDropType LLAssetType::lookupDragAndDropType( EType asset ) +{ + switch( asset ) + { + case AT_TEXTURE: return DAD_TEXTURE; + case AT_SOUND: return DAD_SOUND; + case AT_CALLINGCARD: return DAD_CALLINGCARD; + case AT_LANDMARK: return DAD_LANDMARK; + case AT_SCRIPT: return DAD_NONE; + case AT_CLOTHING: return DAD_CLOTHING; + case AT_OBJECT: return DAD_OBJECT; + case AT_NOTECARD: return DAD_NOTECARD; + case AT_CATEGORY: return DAD_CATEGORY; + case AT_ROOT_CATEGORY: return DAD_ROOT_CATEGORY; + case AT_LSL_TEXT: return DAD_SCRIPT; + case AT_BODYPART: return DAD_BODYPART; + case AT_ANIMATION: return DAD_ANIMATION; + case AT_GESTURE: return DAD_GESTURE; + default: return DAD_NONE; + }; +} + +// static. Generate a good default description +void LLAssetType::generateDescriptionFor(LLAssetType::EType type, + LLString& desc) +{ + const S32 BUF_SIZE = 30; + char time_str[BUF_SIZE]; /* Flawfinder: ignore */ + time_t now; + time(&now); + memset(time_str, '\0', BUF_SIZE); + strftime(time_str, BUF_SIZE - 1, "%Y-%m-%d %H:%M:%S ", localtime(&now)); + desc.assign(time_str); + desc.append(LLAssetType::lookupHumanReadable(type)); +} diff --git a/indra/llcommon/llassettype.h b/indra/llcommon/llassettype.h new file mode 100644 index 0000000000..2b5aadf078 --- /dev/null +++ b/indra/llcommon/llassettype.h @@ -0,0 +1,149 @@ +/** + * @file llassettype.h + * @brief Declaration of LLAssetType. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLASSETTYPE +#define LL_LLASSETTYPE + +#include "stdenums.h" // for EDragAndDropType + +class LLAssetType +{ +public: + enum EType + { + // Used for painting the faces of geometry. + // Stored in typical j2c stream format + AT_TEXTURE = 0, + + // Used to fill the aural spectrum. + AT_SOUND = 1, + + // Links instant message access to the user on the card. eg, a + // card for yourself, a card for linden support, a card for + // the guy you were talking to in the coliseum. + AT_CALLINGCARD = 2, + + // Links to places in the world with location and a screen + // shot or image saved. eg, home, linden headquarters, the + // coliseum, or destinations where we want to increase + // traffic. + AT_LANDMARK = 3, + + // Valid scripts that can be attached to an object. eg. open a + // door, jump into the air. + AT_SCRIPT = 4, + + // A collection of textures and parameters that can be worn + // by an avatar. + AT_CLOTHING = 5, + + // Any combination of textures, sounds, and scripts that are + // associated with a fixed piece of geometry. eg, a hot tub, a + // house with working door. + AT_OBJECT = 6, + + // Just text + AT_NOTECARD = 7, + + // A category holds a collection of inventory items. It's + // treated as an item in the inventory, and therefore needs a + // type. + AT_CATEGORY = 8, + + // A root category is a user's root inventory category. We + // decided to expose it visually, so it seems logical to fold + // it into the asset types. + AT_ROOT_CATEGORY = 9, + + // The LSL is the brand spanking new scripting language. We've + // split it into a text and bytecode representation. + AT_LSL_TEXT = 10, + AT_LSL_BYTECODE = 11, + + // uncompressed TGA texture + AT_TEXTURE_TGA = 12, + + // A collection of textures and parameters that can be worn + // by an avatar. + AT_BODYPART = 13, + + // This asset type is meant to only be used as a marker for a + // category preferred type. Using this, we can throw things in + // the trash before completely deleting. + AT_TRASH = 14, + + // This is a marker for a folder meant for snapshots. No + // actual assets will be snapshots, though if there were, you + // could interpret them as textures. + AT_SNAPSHOT_CATEGORY = 15, + + // This is used to stuff lost&found items into + AT_LOST_AND_FOUND = 16, + + // uncompressed sound + AT_SOUND_WAV = 17, + + // uncompressed image, non-square, and not appropriate for use + // as a texture. + AT_IMAGE_TGA = 18, + + // compressed image, non-square, and not appropriate for use + // as a texture. + AT_IMAGE_JPEG = 19, + + // animation + AT_ANIMATION = 20, + + // gesture, sequence of animations, sounds, chat, wait steps + AT_GESTURE = 21, + + // simstate file + AT_SIMSTATE = 22, + + // +*********************************************+ + // | TO ADD AN ELEMENT TO THIS ENUM: | + // +*********************************************+ + // | 1. INSERT BEFORE AT_COUNT | + // | 2. INCREMENT AT_COUNT BY 1 | + // | 3. ADD TO LLAssetType::mAssetTypeNames | + // | 4. ADD TO LLAssetType::mAssetTypeHumanNames | + // +*********************************************+ + + AT_COUNT = 23, + + AT_NONE = -1 + }; + + // machine transation between type and strings + static EType lookup(const char* name); + static const char* lookup(EType type); + + // translation from a type to a human readable form. + static const char* lookupHumanReadable(EType type); + + static EDragAndDropType lookupDragAndDropType( EType ); + + // Generate a good default description. You may want to add a verb + // or agent name after this depending on your application. + static void generateDescriptionFor(LLAssetType::EType type, + LLString& desc); + + static EType getType(const LLString& sin); + static LLString getDesc(EType type); + +private: + // don't instantiate or derive one of these objects + LLAssetType( void ) {} + ~LLAssetType( void ) {} + +private: + static const char* mAssetTypeNames[]; + static const char* mAssetTypeHumanNames[]; +}; + +#endif // LL_LLASSETTYPE diff --git a/indra/llcommon/llassoclist.h b/indra/llcommon/llassoclist.h new file mode 100644 index 0000000000..d90a26dc8a --- /dev/null +++ b/indra/llcommon/llassoclist.h @@ -0,0 +1,278 @@ +/** + * @file llassoclist.h + * @brief LLAssocList class header file + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLASSOCLIST_H +#define LL_LLASSOCLIST_H + +//------------------------------------------------------------------------ +// LLAssocList is an associative list container class. +// +// The implementation is a single linked list. +// Both index and value objects are stored by value (not reference). +// If pointer values are specified for index and/or value, this +// container does NOT assume ownership of the referenced objects, +// and does NOT delete() them on removal or destruction of the container. +// +// Note that operations are generally not optimized, and may of them +// are O(n) complexity. +//------------------------------------------------------------------------ + +#include + +template +class LLAssocList +{ +private: + // internal list node type + class Node + { + public: + Node(const INDEX_TYPE &index, const VALUE_TYPE &value, Node *next) + { + mIndex = index; + mValue = value; + mNext = next; + } + ~Node() { } + INDEX_TYPE mIndex; + VALUE_TYPE mValue; + Node *mNext; + }; + + // head of the linked list + Node *mHead; + +public: + // Constructor + LLAssocList() + { + mHead = NULL; + } + + // Destructor + ~LLAssocList() + { + removeAll(); + } + + // Returns TRUE if list is empty. + BOOL isEmpty() + { + return (mHead == NULL); + } + + // Returns the number of items in the list. + U32 length() + { + U32 count = 0; + for ( Node *node = mHead; + node; + node = node->mNext ) + { + count++; + } + return count; + } + + // Removes item with the specified index. + BOOL remove( const INDEX_TYPE &index ) + { + if (!mHead) + return FALSE; + + if (mHead->mIndex == index) + { + Node *node = mHead; + mHead = mHead->mNext; + delete node; + return TRUE; + } + + for ( Node *prev = mHead; + prev->mNext; + prev = prev->mNext ) + { + if (prev->mNext->mIndex == index) + { + Node *node = prev->mNext; + prev->mNext = prev->mNext->mNext; + delete node; + return TRUE; + } + } + return FALSE; + } + + // Removes all items from the list. + void removeAll() + { + while ( mHead ) + { + Node *node = mHead; + mHead = mHead->mNext; + delete node; + } + } + + // Adds a new item to the head of the list, + // removing any existing item with same index. + void addToHead( const INDEX_TYPE &index, const VALUE_TYPE &value ) + { + remove(index); + Node *node = new Node(index, value, mHead); + mHead = node; + } + + // Adds a new item to the end of the list, + // removing any existing item with the same index. + void addToTail( const INDEX_TYPE &index, const VALUE_TYPE &value ) + { + remove(index); + Node *node = new Node(index, value, NULL); + if (!mHead) + { + mHead = node; + return; + } + for ( Node *prev=mHead; + prev; + prev=prev->mNext ) + { + if (!prev->mNext) + { + prev->mNext=node; + return; + } + } + } + + // Sets the value of a specified index. + // If index does not exist, a new value will be added only if + // 'addIfNotFound' is set to TRUE. + // Returns TRUE if successful. + BOOL setValue( const INDEX_TYPE &index, const VALUE_TYPE &value, BOOL addIfNotFound=FALSE ) + { + VALUE_TYPE *valueP = getValue(index); + if (valueP) + { + *valueP = value; + return TRUE; + } + if (!addIfNotFound) + return FALSE; + addToTail(index, value); + return TRUE; + } + + // Sets the ith value in the list. + // A new value will NOT be addded, if the ith value does not exist. + // Returns TRUE if successful. + BOOL setValueAt( U32 i, const VALUE_TYPE &value ) + { + VALUE_TYPE *valueP = getValueAt(i); + if (valueP) + { + *valueP = value; + return TRUE; + } + return FALSE; + } + + // Returns a pointer to the value for the specified index, + // or NULL if no item found. + VALUE_TYPE *getValue( const INDEX_TYPE &index ) + { + for ( Node *node = mHead; + node; + node = node->mNext ) + { + if (node->mIndex == index) + return &node->mValue; + } + return NULL; + } + + // Returns a pointer to the ith value in the list, or + // NULL if i is not valid. + VALUE_TYPE *getValueAt( U32 i ) + { + U32 count = 0; + for ( Node *node = mHead; + node; + node = node->mNext ) + { + if (count == i) + return &node->mValue; + count++; + } + return NULL; + } + + // Returns a pointer to the index for the specified index, + // or NULL if no item found. + INDEX_TYPE *getIndex( const INDEX_TYPE &index ) + { + for ( Node *node = mHead; + node; + node = node->mNext ) + { + if (node->mIndex == index) + return &node->mIndex; + } + return NULL; + } + + // Returns a pointer to the ith index in the list, or + // NULL if i is not valid. + INDEX_TYPE *getIndexAt( U32 i ) + { + U32 count = 0; + for ( Node *node = mHead; + node; + node = node->mNext ) + { + if (count == i) + return &node->mIndex; + count++; + } + return NULL; + } + + // Returns a pointer to the value for the specified index, + // or NULL if no item found. + VALUE_TYPE *operator[](const INDEX_TYPE &index) + { + return getValue(index); + } + + // Returns a pointer to the ith value in the list, or + // NULL if i is not valid. + VALUE_TYPE *operator[](U32 i) + { + return getValueAt(i); + } + + // Prints the list contents to the specified stream. + friend std::ostream &operator<<( std::ostream &os, LLAssocList &map ) + { + os << "{"; + for ( Node *node = map.mHead; + node; + node = node->mNext ) + { + os << "<" << node->mIndex << ", " << node->mValue << ">"; + if (node->mNext) + os << ", "; + } + os << "}"; + + return os; + } +}; + +#endif // LL_LLASSOCLIST_H diff --git a/indra/llcommon/llavatarconstants.h b/indra/llcommon/llavatarconstants.h new file mode 100644 index 0000000000..22a088af67 --- /dev/null +++ b/indra/llcommon/llavatarconstants.h @@ -0,0 +1,23 @@ +/** + * @file indra_constants.h + * @brief some useful short term constants for Indra + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_AVATAR_CONSTANTS_H +#define LL_AVATAR_CONSTANTS_H + +// If this string is passed to dataserver in AvatarPropertiesUpdate +// then no change is made to user.profile_web +const char* const BLACKLIST_PROFILE_WEB_STR = "featureWebProfilesDisabled"; + +// If profile web pages are feature blacklisted then this URL is +// shown in the profile instead of the user's set URL +const char* const BLACKLIST_PROFILE_WEB_URL = "http://secondlife.com/app/webdisabled"; + +// Maximum number of avatar picks +const S32 MAX_AVATAR_PICKS = 10; + +#endif diff --git a/indra/llcommon/llboost.h b/indra/llcommon/llboost.h new file mode 100644 index 0000000000..7b39ed082e --- /dev/null +++ b/indra/llcommon/llboost.h @@ -0,0 +1,25 @@ +/** + * @file llboost.h + * @brief helper object & functions for use with boost + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLBOOST_H +#define LL_LLBOOST_H + +#include + +// boost_tokenizer typedef +/* example usage: + boost_tokenizer tokens(input_string, boost::char_separator(" \t\n")); + for (boost_tokenizer::iterator token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + std::string tok = *token_iter; + process_token_string( tok ); + } +*/ +typedef boost::tokenizer > boost_tokenizer; + +#endif // LL_LLBOOST_H diff --git a/indra/llcommon/llchat.h b/indra/llcommon/llchat.h new file mode 100644 index 0000000000..88867b2081 --- /dev/null +++ b/indra/llcommon/llchat.h @@ -0,0 +1,69 @@ +/** + * @file llchat.h + * @author James Cook + * @brief Chat constants and data structures. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLCHAT_H +#define LL_LLCHAT_H + +#include "llstring.h" +#include "lluuid.h" +#include "v3math.h" + +// enumerations used by the chat system +typedef enum e_chat_source_type +{ + CHAT_SOURCE_SYSTEM = 0, + CHAT_SOURCE_AGENT = 1, + CHAT_SOURCE_OBJECT = 2 +} EChatSourceType; + +typedef enum e_chat_type +{ + CHAT_TYPE_WHISPER = 0, + CHAT_TYPE_NORMAL = 1, + CHAT_TYPE_SHOUT = 2, + CHAT_TYPE_START = 4, + CHAT_TYPE_STOP = 5, + CHAT_TYPE_DEBUG_MSG = 6 +} EChatType; + +typedef enum e_chat_audible_level +{ + CHAT_AUDIBLE_NOT = -1, + CHAT_AUDIBLE_BARELY = 0, + CHAT_AUDIBLE_FULLY = 1 +} EChatAudible; + +// A piece of chat +class LLChat +{ +public: + LLChat(const LLString& text = LLString::null) + : mText(text), + mFromName(), + mFromID(), + mSourceType(CHAT_SOURCE_AGENT), + mChatType(CHAT_TYPE_NORMAL), + mAudible(CHAT_AUDIBLE_FULLY), + mMuted(FALSE), + mTime(0.0), + mPosAgent() + { } + + LLString mText; // UTF-8 line of text + LLString mFromName; // agent or object name + LLUUID mFromID; // agent id or object id + EChatSourceType mSourceType; + EChatType mChatType; + EChatAudible mAudible; + BOOL mMuted; // pass muted chat to maintain list of chatters + F64 mTime; // viewer only, seconds from viewer start + LLVector3 mPosAgent; +}; + +#endif diff --git a/indra/llcommon/llclickaction.h b/indra/llcommon/llclickaction.h new file mode 100644 index 0000000000..538fae3658 --- /dev/null +++ b/indra/llcommon/llclickaction.h @@ -0,0 +1,20 @@ +/** + * @file llclickaction.h + * @author James Cook + * @brief Constants for single-click actions on objects + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLCLICKACTION_H +#define LL_LLCLICKACTION_H + +const U8 CLICK_ACTION_NONE = 0; +const U8 CLICK_ACTION_TOUCH = 0; +const U8 CLICK_ACTION_SIT = 1; +const U8 CLICK_ACTION_BUY = 2; +const U8 CLICK_ACTION_PAY = 3; +const U8 CLICK_ACTION_OPEN = 4; + +#endif diff --git a/indra/llcommon/llcommon.cpp b/indra/llcommon/llcommon.cpp new file mode 100644 index 0000000000..ff810abfa9 --- /dev/null +++ b/indra/llcommon/llcommon.cpp @@ -0,0 +1,43 @@ +/** + * @file llcommon.cpp + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llcommon.h" + +//static +BOOL LLCommon::sAprInitialized = FALSE; + +//static +void LLCommon::initClass() +{ + LLMemory::initClass(); + if (!sAprInitialized) + { + ll_init_apr(); + sAprInitialized = TRUE; + } + LLTimer::initClass(); + LLThreadSafeRefCount::initClass(); +// LLWorkerThread::initClass(); +// LLFrameCallbackManager::initClass(); +} + +//static +void LLCommon::cleanupClass() +{ +// LLFrameCallbackManager::cleanupClass(); +// LLWorkerThread::cleanupClass(); + LLThreadSafeRefCount::cleanupClass(); + LLTimer::cleanupClass(); + if (sAprInitialized) + { + ll_cleanup_apr(); + sAprInitialized = FALSE; + } + LLMemory::cleanupClass(); +} diff --git a/indra/llcommon/llcommon.h b/indra/llcommon/llcommon.h new file mode 100644 index 0000000000..c37d479ba9 --- /dev/null +++ b/indra/llcommon/llcommon.h @@ -0,0 +1,28 @@ +/** + * @file llcommon.h + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_COMMON_H +#define LL_COMMON_H + +#include "llmemory.h" +#include "llapr.h" +// #include "llframecallbackmanager.h" +#include "lltimer.h" +#include "llworkerthread.h" +#include "llfile.h" + +class LLCommon +{ +public: + static void initClass(); + static void cleanupClass(); +private: + static BOOL sAprInitialized; +}; + +#endif + diff --git a/indra/llcommon/llcriticaldamp.cpp b/indra/llcommon/llcriticaldamp.cpp new file mode 100644 index 0000000000..ee7dfecdaa --- /dev/null +++ b/indra/llcommon/llcriticaldamp.cpp @@ -0,0 +1,72 @@ +/** + * @file llcriticaldamp.cpp + * @brief Implementation of the critical damping functionality. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include + +#include "llcriticaldamp.h" + +//----------------------------------------------------------------------------- +// static members +//----------------------------------------------------------------------------- +LLFrameTimer LLCriticalDamp::sInternalTimer; +std::map LLCriticalDamp::sInterpolants; +F32 LLCriticalDamp::sTimeDelta; + +//----------------------------------------------------------------------------- +// LLCriticalDamp() +//----------------------------------------------------------------------------- +LLCriticalDamp::LLCriticalDamp() +{ + sTimeDelta = 0.f; +} + +// static +//----------------------------------------------------------------------------- +// updateInterpolants() +//----------------------------------------------------------------------------- +void LLCriticalDamp::updateInterpolants() +{ + sTimeDelta = sInternalTimer.getElapsedTimeAndResetF32(); + + F32 time_constant; + + for (std::map::iterator iter = sInterpolants.begin(); + iter != sInterpolants.end(); iter++) + { + time_constant = iter->first; + F32 new_interpolant = 1.f - pow(2.f, -sTimeDelta / time_constant); + new_interpolant = llclamp(new_interpolant, 0.f, 1.f); + sInterpolants[time_constant] = new_interpolant; + } +} + +//----------------------------------------------------------------------------- +// getInterpolant() +//----------------------------------------------------------------------------- +F32 LLCriticalDamp::getInterpolant(const F32 time_constant, BOOL use_cache) +{ + if (time_constant == 0.f) + { + return 1.f; + } + + if (use_cache && sInterpolants.count(time_constant)) + { + return sInterpolants[time_constant]; + } + + F32 interpolant = 1.f - pow(2.f, -sTimeDelta / time_constant); + interpolant = llclamp(interpolant, 0.f, 1.f); + if (use_cache) + { + sInterpolants[time_constant] = interpolant; + } + + return interpolant; +} diff --git a/indra/llcommon/llcriticaldamp.h b/indra/llcommon/llcriticaldamp.h new file mode 100644 index 0000000000..3f18db9ec3 --- /dev/null +++ b/indra/llcommon/llcriticaldamp.h @@ -0,0 +1,35 @@ +/** + * @file llcriticaldamp.h + * @brief A lightweight class that calculates critical damping constants once + * per frame. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLCRITICALDAMP_H +#define LL_LLCRITICALDAMP_H + +#include + +#include "llframetimer.h" + +class LLCriticalDamp +{ +public: + LLCriticalDamp(); + + // MANIPULATORS + static void updateInterpolants(); + + // ACCESSORS + static F32 getInterpolant(const F32 time_constant, BOOL use_cache = TRUE); + +protected: + static LLFrameTimer sInternalTimer; // frame timer for calculating deltas + + static std::map sInterpolants; + static F32 sTimeDelta; +}; + +#endif // LL_LLCRITICALDAMP_H diff --git a/indra/llcommon/lldarray.h b/indra/llcommon/lldarray.h new file mode 100644 index 0000000000..c8b5b7fb14 --- /dev/null +++ b/indra/llcommon/lldarray.h @@ -0,0 +1,192 @@ +/** + * @file lldarray.h + * @brief Wrapped std::vector for backward compatibility. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLDARRAY_H +#define LL_LLDARRAY_H + +#include "llmath.h" +#include "llerror.h" + +#include +#include + +// class LLDynamicArray<>; // = std::vector + reserves elements +// class LLDynamicArrayIndexed<>; // = std::vector + std::map if indices, only supports operator[] and begin(),end() + +//-------------------------------------------------------- +// LLDynamicArray declaration +//-------------------------------------------------------- +// NOTE: BlockSize is used to reserve a minimal initial amount +template +class LLDynamicArray : public std::vector +{ +public: + enum + { + OKAY = 0, + FAIL = -1 + }; + + LLDynamicArray(S32 size=0) : std::vector(size) { if (size < BlockSize) std::vector::reserve(BlockSize); } + + void reset() { std::vector::resize(0); } + + // ACCESSORS + const Type& get(S32 index) const { return std::vector::operator[](index); } + Type& get(S32 index) { return std::vector::operator[](index); } + S32 find(const Type &obj) const; + + S32 count() const { return std::vector::size(); } + S32 getLength() const { return std::vector::size(); } + S32 getMax() const { return std::vector::capacity(); } + + // MANIPULATE + S32 put(const Type &obj); // add to end of array, returns index +// Type* reserve(S32 num); // reserve a block of indices in advance + Type* reserve_block(U32 num); // reserve a block of indices in advance + + S32 remove(S32 index); // remove by index, no bounds checking + S32 removeObj(const Type &obj); // remove by object + S32 removeLast(); + + void operator+=(const LLDynamicArray &other); +}; + +//-------------------------------------------------------- +// LLDynamicArray implementation +//-------------------------------------------------------- + +template +inline S32 LLDynamicArray::find(const Type &obj) const +{ + typename std::vector::const_iterator iter = std::find(this->begin(), this->end(), obj); + if (iter != this->end()) + { + return iter - this->begin(); + } + return FAIL; +} + + +template +inline S32 LLDynamicArray::remove(S32 i) +{ + // This is a fast removal by swapping with the last element + S32 sz = this->size(); + if (i < 0 || i >= sz) + { + return FAIL; + } + if (i < sz-1) + { + this->operator[](i) = this->back(); + } + this->pop_back(); + return i; +} + +template +inline S32 LLDynamicArray::removeObj(const Type& obj) +{ + typename std::vector::iterator iter = std::find(this->begin(), this->end(), obj); + if (iter != this->end()) + { + typename std::vector::iterator last = this->end(); + --last; + *iter = *last; + this->pop_back(); + return iter - this->begin(); + } + return FAIL; +} + +template +inline S32 LLDynamicArray::removeLast() +{ + if (!this->empty()) + { + this->pop_back(); + return OKAY; + } + return FAIL; +} + +template +inline Type* LLDynamicArray::reserve_block(U32 num) +{ + U32 sz = this->size(); + this->resize(sz+num); + return &(this->operator[](sz)); +} + +template +inline S32 LLDynamicArray::put(const Type &obj) +{ + this->push_back(obj); + return this->size() - 1; +} + +template +inline void LLDynamicArray::operator+=(const LLDynamicArray &other) +{ + insert(this->end(), other.begin(), other.end()); +} + +//-------------------------------------------------------- +// LLDynamicArrayIndexed declaration +//-------------------------------------------------------- + +template +class LLDynamicArrayIndexed +{ +public: + typedef typename std::vector::iterator iterator; + typedef typename std::vector::const_iterator const_iterator; + typedef typename std::vector::reverse_iterator reverse_iterator; + typedef typename std::vector::const_reverse_iterator const_reverse_iterator; + typedef typename std::vector::size_type size_type; +protected: + std::vector mVector; + std::map mIndexMap; + +public: + LLDynamicArrayIndexed() { mVector.reserve(BlockSize); } + + iterator begin() { return mVector.begin(); } + const_iterator begin() const { return mVector.begin(); } + iterator end() { return mVector.end(); } + const_iterator end() const { return mVector.end(); } + + reverse_iterator rbegin() { return mVector.rbegin(); } + const_reverse_iterator rbegin() const { return mVector.rbegin(); } + reverse_iterator rend() { return mVector.rend(); } + const_reverse_iterator rend() const { return mVector.rend(); } + + void reset() { mVector.resize(0); mIndexMap.resize(0); } + bool empty() const { return mVector.empty(); } + size_type size() const { return mVector.empty(); } + + Type& operator[](const Key& k) + { + typename std::map::iterator iter = mIndexMap.find(k); + if (iter == mIndexMap.end()) + { + U32 n = mVector.size(); + mIndexMap[k] = n; + mVector.resize(n+1); + return mVector[n]; + } + else + { + return mVector[iter->second]; + } + } + +}; + +#endif diff --git a/indra/llcommon/lldarrayptr.h b/indra/llcommon/lldarrayptr.h new file mode 100644 index 0000000000..8fe1c5a5df --- /dev/null +++ b/indra/llcommon/lldarrayptr.h @@ -0,0 +1,18 @@ +/** + * @file lldarrayptr.h + * @brief Wrapped std::vector for backward compatibility. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ +#ifndef LL_LLDARRAYPTR_H +#define LL_LLDARRAYPTR_H + +#include "lldarray.h" + +template +class LLDynamicArrayPtr : public LLDynamicArray +{ +}; + +#endif // LL_LLDARRAYPTR_H diff --git a/indra/llcommon/lldate.cpp b/indra/llcommon/lldate.cpp new file mode 100644 index 0000000000..6ae2a1145f --- /dev/null +++ b/indra/llcommon/lldate.cpp @@ -0,0 +1,174 @@ +/** + * @file lldate.cpp + * @author Phoenix + * @date 2006-02-05 + * @brief Implementation of the date class + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "lldate.h" + +#include "apr-1/apr_time.h" + +#include +#include + +static const F64 DATE_EPOCH = 0.0; + +static const F64 LL_APR_USEC_PER_SEC = 1000000.0; + // should be APR_USEC_PER_SEC, but that relies on INT64_C which + // isn't defined in glib under our build set up for some reason + + +LLDate::LLDate() : mSecondsSinceEpoch(DATE_EPOCH) +{ +} + +LLDate::LLDate(const LLDate& date) : + mSecondsSinceEpoch(date.mSecondsSinceEpoch) +{ +} + +LLDate::LLDate(F64 seconds_since_epoch) : + mSecondsSinceEpoch(seconds_since_epoch) +{ +} + +LLDate::LLDate(const std::string& iso8601_date) +{ + if(!fromString(iso8601_date)) + { + mSecondsSinceEpoch = DATE_EPOCH; + } +} + +std::string LLDate::asString() const +{ + std::ostringstream stream; + toStream(stream); + return stream.str(); +} + +void LLDate::toStream(std::ostream& s) const +{ + apr_time_t time = (apr_time_t)(mSecondsSinceEpoch * LL_APR_USEC_PER_SEC); + + apr_time_exp_t exp_time; + if (apr_time_exp_gmt(&exp_time, time) != APR_SUCCESS) + { + s << "1970-01-01T00:00:00Z"; + return; + } + + s << std::dec << std::setfill('0'); +#if( LL_WINDOWS || __GNUC__ > 2) + s << std::right; +#else + s.setf(ios::right); +#endif + s << std::setw(4) << (exp_time.tm_year + 1900) + << '-' << std::setw(2) << (exp_time.tm_mon + 1) + << '-' << std::setw(2) << (exp_time.tm_mday) + << 'T' << std::setw(2) << (exp_time.tm_hour) + << ':' << std::setw(2) << (exp_time.tm_min) + << ':' << std::setw(2) << (exp_time.tm_sec); + if (exp_time.tm_usec > 0) + { + s << '.' << std::setw(2) + << (int)(exp_time.tm_usec / (LL_APR_USEC_PER_SEC / 100)); + } + s << 'Z'; +} + +bool LLDate::fromString(const std::string& iso8601_date) +{ + std::istringstream stream(iso8601_date); + return fromStream(stream); +} + +bool LLDate::fromStream(std::istream& s) +{ + struct apr_time_exp_t exp_time; + apr_int32_t tm_part; + int c; + + s >> tm_part; + exp_time.tm_year = tm_part - 1900; + c = s.get(); // skip the hypen + if (c != '-') { return false; } + s >> tm_part; + exp_time.tm_mon = tm_part - 1; + c = s.get(); // skip the hypen + if (c != '-') { return false; } + s >> tm_part; + exp_time.tm_mday = tm_part; + + c = s.get(); // skip the T + if (c != 'T') { return false; } + + s >> tm_part; + exp_time.tm_hour = tm_part; + c = s.get(); // skip the : + if (c != ':') { return false; } + s >> tm_part; + exp_time.tm_min = tm_part; + c = s.get(); // skip the : + if (c != ':') { return false; } + s >> tm_part; + exp_time.tm_sec = tm_part; + + // zero out the unused fields + exp_time.tm_usec = 0; + exp_time.tm_wday = 0; + exp_time.tm_yday = 0; + exp_time.tm_isdst = 0; + exp_time.tm_gmtoff = 0; + + // generate a time_t from that + apr_time_t time; + if (apr_time_exp_gmt_get(&time, &exp_time) != APR_SUCCESS) + { + return false; + } + + F64 seconds_since_epoch = time / LL_APR_USEC_PER_SEC; + + // check for fractional + c = s.peek(); + if(c == '.') + { + F64 fractional = 0.0; + s >> fractional; + seconds_since_epoch += fractional; + } + s.get(); // skip the Z + if (c != 'Z') { return false; } + + mSecondsSinceEpoch = seconds_since_epoch; + return true; +} + +F64 LLDate::secondsSinceEpoch() const +{ + return mSecondsSinceEpoch; +} + +void LLDate::secondsSinceEpoch(F64 seconds) +{ + mSecondsSinceEpoch = seconds; +} + +std::ostream& operator<<(std::ostream& s, const LLDate& date) +{ + date.toStream(s); + return s; +} + +std::istream& operator>>(std::istream& s, LLDate& date) +{ + date.fromStream(s); + return s; +} diff --git a/indra/llcommon/lldate.h b/indra/llcommon/lldate.h new file mode 100644 index 0000000000..854613ad0b --- /dev/null +++ b/indra/llcommon/lldate.h @@ -0,0 +1,102 @@ +/** + * @file lldate.h + * @author Phoenix + * @date 2006-02-05 + * @brief Declaration of a simple date class. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLDATE_H +#define LL_LLDATE_H + +#include + +#include "stdtypes.h" + +/** + * @class LLDate + * @brief This class represents a particular point in time in UTC. + * + * The date class represents a point in time after epoch - 1970-01-01. + */ +class LLDate +{ +public: + /** + * @brief Construct a date equal to epoch. + */ + LLDate(); + + /** + * @brief Construct a date equal to epoch. + */ + LLDate(const LLDate& date); + + /** + * @brief Construct a date from a seconds since epoch value. + * + * @pararm seconds_since_epoch The number of seconds since UTC epoch. + */ + LLDate(F64 seconds_since_epoch); + + /** + * @brief Construct a date from a string representation + * + * The date is constructed in the fromString() + * method. See that method for details of supported formats. + * If that method fails to parse the date, the date is set to epoch. + * @param iso8601_date An iso-8601 compatible representation of the date. + */ + LLDate(const std::string& iso8601_date); + + /** + * @brief Return the date as in ISO-8601 string. + * + * @return A string representation of the date. + */ + std::string asString() const; + void toStream(std::ostream&) const; + /** + * @brief Set the date from an ISO-8601 string. + * + * The parser only supports strings conforming to + * YYYYF-MM-DDTHH:MM:SS.FFZ where Y is year, M is month, D is day, + * H is hour, M is minute, S is second, F is sub-second, and all + * other characters are literal. + * If this method fails to parse the date, the previous date is + * retained. + * @param iso8601_date An iso-8601 compatible representation of the date. + * @return Returns true if the string was successfully parsed. + */ + bool fromString(const std::string& iso8601_date); + bool fromStream(std::istream&); + + /** + * @brief Return the date in seconds since epoch. + * + * @return The number of seconds since epoch UTC. + */ + F64 secondsSinceEpoch() const; + + /** + * @brief Set the date in seconds since epoch. + * + * @param seconds The number of seconds since epoch UTC. + */ + void secondsSinceEpoch(F64 seconds); + +private: + F64 mSecondsSinceEpoch; +}; + + +// Helper function to stream out a date +std::ostream& operator<<(std::ostream& s, const LLDate& date); + +// Helper function to stream in a date +std::istream& operator>>(std::istream& s, LLDate& date); + + +#endif // LL_LLDATE_H diff --git a/indra/llcommon/lldefs.h b/indra/llcommon/lldefs.h new file mode 100644 index 0000000000..63322effbe --- /dev/null +++ b/indra/llcommon/lldefs.h @@ -0,0 +1,185 @@ +/** + * @file lldefs.h + * @brief Various generic constant definitions. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLDEFS_H +#define LL_LLDEFS_H + +#include "stdtypes.h" + +// Often used array indices +const U32 VX = 0; +const U32 VY = 1; +const U32 VZ = 2; +const U32 VW = 3; +const U32 VS = 3; + +const U32 VRED = 0; +const U32 VGREEN = 1; +const U32 VBLUE = 2; +const U32 VALPHA = 3; + +const U32 INVALID_DIRECTION = 0xFFFFFFFF; +const U32 EAST = 0; +const U32 NORTH = 1; +const U32 WEST = 2; +const U32 SOUTH = 3; + +const U32 NORTHEAST = 4; +const U32 NORTHWEST = 5; +const U32 SOUTHWEST = 6; +const U32 SOUTHEAST = 7; +const U32 MIDDLE = 8; + +const U8 LL_SOUND_FLAG_NONE = 0x00; +const U8 LL_SOUND_FLAG_LOOP = 0x01; +const U8 LL_SOUND_FLAG_SYNC_MASTER = 0x02; +const U8 LL_SOUND_FLAG_SYNC_SLAVE = 0x04; +const U8 LL_SOUND_FLAG_SYNC_PENDING = 0x08; +const U8 LL_SOUND_FLAG_SYNC_MASK = LL_SOUND_FLAG_SYNC_MASTER | LL_SOUND_FLAG_SYNC_SLAVE | LL_SOUND_FLAG_SYNC_PENDING; +const U8 LL_SOUND_FLAG_QUEUE = 0x10; + +const U32 gDirOpposite[8] = {2, 3, 0, 1, 6, 7, 4, 5}; +const U32 gDirAdjacent[8][2] = { + {4, 7}, + {4, 5}, + {5, 6}, + {6, 7}, + {0, 1}, + {1, 2}, + {2, 3}, + {0, 3} + }; + +// Magnitude along the x and y axis +const S32 gDirAxes[8][2] = { + { 1, 0}, // east + { 0, 1}, // north + {-1, 0}, // west + { 0,-1}, // south + { 1, 1}, // ne + {-1, 1}, // nw + {-1,-1}, // sw + { 1,-1}, // se + }; + +const U8 EAST_MASK = 1; +const U8 NORTH_MASK = 2; +const U8 WEST_MASK = 4; +const U8 SOUTH_MASK = 8; +const U8 NORTHEAST_MASK = NORTH_MASK | EAST_MASK; +const U8 NORTHWEST_MASK = NORTH_MASK | WEST_MASK; +const U8 SOUTHWEST_MASK = SOUTH_MASK | WEST_MASK; +const U8 SOUTHEAST_MASK = SOUTH_MASK | EAST_MASK; + +const S32 gDirMasks[8] = { + EAST_MASK, + NORTH_MASK, + WEST_MASK, + SOUTH_MASK, + NORTHEAST_MASK, + NORTHWEST_MASK, + SOUTHWEST_MASK, + SOUTHEAST_MASK + }; + +// Sides of a box... +// . Z __.Y +// /|\ /| 0 = NO_SIDE +// | / 1 = FRONT_SIDE = +x +// +------|-----------+ 2 = BACK_SIDE = -x +// /| |/ / /| 3 = LEFT_SIDE = +y +// / | -5- |/ / | 4 = RIGHT_SIDE = -y +// / | /| -3- / | 5 = TOP_SIDE = +z +// +------------------+ | 6 = BOTTOM_SIDE = -z +// | | | / | | +// | |/| | / | |/| +// | 2 | | *-------|-1--------> X +// |/| | -4- |/| | +// | +----|---------|---+ +// | / / | / +// | / -6- | / +// |/ / |/ +// +------------------+ +const U32 NO_SIDE = 0; +const U32 FRONT_SIDE = 1; +const U32 BACK_SIDE = 2; +const U32 LEFT_SIDE = 3; +const U32 RIGHT_SIDE = 4; +const U32 TOP_SIDE = 5; +const U32 BOTTOM_SIDE = 6; + +const U32 LL_MAX_PATH = 1024; // buffer size of maximum path + filename string length + +// For strings we send in messages +const U32 STD_STRING_BUF_SIZE = 255; // Buffer size +const U32 STD_STRING_STR_LEN = 254; // Length of the string (not including \0) +const U32 MAX_STRING = STD_STRING_BUF_SIZE; // Buffer size + +const U32 MAXADDRSTR = 17; // 123.567.901.345 = 15 chars + \0 + 1 for good luck + +// C++ is our friend. . . use template functions to make life easier! + +// specific inlines for basic types +// +// defined for all: +// llmin(a,b) +// llmax(a,b) +// llclamp(a,minimum,maximum) +// +// defined for F32, F64: +// llclampf(a) // clamps a to [0.0 .. 1.0] +// +// defined for U16, U32, U64, S16, S32, S64, : +// llclampb(a) // clamps a to [0 .. 255] +// + +template +inline LLDATATYPE llmax(const LLDATATYPE& d1, const LLDATATYPE& d2) +{ + return (d1 > d2) ? d1 : d2; +} + +template +inline LLDATATYPE llmax(const LLDATATYPE& d1, const LLDATATYPE& d2, const LLDATATYPE& d3) +{ + LLDATATYPE r = llmax(d1,d2); + return (r > d3 ? r : d3); +} + +template +inline LLDATATYPE llmin(const LLDATATYPE& d1, const LLDATATYPE& d2) +{ + return (d1 < d2) ? d1 : d2; +} + +template +inline LLDATATYPE llmin(const LLDATATYPE& d1, const LLDATATYPE& d2, const LLDATATYPE& d3) +{ + LLDATATYPE r = llmin(d1,d2); + return (r < d3 ? r : d3); +} + +template +inline LLDATATYPE llclamp(const LLDATATYPE& a, const LLDATATYPE& minval, const LLDATATYPE& maxval) +{ + return llmin(llmax(a, minval), maxval); +} + +template +inline LLDATATYPE llclampf(const LLDATATYPE& a) +{ + return llmin(llmax(a, (LLDATATYPE)0), (LLDATATYPE)1); +} + +template +inline LLDATATYPE llclampb(const LLDATATYPE& a) +{ + return llmin(llmax(a, (LLDATATYPE)0), (LLDATATYPE)255); +} + +#endif // LL_LLDEFS_H diff --git a/indra/llcommon/lldepthstack.h b/indra/llcommon/lldepthstack.h new file mode 100644 index 0000000000..6b259ec9b0 --- /dev/null +++ b/indra/llcommon/lldepthstack.h @@ -0,0 +1,81 @@ +/** + * @file lldepthstack.h + * @brief Declaration of the LLDepthStack class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLDEPTHSTACK_H +#define LL_LLDEPTHSTACK_H + +#include "linked_lists.h" + +template class LLDepthStack +{ +private: + LLLinkedList mStack; + U32 mCurrentDepth; + U32 mMaxDepth; + +public: + LLDepthStack() : mCurrentDepth(0), mMaxDepth(0) {} + ~LLDepthStack() {} + + void setDepth(U32 depth) + { + mMaxDepth = depth; + } + + U32 getDepth(void) const + { + return mCurrentDepth; + } + + void push(DATA_TYPE *data) + { + if (mCurrentDepth < mMaxDepth) + { + mStack.addData(data); + mCurrentDepth++; + } + else + { + // the last item falls off stack and is deleted + mStack.getLastData(); + mStack.deleteCurrentData(); + mStack.addData(data); + } + } + + DATA_TYPE *pop() + { + DATA_TYPE *tempp = mStack.getFirstData(); + if (tempp) + { + mStack.removeCurrentData(); + mCurrentDepth--; + } + return tempp; + } + + DATA_TYPE *check() + { + DATA_TYPE *tempp = mStack.getFirstData(); + return tempp; + } + + void deleteAllData() + { + mCurrentDepth = 0; + mStack.deleteAllData(); + } + + void removeAllNodes() + { + mCurrentDepth = 0; + mStack.removeAllNodes(); + } +}; + +#endif diff --git a/indra/llcommon/lldlinked.h b/indra/llcommon/lldlinked.h new file mode 100644 index 0000000000..54087848d9 --- /dev/null +++ b/indra/llcommon/lldlinked.h @@ -0,0 +1,77 @@ +/** + * @file lldlinked.h + * @brief Declaration of the LLDLinked class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ +#ifndef LL_LLDLINKED_H +#define LL_LLDLINKED_H + +#include + +template class LLDLinked +{ + LLDLinked* mNextp; + LLDLinked* mPrevp; +public: + + Type* getNext() { return (Type*)mNextp; } + Type* getPrev() { return (Type*)mPrevp; } + Type* getFirst() { return (Type*)mNextp; } + + void init() + { + mNextp = mPrevp = NULL; + } + + void unlink() + { + if (mPrevp) mPrevp->mNextp = mNextp; + if (mNextp) mNextp->mPrevp = mPrevp; + } + + LLDLinked() { mNextp = mPrevp = NULL; } + virtual ~LLDLinked() { unlink(); } + + virtual void deleteAll() + { + Type *curp = getFirst(); + while(curp) + { + Type *nextp = curp->getNext(); + curp->unlink(); + delete curp; + curp = nextp; + } + } + + void relink(Type &after) + { + LLDLinked *afterp = (LLDLinked*)&after; + afterp->mPrevp = this; + mNextp = afterp; + } + + virtual void append(Type& after) + { + LLDLinked *afterp = (LLDLinked*)&after; + afterp->mPrevp = this; + afterp->mNextp = mNextp; + if (mNextp) mNextp->mPrevp = afterp; + mNextp = afterp; + } + + virtual void insert(Type& before) + { + LLDLinked *beforep = (LLDLinked*)&before; + beforep->mNextp = this; + beforep->mPrevp = mPrevp; + if (mPrevp) mPrevp->mNextp = beforep; + mPrevp = beforep; + } + + virtual void put(Type& obj) { append(obj); } +}; + +#endif diff --git a/indra/llcommon/lldqueueptr.h b/indra/llcommon/lldqueueptr.h new file mode 100644 index 0000000000..b5407d826a --- /dev/null +++ b/indra/llcommon/lldqueueptr.h @@ -0,0 +1,334 @@ +/** + * @file lldqueueptr.h + * @brief LLDynamicQueuePtr declaration + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ +#ifndef LL_LLDQUEUEPTR_H +#define LL_LLDQUEUEPTR_H + +template +class LLDynamicQueuePtr +{ +public: + enum + { + OKAY = 0, + FAIL = -1 + }; + + LLDynamicQueuePtr(const S32 size=8); + ~LLDynamicQueuePtr(); + + void init(); + void destroy(); + void reset(); + void realloc(U32 newsize); + + // ACCESSORS + const Type& get(const S32 index) const; // no bounds checking + Type& get(const S32 index); // no bounds checking + const Type& operator [] (const S32 index) const { return get(index); } + Type& operator [] (const S32 index) { return get(index); } + S32 find(const Type &obj) const; + + S32 count() const { return (mLastObj >= mFirstObj ? mLastObj - mFirstObj : mLastObj + mMaxObj - mFirstObj); } + S32 getMax() const { return mMaxObj; } + S32 getFirst() const { return mFirstObj; } + S32 getLast () const { return mLastObj; } + + // MANIPULATE + S32 push(const Type &obj); // add to end of Queue, returns index from start + S32 pull( Type &obj); // pull from Queue, returns index from start + + S32 remove (S32 index); // remove by index + S32 removeObj(const Type &obj); // remove by object + +protected: + S32 mFirstObj, mLastObj, mMaxObj; + Type* mMemory; + +public: + + void print() + { + /* + Convert this to llinfos if it's intended to be used - djs 08/30/02 + + printf("Printing from %d to %d (of %d): ",mFirstObj, mLastObj, mMaxObj); + + if (mFirstObj <= mLastObj) + { + for (S32 i=mFirstObj;i +inline LLDynamicQueuePtr::LLDynamicQueuePtr(const S32 size) +{ + init(); + realloc(size); +} + +template +inline LLDynamicQueuePtr::~LLDynamicQueuePtr() +{ + destroy(); +} + +template +inline void LLDynamicQueuePtr::init() +{ + mFirstObj = 0; + mLastObj = 0; + mMaxObj = 0; + mMemory = NULL; +} + +template +inline void LLDynamicQueuePtr::realloc(U32 newsize) +{ + if (newsize) + { + if (mFirstObj > mLastObj && newsize > mMaxObj) + { + Type* new_memory = new Type[newsize]; + + llassert(new_memory); + + S32 _count = count(); + S32 i, m = 0; + for (i=mFirstObj; i < mMaxObj; i++) + { + new_memory[m++] = mMemory[i]; + } + for (i=0; i <=mLastObj; i++) + { + new_memory[m++] = mMemory[i]; + } + + delete[] mMemory; + mMemory = new_memory; + + mFirstObj = 0; + mLastObj = _count; + } + else + { + Type* new_memory = new Type[newsize]; + + llassert(new_memory); + + S32 i, m = 0; + for (i=0; i < mLastObj; i++) + { + new_memory[m++] = mMemory[i]; + } + delete[] mMemory; + mMemory = new_memory; + } + } + else if (mMemory) + { + delete[] mMemory; + mMemory = NULL; + } + + mMaxObj = newsize; +} + +template +inline void LLDynamicQueuePtr::destroy() +{ + reset(); + delete[] mMemory; + mMemory = NULL; +} + + +template +void LLDynamicQueuePtr::reset() +{ + for (S32 i=0; i < mMaxObj; i++) + { + get(i) = NULL; // unrefs for pointers + } + + mFirstObj = 0; + mLastObj = 0; +} + + +template +inline S32 LLDynamicQueuePtr::find(const Type &obj) const +{ + S32 i; + if (mFirstObj <= mLastObj) + { + for ( i = mFirstObj; i < mLastObj; i++ ) + { + if (mMemory[i] == obj) + { + return i; + } + } + } + else + { + for ( i = mFirstObj; i < mMaxObj; i++ ) + { + if (mMemory[i] == obj) + { + return i; + } + } + for ( i = 0; i < mLastObj; i++ ) + { + if (mMemory[i] == obj) + { + return i; + } + } + } + + return FAIL; +} + +template +inline S32 LLDynamicQueuePtr::remove(S32 i) +{ + if (mFirstObj > mLastObj) + { + if (i >= mFirstObj && i < mMaxObj) + { + while( i > mFirstObj) + { + mMemory[i] = mMemory[i-1]; + i--; + } + mMemory[mFirstObj] = NULL; + mFirstObj++; + if (mFirstObj >= mMaxObj) mFirstObj = 0; + + return count(); + } + else if (i < mLastObj && i >= 0) + { + while(i < mLastObj) + { + mMemory[i] = mMemory[i+1]; + i++; + } + mMemory[mLastObj] = NULL; + mLastObj--; + if (mLastObj < 0) mLastObj = mMaxObj-1; + + return count(); + } + } + else if (i <= mLastObj && i >= mFirstObj) + { + while(i < mLastObj) + { + mMemory[i] = mMemory[i+1]; + i++; + } + mMemory[mLastObj] = NULL; + mLastObj--; + if (mLastObj < 0) mLastObj = mMaxObj-1; + + return count(); + } + + + return FAIL; +} + +template +inline S32 LLDynamicQueuePtr::removeObj(const Type& obj) +{ + S32 ind = find(obj); + if (ind >= 0) + { + return remove(ind); + } + return FAIL; +} + +template +inline S32 LLDynamicQueuePtr::push(const Type &obj) +{ + if (mMaxObj - count() <= 1) + { + realloc(mMaxObj * 2); + } + + mMemory[mLastObj++] = obj; + + if (mLastObj >= mMaxObj) + { + mLastObj = 0; + } + + return count(); +} + +template +inline S32 LLDynamicQueuePtr::pull(Type &obj) +{ + obj = NULL; + + if (count() < 1) return -1; + + obj = mMemory[mFirstObj]; + mMemory[mFirstObj] = NULL; + + mFirstObj++; + + if (mFirstObj >= mMaxObj) + { + mFirstObj = 0; + } + + return count(); +} + +template +inline const Type& LLDynamicQueuePtr::get(const S32 i) const +{ + return mMemory[i]; +} + +template +inline Type& LLDynamicQueuePtr::get(const S32 i) +{ + return mMemory[i]; +} + + +#endif // LL_LLDQUEUEPTR_H diff --git a/indra/llcommon/llendianswizzle.h b/indra/llcommon/llendianswizzle.h new file mode 100644 index 0000000000..1794620b46 --- /dev/null +++ b/indra/llcommon/llendianswizzle.h @@ -0,0 +1,76 @@ +/** + * @file llendianswizzle.h + * @brief Functions for in-place bit swizzling + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLENDIANSWIZZLE_H +#define LL_LLENDIANSWIZZLE_H + +/* This function is intended to be used for in-place swizzling, particularly after fread() of + binary values from a file. Such as: + + numRead = fread(scale.mV, sizeof(float), 3, fp); + llendianswizzle(scale.mV, sizeof(float), 3); + + It assumes that the values in the file are LITTLE endian, so it's a no-op on a little endian machine. + + It keys off of typesize to do the correct swizzle, so make sure that typesize is the size of the native type. + + 64-bit types are not yet handled. +*/ + +#ifdef LL_LITTLE_ENDIAN + // little endian is native for most things. + inline void llendianswizzle(void *,int,int) + { + // Nothing to do + } +#endif + +#ifdef LL_BIG_ENDIAN + // big endian requires a bit of work. + inline void llendianswizzle(void *p,int typesize, int count) + { + int i; + switch(typesize) + { + case 2: + { + U16 temp; + for(i=count ;i!=0 ;i--) + { + temp = ((U16*)p)[0]; + ((U16*)p)[0] = ((temp >> 8) & 0x000000FF) | ((temp << 8) & 0x0000FF00); + p = (void*)(((U16*)p) + 1); + } + } + break; + + case 4: + { + U32 temp; + for(i=count; i!=0; i--) + { + temp = ((U32*)p)[0]; + ((U32*)p)[0] = + ((temp >> 24) & 0x000000FF) | + ((temp >> 8) & 0x0000FF00) | + ((temp << 8) & 0x00FF0000) | + ((temp << 24) & 0xFF000000); + p = (void*)(((U32*)p) + 1); + } + } + break; + } + + } +#endif + +// Use this when working with a single integral value you want swizzled + +#define llendianswizzleone(x) llendianswizzle(&(x), sizeof(x), 1) + +#endif // LL_LLENDIANSWIZZLE_H diff --git a/indra/llcommon/llenum.h b/indra/llcommon/llenum.h new file mode 100644 index 0000000000..7443fe9d0f --- /dev/null +++ b/indra/llcommon/llenum.h @@ -0,0 +1,60 @@ +/** + * @file llenum.h + * @author Tom Yedwab + * @brief Utility class for storing enum value <-> string lookup. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLENUM_H +#define LL_LLENUM_H + +class LLEnum +{ +public: + typedef std::pair enum_t; + enum + { + UNDEFINED = 0xffffffff, + }; + + LLEnum(const enum_t values_array[], const U32 length) + { + for (U32 i=0; i= mEnumArray.size()) + { + mEnumArray.resize(values_array[i].second+1); + } + mEnumArray[values_array[i].second] = values_array[i].first; + } + } + + const U32 operator[](std::string str) + { + std::map::iterator itor; + itor = mEnumMap.find(str); + if (itor != mEnumMap.end()) + { + return itor->second; + } + return UNDEFINED; + } + + const std::string operator[](U32 index) + { + if (index < mEnumArray.size()) + { + return mEnumArray[index]; + } + return ""; + } + +private: + std::map mEnumMap; + std::vector mEnumArray; +}; + +#endif // LL_LLENUM_H diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp new file mode 100644 index 0000000000..9f643fd9eb --- /dev/null +++ b/indra/llcommon/llerror.cpp @@ -0,0 +1,40 @@ +/** + * @file llerror.cpp + * @brief Function to crash. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ +#include "linden_common.h" + +#include "llerror.h" + +LLErrorBuffer gErrorBuffer; +LLErrorStream gErrorStream(&gErrorBuffer); + + +void _llcrash_and_loop() +{ + // Now, we go kaboom! + U32* crash = NULL; + + *crash = 0; + + while(TRUE) + { + + // Loop forever, in case the crash didn't work? + } +} + +LLScopedErrorLevel::LLScopedErrorLevel(LLErrorBuffer::ELevel error_level) +{ + mOrigErrorLevel = gErrorStream.getErrorLevel(); + gErrorStream.setErrorLevel(error_level); +} + + +LLScopedErrorLevel::~LLScopedErrorLevel() +{ + gErrorStream.setErrorLevel(mOrigErrorLevel); +} diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h new file mode 100644 index 0000000000..796ec4a421 --- /dev/null +++ b/indra/llcommon/llerror.h @@ -0,0 +1,218 @@ +/** + * @file llerror.h + * @brief Constants, functions, and macros for logging and runtime errors. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLERROR_H +#define LL_LLERROR_H + +#include +#include +#include + +#include "llerrorstream.h" +#include "llerrorbuffer.h" + +// Specific error codes +const S32 LL_ERR_NOERR = 0; +const S32 LL_ERR_ASSET_REQUEST_FAILED = -1; +//const S32 LL_ERR_ASSET_REQUEST_INVALID = -2; +const S32 LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE = -3; +const S32 LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE = -4; +const S32 LL_ERR_INSUFFICIENT_PERMISSIONS = -5; +const S32 LL_ERR_EOF = -39; +const S32 LL_ERR_CANNOT_OPEN_FILE = -42; +const S32 LL_ERR_FILE_NOT_FOUND = -43; +const S32 LL_ERR_FILE_EMPTY = -44; +const S32 LL_ERR_TCP_TIMEOUT = -23016; +const S32 LL_ERR_CIRCUIT_GONE = -23017; + +// Error types + +#define LLERR_IMAGE (1 << 1) // Image requests +#define LLERR_MESSAGE (1 << 2) // Messaging +#define LLERR_PERF (1 << 3) // Performance +#define LLERR_SQL (1 << 4) // SQL statements +#define LLERR_DOUG (1 << 5) // Doug's debugging +#define LLERR_USER_INPUT (1 << 6) // Keyboard and mouse +#define LLERR_TIMING (1 << 7) // Verbose time info +#define LLERR_TASK (1 << 8) // Tracking tasks +#define LLERR_MSG_HANDLER (1 << 9) // +#define LLERR_CIRCUIT_INFO (1 << 10) // Message system circuit info +#define LLERR_PHYSICS (1 << 11) // physics +#define LLERR_VFS (1 << 12) // VFS +const U32 LLERR_ALL = 0xffff; +const U32 LLERR_NONE = 0x0; + +// Define one of these for different error levels in release... +// #define RELEASE_SHOW_DEBUG // Define this if you want your release builds to show lldebug output. +#define RELEASE_SHOW_INFO // Define this if you want your release builds to show llinfo output +#define RELEASE_SHOW_WARN // Define this if you want your release builds to show llwarn output. + + +////////////////////////////////////////// +// +// Implementation - ignore +// +// +#ifdef _DEBUG +#define SHOW_DEBUG +#define SHOW_WARN +#define SHOW_INFO +#define SHOW_ASSERT +#else // _DEBUG + +#ifdef RELEASE_SHOW_DEBUG +#define SHOW_DEBUG +#endif + +#ifdef RELEASE_SHOW_WARN +#define SHOW_WARN +#endif + +#ifdef RELEASE_SHOW_INFO +#define SHOW_INFO +#endif + +#ifdef RELEASE_SHOW_ASSERT +#define SHOW_ASSERT +#endif + +#endif // _DEBUG + + +extern LLErrorStream gErrorStream; + + +// LL Error macros +// +// Usage: +// +// llerrs << "An error, oh my!" << variable << endl; +// llwarns << "Another error, fuck me!" << variable << endl; +// llwarnst(LLERR_IMAGE) << "Debug, mother fucker" << endl; +// +// NOTE: The output format of filename(lineno): is so that MS DevStudio +// can parse the output and automatically jump to that location + +inline std::string llerrno_string(int errnum) +{ + std::stringstream res; + res << "error(" << errnum << "):" << strerror(errnum) << " "; + return res.str(); +} + +inline std::string llerror_file_line(const char* file, S32 line) +{ + std::stringstream res; + res << file << "(" <first; + LLChildInfo &child_info = iter->second; + // check the status of *all* children, in case we missed a signal + if (0 != waitpid(child_pid, &status, WNOHANG)) + { + bool exited = false; + int exit_status = -1; + get_child_status(status, exit_status, exited, LLApp::sLogInSignal); + + if (child_info.mCallback) + { + if (LLApp::sLogInSignal) + { + llinfos << "Signal handler - Running child callback" << llendl; + } + child_info.mCallback(child_pid, exited, status); + } + LLApp::sChildMap.erase(iter++); + } + else + { + // Child didn't terminate, yet we got a sigchild somewhere... + if (child_info.mGotSigChild && child_info.mCallback) + { + child_info.mCallback(child_pid, false, 0); + } + child_info.mGotSigChild = FALSE; + iter++; + } + } + + // check the status of *all* children, in case we missed a signal + // Same as above, but use the default child callback + while(0 < (child_pid = waitpid( -1, &status, WNOHANG ))) + { + if (0 != waitpid(child_pid, &status, WNOHANG)) + { + bool exited = false; + int exit_status = -1; + get_child_status(status, exit_status, exited, LLApp::sLogInSignal); + if (LLApp::sDefaultChildCallback) + { + if (LLApp::sLogInSignal) + { + llinfos << "Signal handler - Running default child callback" << llendl; + } + LLApp::sDefaultChildCallback(child_pid, true, status); + } + } + } + } + + +#endif + ms_sleep(10); + counter++; + } + if (LLApp::isError()) + { + // The app is in an error state, run the application's error handler. + //llinfos << "thread_error - An error has occurred, running error callback!" << llendl; + // Run the error handling callback + LLApp::runErrorHandler(); + } + else + { + // Everything is okay, a clean exit. + //llinfos << "thread_error - Application exited cleanly" << llendl; + } + + //llinfos << "thread_error - Exiting" << llendl; + LLApp::sErrorThreadRunning = FALSE; +} + diff --git a/indra/llcommon/llerrorthread.h b/indra/llcommon/llerrorthread.h new file mode 100644 index 0000000000..66cbed519e --- /dev/null +++ b/indra/llcommon/llerrorthread.h @@ -0,0 +1,28 @@ +/** + * @file llerrorthread.h + * @brief Specialized thread to handle runtime errors. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLERRORTHREAD_H +#define LL_LLERRORTHREAD_H + +#include "llthread.h" + +class LLErrorThread : public LLThread +{ +public: + LLErrorThread(); + ~LLErrorThread(); + + /*virtual*/ void run(void); + void setUserData(void *user_data); + void *getUserData() const; + +protected: + void* mUserDatap; // User data associated with this thread +}; + +#endif // LL_LLERRORTHREAD_H diff --git a/indra/llcommon/llevent.cpp b/indra/llcommon/llevent.cpp new file mode 100644 index 0000000000..6e6fce6ec3 --- /dev/null +++ b/indra/llcommon/llevent.cpp @@ -0,0 +1,285 @@ +/** + * @file llevent.cpp + * @brief LLEvent and LLEventListener base classes. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llevent.h" + +/************************************************ + Events +************************************************/ + +// virtual +LLEvent::~LLEvent() +{ +} + +// virtual +bool LLEvent::accept(LLEventListener* listener) +{ + return true; +} + +// virtual +const std::string& LLEvent::desc() +{ + return mDesc; +} + +/************************************************ + Observables +************************************************/ + +LLObservable::LLObservable() + : mDispatcher(new LLEventDispatcher()) +{ +} + +// virtual +LLObservable::~LLObservable() +{ + if (mDispatcher.notNull()) + { + mDispatcher->disengage(this); + mDispatcher = NULL; + } +} + +// virtual +bool LLObservable::setDispatcher(LLPointer dispatcher) +{ + if (mDispatcher.notNull()) + { + mDispatcher->disengage(this); + mDispatcher = NULL; + } + if (dispatcher.notNull() || dispatcher->engage(this)) + { + mDispatcher = dispatcher; + return true; + } + return false; +} + +// Returns the current dispatcher pointer. +// virtual +LLEventDispatcher* LLObservable::getDispatcher() +{ + return mDispatcher; +} + +// Notifies the dispatcher of an event being fired. +void LLObservable::fireEvent(LLPointer event, LLSD filter) +{ + if (mDispatcher.notNull()) + { + mDispatcher->fireEvent(event, filter); + } +} + +/************************************************ + Dispatchers +************************************************/ + +class LLEventDispatcher::Impl +{ +public: + virtual ~Impl() { } + virtual bool engage(LLObservable* observable) { return true; } + virtual void disengage(LLObservable* observable) { } + + virtual void addListener(LLEventListener *listener, LLSD filter, const LLSD& userdata) = 0; + virtual void removeListener(LLEventListener *listener) = 0; + virtual std::vector getListeners() const = 0; + virtual bool fireEvent(LLPointer event, LLSD filter) = 0; +}; + +bool LLEventDispatcher::engage(LLObservable* observable) +{ + return impl->engage(observable); +} + +void LLEventDispatcher::disengage(LLObservable* observable) +{ + impl->disengage(observable); +} + +void LLEventDispatcher::addListener(LLEventListener *listener, LLSD filter, const LLSD& userdata) +{ + impl->addListener(listener, filter, userdata); +} + +void LLEventDispatcher::removeListener(LLEventListener *listener) +{ + impl->removeListener(listener); +} + +std::vector LLEventDispatcher::getListeners() const +{ + return impl->getListeners(); +} + + +bool LLEventDispatcher::fireEvent(LLPointer event, LLSD filter) +{ + return impl->fireEvent(event, filter); +} + +class LLSimpleDispatcher : public LLEventDispatcher::Impl +{ +public: + LLSimpleDispatcher(LLEventDispatcher *parent) : mParent(parent) { } + virtual ~LLSimpleDispatcher(); + virtual void addListener(LLEventListener* listener, LLSD filter, const LLSD& userdata); + virtual void removeListener(LLEventListener* listener); + virtual std::vector getListeners() const; + virtual bool fireEvent(LLPointer event, LLSD filter); + +protected: + std::vector mListeners; + LLEventDispatcher *mParent; +}; + +LLSimpleDispatcher::~LLSimpleDispatcher() +{ + while (mListeners.size() > 0) + { + removeListener(mListeners.begin()->listener); + } +} + +void LLSimpleDispatcher::addListener(LLEventListener* listener, LLSD filter, const LLSD& userdata) +{ + if (listener == NULL) return; + removeListener(listener); + LLListenerEntry new_entry; + new_entry.listener = listener; + new_entry.filter = filter; + new_entry.userdata = userdata; + mListeners.push_back(new_entry); + listener->handleAttach(mParent); +} + +void LLSimpleDispatcher::removeListener(LLEventListener* listener) +{ + std::vector::iterator itor; + for (itor=mListeners.begin(); itor!=mListeners.end();) + { + if ((*itor).listener == listener) + { + mListeners.erase(itor); + } + else + { + ++itor; + } + } + listener->handleDetach(mParent); +} + +std::vector LLSimpleDispatcher::getListeners() const +{ + std::vector ret; + std::vector::const_iterator itor; + for (itor=mListeners.begin(); itor!=mListeners.end(); ++itor) + { + ret.push_back(*itor); + } + + return ret; +} + +// virtual +bool LLSimpleDispatcher::fireEvent(LLPointer event, LLSD filter) +{ + std::vector::iterator itor; + LLString filter_string = filter.asString(); + for (itor=mListeners.begin(); itor!=mListeners.end(); ++itor) + { + LLListenerEntry& entry = *itor; + if (filter_string == "" || entry.filter.asString() == filter_string) + { + (entry.listener)->handleEvent(event, (*itor).userdata); + } + } + return true; +} + +LLEventDispatcher::LLEventDispatcher() +{ + impl = new LLSimpleDispatcher(this); +} + +LLEventDispatcher::~LLEventDispatcher() +{ + if (impl) + { + delete impl; + } +} + +/************************************************ + Listeners +************************************************/ + +LLEventListener::~LLEventListener() +{ +} + +LLSimpleListener::~LLSimpleListener() +{ + clearDispatchers(); +} + +void LLSimpleListener::clearDispatchers() +{ + // Remove myself from all listening dispatchers + std::vector::iterator itor; + while (mDispatchers.size() > 0) + { + itor = mDispatchers.begin(); + LLEventDispatcher *dispatcher = *itor; + dispatcher->removeListener(this); + itor = mDispatchers.begin(); + if (itor != mDispatchers.end() && (*itor) == dispatcher) + { + // Somehow, the dispatcher was not removed. Remove it forcibly + mDispatchers.erase(itor); + } + } +} + +bool LLSimpleListener::handleAttach(LLEventDispatcher *dispatcher) +{ + // Add dispatcher if it doesn't already exist + std::vector::iterator itor; + for (itor = mDispatchers.begin(); itor != mDispatchers.end(); ++itor) + { + if ((*itor) == dispatcher) return true; + } + mDispatchers.push_back(dispatcher); + return true; +} + +bool LLSimpleListener::handleDetach(LLEventDispatcher *dispatcher) +{ + // Remove dispatcher from list + std::vector::iterator itor; + for (itor = mDispatchers.begin(); itor != mDispatchers.end(); ) + { + if ((*itor) == dispatcher) + { + itor = mDispatchers.erase(itor); + } + else + { + ++itor; + } + } + return true; +} diff --git a/indra/llcommon/llevent.h b/indra/llcommon/llevent.h new file mode 100644 index 0000000000..4a619ba16f --- /dev/null +++ b/indra/llcommon/llevent.h @@ -0,0 +1,178 @@ +/** + * @file llevent.h + * @author Tom Yedwab + * @brief LLEvent and LLEventListener base classes. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_EVENT_H +#define LL_EVENT_H + +#include "llsd.h" +#include "llmemory.h" + +class LLEventListener; +class LLEvent; +class LLEventDispatcher; +class LLObservable; + +// Abstract event. All events derive from LLEvent +class LLEvent : public LLThreadSafeRefCount +{ +protected: + virtual ~LLEvent(); + +public: + LLEvent(LLObservable* source, const std::string& desc = "") : mSource(source), mDesc(desc) { } + + LLObservable* getSource() { return mSource; } + virtual LLSD getValue() { return LLSD(); } + // Determines whether this particular listener + // should be notified of this event. + // If this function returns true, handleEvent is + // called on the listener with this event as the + // argument. + // Defaults to handling all events. Override this + // if associated with an Observable with many different listeners + virtual bool accept(LLEventListener* listener); + + // return a string describing the event + virtual const std::string& desc(); + +private: + LLObservable* mSource; + std::string mDesc; +}; + +// Abstract listener. All listeners derive from LLEventListener +class LLEventListener : public LLThreadSafeRefCount +{ +protected: + virtual ~LLEventListener(); + +public: + + // Processes the event. + // TODO: Make the return value less ambiguous? + virtual bool handleEvent(LLPointer event, const LLSD& userdata) = 0; + + // Called when an dispatcher starts/stops listening + virtual bool handleAttach(LLEventDispatcher *dispatcher) = 0; + virtual bool handleDetach(LLEventDispatcher *dispatcher) = 0; +}; + +// A listener which tracks references to it and cleans up when it's deallocated +class LLSimpleListener : public LLEventListener +{ +public: + virtual ~LLSimpleListener(); + void clearDispatchers(); + virtual bool handleAttach(LLEventDispatcher *dispatcher); + virtual bool handleDetach(LLEventDispatcher *dispatcher); + +protected: + std::vector mDispatchers; +}; + +class LLObservable; // defined below + +// A structure which stores a Listener and its metadata +struct LLListenerEntry +{ + LLEventListener* listener; + LLSD filter; + LLSD userdata; +}; + +// Base class for a dispatcher - an object which listens +// to events being fired and relays them to their +// appropriate destinations. +class LLEventDispatcher : public LLThreadSafeRefCount +{ +protected: + virtual ~LLEventDispatcher(); + +public: + // The default constructor creates a default simple dispatcher implementation. + // The simple implementation has an array of listeners and fires every event to + // all of them. + LLEventDispatcher(); + + // This dispatcher is being attached to an observable object. + // If we return false, the attach fails. + bool engage(LLObservable* observable); + + // This dispatcher is being detached from an observable object. + void disengage(LLObservable* observable); + + // Adds a listener to this dispatcher, with a given user data + // that will be passed to the listener when an event is fired. + void addListener(LLEventListener *listener, LLSD filter, const LLSD& userdata); + + // Removes a listener from this dispatcher + void removeListener(LLEventListener *listener); + + // Gets a list of interested listeners + std::vector getListeners() const; + + // Handle an event that has just been fired by communicating it + // to listeners, passing it across a network, etc. + bool fireEvent(LLPointer event, LLSD filter); + +public: + class Impl; +private: + Impl* impl; +}; + +// Interface for observable data (data that fires events) +// In order for this class to work properly, it needs +// an instance of an LLEventDispatcher to route events to their +// listeners. +class LLObservable +{ +public: + // Initialize with the default Dispatcher + LLObservable(); + virtual ~LLObservable(); + + // Replaces the existing dispatcher pointer to the new one, + // informing the dispatcher of the change. + virtual bool setDispatcher(LLPointer dispatcher); + + // Returns the current dispatcher pointer. + virtual LLEventDispatcher* getDispatcher(); + + void addListener(LLEventListener *listener, LLSD filter = "", const LLSD& userdata = "") + { + if (mDispatcher.notNull()) mDispatcher->addListener(listener, filter, userdata); + } + void removeListener(LLEventListener *listener) + { + if (mDispatcher.notNull()) mDispatcher->removeListener(listener); + } + // Notifies the dispatcher of an event being fired. + void fireEvent(LLPointer event, LLSD filter); + +protected: + LLPointer mDispatcher; +}; + +// Utility mixer class which fires & handles events +class LLSimpleListenerObservable : public LLObservable, public LLSimpleListener +{ +public: + virtual bool handleEvent(LLPointer event, const LLSD& userdata) = 0; +}; + +class LLValueChangedEvent : public LLEvent +{ +public: + LLValueChangedEvent(LLObservable* source, LLSD value) : LLEvent(source, "value_changed"), mValue(value) { } + LLSD getValue() { return mValue; } + LLSD mValue; +}; + +#endif // LL_EVENT_H diff --git a/indra/llcommon/lleventemitter.h b/indra/llcommon/lleventemitter.h new file mode 100644 index 0000000000..649f32df1e --- /dev/null +++ b/indra/llcommon/lleventemitter.h @@ -0,0 +1,84 @@ +/** + * @file lleventemitter.h + * @brief General event emitter class + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// header guard +#ifndef LL_EVENTEMITTER_H +#define LL_EVENTEMITTER_H + +// standard headers +#include +#include +#include +#include +#include + +#include "stdtypes.h" + +/////////////////////////////////////////////////////////////////////////////// +// templatized emitter class +template < class T > +class eventEmitter +{ + public: + typedef typename T::EventType EventType; + typedef std::list< T* > ObserverContainer; + typedef void ( T::*observerMethod )( const EventType& ); + + protected: + ObserverContainer observers; + + public: + eventEmitter () { }; + + ~eventEmitter () { }; + + /////////////////////////////////////////////////////////////////////////////// + // + BOOL addObserver ( T* observerIn ) + { + if ( ! observerIn ) + return FALSE; + + // check if observer already exists + if ( std::find ( observers.begin (), observers.end (), observerIn ) != observers.end () ) + return FALSE; + + // save it + observers.push_back ( observerIn ); + + return true; + }; + + /////////////////////////////////////////////////////////////////////////////// + // + BOOL remObserver ( T* observerIn ) + { + if ( ! observerIn ) + return FALSE; + + observers.remove ( observerIn ); + + return TRUE; + }; + + /////////////////////////////////////////////////////////////////////////////// + // + void update ( observerMethod method, const EventType& msgIn ) + { + typename std::list< T* >::iterator iter = observers.begin (); + + while ( iter != observers.end () ) + { + ( ( *iter )->*method ) ( msgIn ); + + ++iter; + }; + }; +}; + +#endif // lleventemitter_h diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h new file mode 100644 index 0000000000..b5d7f8adc8 --- /dev/null +++ b/indra/llcommon/llfasttimer.h @@ -0,0 +1,187 @@ +/** + * @file llfasttimer.h + * @brief Declaration of a fast timer. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLFASTTIMER_H +#define LL_LLFASTTIMER_H + +#include "lltimer.h" + +#define FAST_TIMER_ON 1 + +U64 get_cpu_clock_count(); + +class LLFastTimer +{ +public: + enum EFastTimerType + { + // high level + FTM_FRAME, + FTM_UPDATE, + FTM_RENDER, + FTM_SWAP, + FTM_IDLE, + FTM_SLEEP, + + // common simulate components + FTM_UPDATE_ANIMATION, + FTM_UPDATE_TERRAIN, + FTM_UPDATE_PRIMITIVES, + FTM_UPDATE_PARTICLES, + FTM_UPDATE_SKY, + FTM_UPDATE_TEXTURES, + + // common render components + FTM_RENDER_GEOMETRY, + FTM_RENDER_TERRAIN, + FTM_RENDER_SIMPLE, + FTM_RENDER_SHINY, + FTM_RENDER_BUMP, + FTM_RENDER_TREES, + FTM_RENDER_CHARACTERS, + FTM_RENDER_OCCLUSION, + FTM_RENDER_ALPHA, + FTM_RENDER_CLOUDS, + FTM_RENDER_HUD, + FTM_RENDER_PARTICLES, + FTM_RENDER_WATER, + FTM_RENDER_TIMER, + FTM_RENDER_UI, + FTM_RENDER_FONTS, + + // newview specific + FTM_MESSAGES, + FTM_REBUILD, + FTM_STATESORT, + FTM_POOLS, + FTM_POOLRENDER, + FTM_IDLE_CB, + FTM_WORLD_UPDATE, + FTM_UPDATE_MOVE, + FTM_OCTREE_BALANCE, + FTM_UPDATE_LIGHTS, + FTM_CULL, + FTM_CULL_REBOUND, + FTM_GEO_UPDATE, + FTM_GEO_RESERVE, + FTM_GEO_LIGHT, + FTM_GEO_SHADOW, + FTM_GEN_VOLUME, + FTM_GEN_TRIANGLES, + FTM_GEN_FLEX, + FTM_AUDIO_UPDATE, + FTM_RESET_DRAWORDER, + FTM_OBJECTLIST_UPDATE, + FTM_AVATAR_UPDATE, + FTM_JOINT_UPDATE, + FTM_ATTACHMENT_UPDATE, + FTM_LOD_UPDATE, + FTM_REGION_UPDATE, + FTM_CLEANUP, + FTM_NETWORK, + FTM_IDLE_NETWORK, + FTM_CREATE_OBJECT, + FTM_LOAD_AVATAR, + FTM_PROCESS_MESSAGES, + FTM_PROCESS_OBJECTS, + FTM_PROCESS_IMAGES, + FTM_IMAGE_UPDATE, + FTM_IMAGE_CREATE, + FTM_IMAGE_DECODE, + FTM_PIPELINE, + FTM_VFILE_WAIT, + FTM_FLEXIBLE_UPDATE, + FTM_OCCLUSION, + FTM_OCCLUSION_READBACK, + FTM_HUD_EFFECTS, + FTM_HUD_UPDATE, + FTM_INVENTORY, + FTM_AUTO_SELECT, + FTM_ARRANGE, + FTM_FILTER, + FTM_REFRESH, + FTM_SORT, + + // Temp + FTM_TEMP1, + FTM_TEMP2, + FTM_TEMP3, + FTM_TEMP4, + FTM_TEMP5, + FTM_TEMP6, + FTM_TEMP7, + FTM_TEMP8, + + FTM_OTHER, // Special, used by display code + + FTM_NUM_TYPES + }; + enum { FTM_HISTORY_NUM = 60 }; + enum { FTM_MAX_DEPTH = 64 }; + +public: + LLFastTimer(EFastTimerType type) + { +#if FAST_TIMER_ON + mType = type; + + // These don't get counted, because they use CPU clockticks + //gTimerBins[gCurTimerBin]++; + //LLTimer::sNumTimerCalls++; + + U64 cpu_clocks = get_cpu_clock_count(); + + sStart[sCurDepth] = cpu_clocks; + sCurDepth++; +#endif + }; + ~LLFastTimer() + { +#if FAST_TIMER_ON + U64 end,delta; + int i; + + // These don't get counted, because they use CPU clockticks + //gTimerBins[gCurTimerBin]++; + //LLTimer::sNumTimerCalls++; + end = get_cpu_clock_count(); + + sCurDepth--; + delta = end - sStart[sCurDepth]; + sCounter[mType] += delta; + sCalls[mType]++; + // Subtract delta from parents + for (i=0; iclose() == 0) + { + _Myios::setstate(ios_base::failbit); /*Flawfinder: ignore*/ + } +} + +void llifstream::open(const char *_Filename, + ios_base::openmode _Mode, + int _Prot) /* Flawfinder: ignore */ +{ // open a C stream with specified mode + + FILE* filep = LLFile::_Fiopen(_Filename,_Mode | ios_base::in, _Prot); + if(filep == NULL) + { + _Myios::setstate(ios_base::failbit); /*Flawfinder: ignore*/ + return; + } + llassert(_Filebuffer == NULL); + _Filebuffer = new _Myfb(filep); + _Myios::init(_Filebuffer); +} + +bool llifstream::is_open() const +{ // test if C stream has been opened + if(_Filebuffer) + return (_Filebuffer->is_open()); + return false; +} +llifstream::~llifstream() +{ + delete _Filebuffer; +} + +llifstream::llifstream(const char *_Filename, + ios_base::openmode _Mode, + int _Prot) + : std::basic_istream< char , std::char_traits< char > >(NULL,true),_Filebuffer(NULL) + +{ // construct with named file and specified mode + open(_Filename, _Mode | ios_base::in, _Prot); /* Flawfinder: ignore */ +} + + +/************** output file stream ********************************/ + +bool llofstream::is_open() const +{ // test if C stream has been opened + if(_Filebuffer) + return (_Filebuffer->is_open()); + return false; +} + +void llofstream::open(const char *_Filename, + ios_base::openmode _Mode, + int _Prot) /* Flawfinder: ignore */ +{ // open a C stream with specified mode + + FILE* filep = LLFile::_Fiopen(_Filename,_Mode | ios_base::out, _Prot); + if(filep == NULL) + { + _Myios::setstate(ios_base::failbit); /*Flawfinder: ignore*/ + return; + } + llassert(_Filebuffer==NULL); + _Filebuffer = new _Myfb(filep); + _Myios::init(_Filebuffer); +} + +void llofstream::close() +{ // close the C stream + llassert(_Filebuffer); + if (_Filebuffer->close() == 0) + _Myios::setstate(ios_base::failbit); /*Flawfinder: ignore*/ +} + +llofstream::llofstream(const char *_Filename, + std::ios_base::openmode _Mode, + int _Prot) + : std::basic_ostream >(NULL,true),_Filebuffer(NULL) +{ // construct with named file and specified mode + open(_Filename, _Mode , _Prot); /* Flawfinder: ignore */ +} + +llofstream::~llofstream() +{ // destroy the object + delete _Filebuffer; +} + +#endif // #if USE_LLFILESTREAMS + diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h new file mode 100644 index 0000000000..c7c4d2718a --- /dev/null +++ b/indra/llcommon/llfile.h @@ -0,0 +1,151 @@ +/** + * @file llfile.h + * @author Michael Schlachter + * @date 2006-03-23 + * @brief Declaration of cross-platform POSIX file buffer and c++ + * stream classes. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLFILE_H +#define LL_LLFILE_H + +/** + * This class provides a cross platform interface to the filesystem. + * Attempts to mostly mirror the POSIX style IO functions. + */ + +#include +#include +#include +#include +#include "stdtypes.h" + +typedef FILE LLFILE; + +#ifdef LL_WINDOWS +#define USE_LLFILESTREAMS 1 +#else +#define USE_LLFILESTREAMS 0 +#endif + + +#if LL_WINDOWS +// windows version of stat function and stat data structure are called _stat +typedef struct _stat llstat; +#else +typedef struct stat llstat; +#endif + +class LLFile +{ +public: + // All these functions take UTF8 path/filenames. + static LLFILE* fopen(const char* filename,const char* accessmode); /* Flawfinder: ignore */ + static LLFILE* _fsopen(const char* filename,const char* accessmode,int sharingFlag); + + // perms is a permissions mask like 0777 or 0700. In most cases it will + // be overridden by the user's umask. It is ignored on Windows. + static int mkdir(const char* filename, int perms = 0700); + + static int remove(const char* filename); + static int rename(const char* filename,const char* newname); + static int stat(const char* filename,llstat* file_status); + static LLFILE * _Fiopen(const char *filename, std::ios::openmode mode,int); // protection currently unused +}; + + +#if USE_LLFILESTREAMS + +class llifstream : public std::basic_istream < char , std::char_traits < char > > +{ + // input stream associated with a C stream +public: + typedef std::basic_ifstream > _Myt; + typedef std::basic_filebuf > _Myfb; + typedef std::basic_ios > _Myios; + + llifstream() + : std::basic_istream >(NULL,true),_Filebuffer(NULL) + { // construct unopened + } + + explicit llifstream(const char *_Filename, + ios_base::openmode _Mode = ios_base::in, + int _Prot = (int)ios_base::_Openprot); + + explicit llifstream(_Filet *_File) + : std::basic_istream >(NULL,true), + _Filebuffer(new _Myfb(_File)) + { // construct with specified C stream + } + virtual ~llifstream(); + + _Myfb *rdbuf() const + { // return pointer to file buffer + return _Filebuffer; + } + bool is_open() const; + void open(const char *_Filename, + ios_base::openmode _Mode = ios_base::in, + int _Prot = (int)ios_base::_Openprot); /* Flawfinder: ignore */ + void close(); + +private: + _Myfb* _Filebuffer; // the file buffer +}; + + +class llofstream : public std::basic_ostream< char , std::char_traits < char > > +{ +public: + typedef std::basic_ostream< char , std::char_traits < char > > _Myt; + typedef std::basic_filebuf< char , std::char_traits < char > > _Myfb; + typedef std::basic_ios > _Myios; + + llofstream() + : std::basic_ostream >(NULL,true),_Filebuffer(NULL) + { // construct unopened + } + + explicit llofstream(const char *_Filename, + std::ios_base::openmode _Mode = ios_base::out, + int _Prot = (int)std::ios_base::_Openprot); + + + explicit llofstream(_Filet *_File) + : std::basic_ostream >(NULL,true), + _Filebuffer(new _Myfb(_File))//_File) + { // construct with specified C stream + } + + virtual ~llofstream(); + + _Myfb *rdbuf() const + { // return pointer to file buffer + return _Filebuffer; + } + + bool is_open() const; + + void open(const char *_Filename,ios_base::openmode _Mode = ios_base::out,int _Prot = (int)ios_base::_Openprot); /* Flawfinder: ignore */ + + void close(); + +private: + _Myfb *_Filebuffer; // the file buffer +}; + + + +#else +//Use standard file streams on non windows platforms +#define llifstream std::ifstream +#define llofstream std::ofstream + +#endif + + +#endif // not LL_LLFILE_H diff --git a/indra/llcommon/llfixedbuffer.cpp b/indra/llcommon/llfixedbuffer.cpp new file mode 100644 index 0000000000..ca7bf1ce48 --- /dev/null +++ b/indra/llcommon/llfixedbuffer.cpp @@ -0,0 +1,71 @@ +/** + * @file llfixedbuffer.cpp + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ +#include "linden_common.h" + +#include "llfixedbuffer.h" + +LLFixedBuffer::LLFixedBuffer(const U32 max_lines) +{ + mMaxLines = max_lines; + mTimer.reset(); +} + + +LLFixedBuffer::~LLFixedBuffer() +{ + clear(); +} + + +void LLFixedBuffer::clear() +{ + mLines.clear(); + mAddTimes.clear(); + mLineLengths.clear(); + + mTimer.reset(); +} + + +void LLFixedBuffer::addLine(const LLString& utf8line) +{ + LLWString wstring = utf8str_to_wstring(utf8line); + LLFixedBuffer::addLine(wstring); +} + +void LLFixedBuffer::addLine(const LLWString& line) +{ + if (line.empty()) + { + return; + } + + removeExtraLines(); + + mLines.push_back(line); + mLineLengths.push_back((S32)line.length()); + mAddTimes.push_back(mTimer.getElapsedTimeF32()); +} + + +void LLFixedBuffer::setMaxLines(S32 max_lines) +{ + mMaxLines = max_lines; + + removeExtraLines(); +} + + +void LLFixedBuffer::removeExtraLines() +{ + while ((S32)mLines.size() > llmax(0, (S32)(mMaxLines - 1))) + { + mLines.pop_front(); + mAddTimes.pop_front(); + mLineLengths.pop_front(); + } +} diff --git a/indra/llcommon/llfixedbuffer.h b/indra/llcommon/llfixedbuffer.h new file mode 100644 index 0000000000..2ca21b2ded --- /dev/null +++ b/indra/llcommon/llfixedbuffer.h @@ -0,0 +1,44 @@ +/** + * @file llfixedbuffer.h + * @brief A fixed size buffer of lines. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLFIXEDBUFFER_H +#define LL_LLFIXEDBUFFER_H + +#include "timer.h" +#include +#include +#include "llstring.h" + +// Fixed size buffer for console output and other things. + +class LLFixedBuffer +{ +public: + LLFixedBuffer(const U32 max_lines = 20); + virtual ~LLFixedBuffer(); + + LLTimer mTimer; + U32 mMaxLines; + std::deque mLines; + std::deque mAddTimes; + std::deque mLineLengths; + + void clear(); // Clear the buffer, and reset it. + virtual void addLine(const LLString& utf8line); + virtual void addLine(const LLWString& line); + + // Get lines currently in the buffer, up to max_size chars, max_length lines + char *getLines(U32 max_size = 0, U32 max_length = 0); + void setMaxLines(S32 max_lines); +protected: + virtual void removeExtraLines(); +}; + +const U32 FIXED_BUF_MAX_LINE_LEN = 255; // Not including termnating 0 + +#endif //LL_FIXED_BUFFER_H diff --git a/indra/llcommon/llframetimer.cpp b/indra/llcommon/llframetimer.cpp new file mode 100644 index 0000000000..c66410651f --- /dev/null +++ b/indra/llcommon/llframetimer.cpp @@ -0,0 +1,69 @@ +/** + * @file llframetimer.cpp + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "u64.h" + +#include "llframetimer.h" + +// Static members +//LLTimer LLFrameTimer::sInternalTimer; +U64 LLFrameTimer::sStartTotalTime = totalTime(); +F64 LLFrameTimer::sFrameTime = 0.0; +U64 LLFrameTimer::sTotalTime = 0; +F64 LLFrameTimer::sTotalSeconds = 0.0; +S32 LLFrameTimer::sFrameCount = 0; +U64 LLFrameTimer::sFrameDeltaTime = 0; +const F64 USEC_PER_SECOND = 1000000.0; +const F64 USEC_TO_SEC_F64 = 0.000001; + +// static +void LLFrameTimer::updateFrameTime() +{ + U64 total_time = totalTime(); + sFrameDeltaTime = total_time - sTotalTime; + sTotalTime = total_time; + sTotalSeconds = U64_to_F64(sTotalTime) * USEC_TO_SEC_F64; + sFrameTime = U64_to_F64(sTotalTime - sStartTotalTime) * USEC_TO_SEC_F64; + sFrameCount++; +} + +void LLFrameTimer::setExpiryAt(F64 seconds_since_epoch) +{ + mStartTime = sFrameTime; + mExpiry = seconds_since_epoch - (USEC_TO_SEC_F64 * sStartTotalTime); +} + +F64 LLFrameTimer::expiresAt() const +{ + F64 expires_at = U64_to_F64(sStartTotalTime) * USEC_TO_SEC_F64; + expires_at += mExpiry; + return expires_at; +} + +BOOL LLFrameTimer::checkExpirationAndReset(F32 expiration) +{ + //llinfos << "LLFrameTimer::checkExpirationAndReset()" << llendl; + //llinfos << " mStartTime:" << mStartTime << llendl; + //llinfos << " sFrameTime:" << sFrameTime << llendl; + //llinfos << " mExpiry: " << mExpiry << llendl; + + if(hasExpired()) + { + reset(); + setTimerExpirySec(expiration); + return TRUE; + } + return FALSE; +} + +// static +F32 LLFrameTimer::getFrameDeltaTimeF32() +{ + return (F32)(U64_to_F64(sFrameDeltaTime) * USEC_TO_SEC_F64); +} diff --git a/indra/llcommon/llframetimer.h b/indra/llcommon/llframetimer.h new file mode 100644 index 0000000000..05b01e6a72 --- /dev/null +++ b/indra/llcommon/llframetimer.h @@ -0,0 +1,122 @@ +/** + * @file llframetimer.h + * @brief A lightweight timer that measures seconds and is only + * updated once per frame. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLFRAMETIMER_H +#define LL_LLFRAMETIMER_H + +/** + * *NOTE: Because of limitations on linux which we do not really have + * time to explore, the total time is derived from the frame time + * and is recsynchronized on every frame. + */ + +#include "lltimer.h" +#include "timing.h" + +class LLFrameTimer +{ +public: + LLFrameTimer() : mStartTime( sFrameTime ), mExpiry(0), mStarted(TRUE) {} + + // Return the number of seconds since the start of this + // application instance. + static F64 getElapsedSeconds() + { + // Loses msec precision after ~4.5 hours... + return sFrameTime; + } + + // Return a low precision usec since epoch + static U64 getTotalTime() + { + return sTotalTime ? sTotalTime : totalTime(); + } + + // Return a low precision seconds since epoch + static F64 getTotalSeconds() + { + return sTotalSeconds; + } + + // Call this method once per frame to update the current frame + // time. + static void updateFrameTime(); + + static S32 getFrameCount() { return sFrameCount; } + + static F32 getFrameDeltaTimeF32(); + + // MANIPULATORS + void start() { reset(); mStarted = TRUE; } + void stop() { mStarted = FALSE; } + void reset() { mStartTime = sFrameTime; mExpiry = sFrameTime; } + void setTimerExpirySec(F32 expiration) { mExpiry = expiration + mStartTime; } + void setExpiryAt(F64 seconds_since_epoch); + BOOL checkExpirationAndReset(F32 expiration); + F32 getElapsedTimeAndResetF32() { F32 t = F32(sFrameTime - mStartTime); reset(); return t; } + + void setAge(const F64 age) { mStartTime = sFrameTime - age; } + + // ACCESSORS + BOOL hasExpired() const { return (sFrameTime >= mExpiry); } + F32 getTimeToExpireF32() const { return (F32)(mExpiry - sFrameTime); } + F32 getElapsedTimeF32() const { return (F32)(sFrameTime - mStartTime); } + BOOL getStarted() const { return mStarted; } + + // return the seconds since epoch when this timer will expire. + F64 expiresAt() const; + +protected: + // A single, high resolution timer that drives all LLFrameTimers + // *NOTE: no longer used. + //static LLTimer sInternalTimer; + + // + // Aplication constants + // + + // Start time of opp in usec since epoch + static U64 sStartTotalTime; + + // + // Data updated per frame + // + + // Seconds since application start + static F64 sFrameTime; + + // Time that has elapsed since last call to updateFrameTime() + static U64 sFrameDeltaTime; + + // Total microseconds since epoch. + static U64 sTotalTime; + + // Seconds since epoch. + static F64 sTotalSeconds; + + // Total number of frames elapsed in application + static S32 sFrameCount; + + // + // Member data + // + + // Number of seconds after application start when this timer was + // started. Set equal to sFrameTime when reset. + F64 mStartTime; + + // Timer expires this many seconds after application start time. + F64 mExpiry; + + // Useful bit of state usually associated with timers, but does + // not affect actual functionality + BOOL mStarted; +}; + +#endif // LL_LLFRAMETIMER_H diff --git a/indra/llcommon/llhash.h b/indra/llcommon/llhash.h new file mode 100644 index 0000000000..fbf059bbcd --- /dev/null +++ b/indra/llcommon/llhash.h @@ -0,0 +1,45 @@ +/** + * @file llhash.h + * @brief Wrapper for a hash function. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLHASH_H +#define LL_LLHASH_H + +#include "llpreprocessor.h" // for GCC_VERSION + +#if (LL_WINDOWS) +#include +#include +#elif LL_DARWIN || LL_LINUX +# if GCC_VERSION >= 30400 // gcc 3.4 and up +# include +# elif __GNUC__ >= 3 +# include +# else +# include +# endif +#else +#error Please define your platform. +#endif + +template inline size_t llhash(T value) +{ +#if LL_WINDOWS + return stdext::hash_value(value); +#elif ( (defined _STLPORT_VERSION) || ((LL_LINUX) && (__GNUC__ <= 2)) ) + std::hash H; + return H(value); +#elif LL_DARWIN || LL_LINUX + __gnu_cxx::hash H; + return H(value); +#else +#error Please define your platform. +#endif +} + +#endif + diff --git a/indra/llcommon/llindexedqueue.h b/indra/llcommon/llindexedqueue.h new file mode 100644 index 0000000000..c5ee58a4c6 --- /dev/null +++ b/indra/llcommon/llindexedqueue.h @@ -0,0 +1,137 @@ +/** + * @file llindexedqueue.h + * @brief An indexed FIFO queue, where only one element with each key + * can be in the queue. + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLINDEXEDQUEUE_H +#define LL_LLINDEXEDQUEUE_H + +// An indexed FIFO queue, where only one element with each key can be in the queue. +// This is ONLY used in the interest list, you'll probably want to review this code +// carefully if you want to use it elsewhere - Doug + +template +class LLIndexedQueue +{ +protected: + typedef std::deque type_deque; + type_deque mQueue; + std::set mKeySet; + +public: + LLIndexedQueue() {} + + // move_if_there is an O(n) operation + bool push_back(const Type &value, bool move_if_there = false) + { + if (mKeySet.find(value) != mKeySet.end()) + { + // Already on the queue + if (move_if_there) + { + // Remove the existing entry. + typename type_deque::iterator it; + for (it = mQueue.begin(); it != mQueue.end(); ++it) + { + if (*it == value) + { + break; + } + } + + // This HAS to succeed, otherwise there's a serious bug in the keyset implementation + // (although this isn't thread safe, at all) + + mQueue.erase(it); + } + else + { + // We're not moving it, leave it alone + return false; + } + } + else + { + // Doesn't exist, add it to the key set + mKeySet.insert(value); + } + + mQueue.push_back(value); + + // We succeeded in adding the new element. + return true; + } + + bool push_front(const Type &value, bool move_if_there = false) + { + if (mKeySet.find(value) != mKeySet.end()) + { + // Already on the queue + if (move_if_there) + { + // Remove the existing entry. + typename type_deque::iterator it; + for (it = mQueue.begin(); it != mQueue.end(); ++it) + { + if (*it == value) + { + break; + } + } + + // This HAS to succeed, otherwise there's a serious bug in the keyset implementation + // (although this isn't thread safe, at all) + + mQueue.erase(it); + } + else + { + // We're not moving it, leave it alone + return false; + } + } + else + { + // Doesn't exist, add it to the key set + mKeySet.insert(value); + } + + mQueue.push_front(value); + return true; + } + + void pop() + { + Type value = mQueue.front(); + mKeySet.erase(value); + mQueue.pop_front(); + } + + Type &front() + { + return mQueue.front(); + } + + S32 size() const + { + return mQueue.size(); + } + + bool empty() const + { + return mQueue.empty(); + } + + void clear() + { + // Clear out all elements on the queue + mQueue.clear(); + mKeySet.clear(); + } +}; + +#endif // LL_LLINDEXEDQUEUE_H diff --git a/indra/llcommon/lllinkedqueue.h b/indra/llcommon/lllinkedqueue.h new file mode 100644 index 0000000000..c386ca0e2b --- /dev/null +++ b/indra/llcommon/lllinkedqueue.h @@ -0,0 +1,291 @@ +/** + * @file lllinkedqueue.h + * @brief Declaration of linked queue classes. + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLLINKEDQUEUE_H +#define LL_LLLINKEDQUEUE_H + +#include "llerror.h" + +// node that actually contains the data +template class LLLinkedQueueNode +{ +public: + DATA_TYPE mData; + LLLinkedQueueNode *mNextp; + LLLinkedQueueNode *mPrevp; + + +public: + LLLinkedQueueNode(); + LLLinkedQueueNode(const DATA_TYPE data); + + // destructor does not, by default, destroy associated data + // however, the mDatap must be NULL to ensure that we aren't causing memory leaks + ~LLLinkedQueueNode(); +}; + + + +template class LLLinkedQueue +{ + +public: + LLLinkedQueue(); + + // destructor destroys list and nodes, but not data in nodes + ~LLLinkedQueue(); + + // Puts at end of FIFO + void push(const DATA_TYPE data); + + // Takes off front of FIFO + BOOL pop(DATA_TYPE &data); + BOOL peek(DATA_TYPE &data); + + void reset(); + + S32 getLength() const; + + BOOL isEmpty() const; + + BOOL remove(const DATA_TYPE data); + + BOOL checkData(const DATA_TYPE data) const; + +private: + // add node to end of list + // set mCurrentp to mQueuep + void addNodeAtEnd(LLLinkedQueueNode *nodep); + +private: + LLLinkedQueueNode mHead; // head node + LLLinkedQueueNode mTail; // tail node + S32 mLength; +}; + + +// +// Nodes +// + +template +LLLinkedQueueNode::LLLinkedQueueNode() : + mData(), mNextp(NULL), mPrevp(NULL) +{ } + +template +LLLinkedQueueNode::LLLinkedQueueNode(const DATA_TYPE data) : + mData(data), mNextp(NULL), mPrevp(NULL) +{ } + +template +LLLinkedQueueNode::~LLLinkedQueueNode() +{ } + + +// +// Queue itself +// + +template +LLLinkedQueue::LLLinkedQueue() +: mHead(), + mTail(), + mLength(0) +{ } + + +// destructor destroys list and nodes, but not data in nodes +template +LLLinkedQueue::~LLLinkedQueue() +{ + reset(); +} + + +// put data into a node and stick it at the end of the list +template +void LLLinkedQueue::push(const DATA_TYPE data) +{ + // make the new node + LLLinkedQueueNode *nodep = new LLLinkedQueueNode(data); + + addNodeAtEnd(nodep); +} + + +// search the list starting at mHead.mNextp and remove the link with mDatap == data +// set mCurrentp to mQueuep, or NULL if mQueuep points to node with mDatap == data +// return TRUE if found, FALSE if not found +template +BOOL LLLinkedQueue::remove(const DATA_TYPE data) +{ + BOOL b_found = FALSE; + + LLLinkedQueueNode *currentp = mHead.mNextp; + + while (currentp) + { + if (currentp->mData == data) + { + b_found = TRUE; + + // if there is a next one, fix it + if (currentp->mNextp) + { + currentp->mNextp->mPrevp = currentp->mPrevp; + } + else // we are at end of list + { + mTail.mPrevp = currentp->mPrevp; + } + + // if there is a previous one, fix it + if (currentp->mPrevp) + { + currentp->mPrevp->mNextp = currentp->mNextp; + } + else // we are at beginning of list + { + mHead.mNextp = currentp->mNextp; + } + + // remove the node + delete currentp; + mLength--; + break; + } + currentp = currentp->mNextp; + } + + return b_found; +} + + +// remove all nodes from the list but do not delete associated data +template +void LLLinkedQueue::reset() +{ + LLLinkedQueueNode *currentp; + LLLinkedQueueNode *nextp; + currentp = mHead.mNextp; + + while (currentp) + { + nextp = currentp->mNextp; + delete currentp; + currentp = nextp; + } + + // reset mHead and mCurrentp + mHead.mNextp = NULL; + mTail.mPrevp = NULL; + mLength = 0; +} + +template +S32 LLLinkedQueue::getLength() const +{ + return mLength; +} + +template +BOOL LLLinkedQueue::isEmpty() const +{ + return mLength <= 0; +} + +// check to see if data is in list +// set mCurrentp and mQueuep to the target of search if found, otherwise set mCurrentp to mQueuep +// return TRUE if found, FALSE if not found +template +BOOL LLLinkedQueue::checkData(const DATA_TYPE data) const +{ + LLLinkedQueueNode *currentp = mHead.mNextp; + + while (currentp) + { + if (currentp->mData == data) + { + return TRUE; + } + currentp = currentp->mNextp; + } + return FALSE; +} + +template +BOOL LLLinkedQueue::pop(DATA_TYPE &data) +{ + LLLinkedQueueNode *currentp; + + currentp = mHead.mNextp; + if (!currentp) + { + return FALSE; + } + + mHead.mNextp = currentp->mNextp; + if (currentp->mNextp) + { + currentp->mNextp->mPrevp = currentp->mPrevp; + } + else + { + mTail.mPrevp = currentp->mPrevp; + } + + data = currentp->mData; + delete currentp; + mLength--; + return TRUE; +} + +template +BOOL LLLinkedQueue::peek(DATA_TYPE &data) +{ + LLLinkedQueueNode *currentp; + + currentp = mHead.mNextp; + if (!currentp) + { + return FALSE; + } + data = currentp->mData; + return TRUE; +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// private members +////////////////////////////////////////////////////////////////////////////////////////// + + +// add node to end of list +// set mCurrentp to mQueuep +template +void LLLinkedQueue::addNodeAtEnd(LLLinkedQueueNode *nodep) +{ + // add the node to the end of the list + nodep->mNextp = NULL; + nodep->mPrevp = mTail.mPrevp; + mTail.mPrevp = nodep; + + // if there's something in the list, fix its back pointer + if (nodep->mPrevp) + { + nodep->mPrevp->mNextp = nodep; + } + else // otherwise fix the head node + { + mHead.mNextp = nodep; + } + mLength++; +} + +#endif diff --git a/indra/llcommon/lllivefile.cpp b/indra/llcommon/lllivefile.cpp new file mode 100644 index 0000000000..7dad6f82d8 --- /dev/null +++ b/indra/llcommon/lllivefile.cpp @@ -0,0 +1,74 @@ +/** + * @file lllivefile.cpp + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lllivefile.h" + + +LLLiveFile::LLLiveFile(const std::string &filename, const F32 refresh_period) : +mForceCheck(true), +mRefreshPeriod(refresh_period), +mFilename(filename), +mLastModTime(0), +mLastExists(false) +{ +} + + +LLLiveFile::~LLLiveFile() +{ +} + + +bool LLLiveFile::checkAndReload() +{ + if (!mForceCheck && mRefreshTimer.getElapsedTimeF32() < mRefreshPeriod) + { + // Skip the check if not enough time has elapsed and we're not + // forcing a check of the file + return false; + } + mForceCheck = false; + mRefreshTimer.reset(); + + // Stat the file to see if it exists and when it was last modified. + llstat stat_data; + int res = LLFile::stat(mFilename.c_str(), &stat_data); + + if (res) + { + // Couldn't stat the file, that means it doesn't exist or is + // broken somehow. Clear flags and return. + if (mLastExists) + { + loadFile(); // Load the file, even though it's missing to allow it to clear state. + mLastExists = false; + return true; + } + return false; + } + + // The file exists, decide if we want to load it. + if (mLastExists) + { + // The file existed last time, don't read it if it hasn't changed since + // last time. + if (stat_data.st_mtime <= mLastModTime) + { + return false; + } + } + + // We want to read the file. Update status info for the file. + mLastExists = true; + mLastModTime = stat_data.st_mtime; + + loadFile(); + return true; +} + diff --git a/indra/llcommon/lllivefile.h b/indra/llcommon/lllivefile.h new file mode 100644 index 0000000000..97c88a5c5c --- /dev/null +++ b/indra/llcommon/lllivefile.h @@ -0,0 +1,34 @@ +/** + * @file lllivefile.h + * @brief Automatically reloads a file whenever it changes or is removed. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLLIVEFILE_H +#define LL_LLLIVEFILE_H + +#include "llframetimer.h" + +class LLLiveFile +{ +public: + LLLiveFile(const std::string &filename, const F32 refresh_period = 5.f); + virtual ~LLLiveFile(); + + bool checkAndReload(); // Returns true if the file changed in any way + +protected: + virtual void loadFile() = 0; // Implement this to load your file if it changed + + bool mForceCheck; + F32 mRefreshPeriod; + LLFrameTimer mRefreshTimer; + + std::string mFilename; + time_t mLastModTime; + bool mLastExists; +}; + +#endif //LL_LLLIVEFILE_H diff --git a/indra/llcommon/lllocalidhashmap.h b/indra/llcommon/lllocalidhashmap.h new file mode 100644 index 0000000000..12f2b3f2d7 --- /dev/null +++ b/indra/llcommon/lllocalidhashmap.h @@ -0,0 +1,877 @@ +/** + * @file lllocalidhashmap.h + * @brief Map specialized for dealing with local ids + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLLOCALIDHASHMAP_H +#define LL_LLLOCALIDHASHMAP_H + +#include "stdtypes.h" +#include "llerror.h" + +const S32 MAX_ITERS = 4; +// LocalID hash map + +// +// LLLocalIDHashNode +// + +template +class LLLocalIDHashNode +{ +public: + LLLocalIDHashNode(); + +public: + S32 mCount; + U32 mKey[SIZE]; + DATA mData[SIZE]; + LLLocalIDHashNode *mNextNodep; +}; + + +// +// LLLocalIDHashNode implementation +// +template +LLLocalIDHashNode::LLLocalIDHashNode() +{ + mCount = 0; + mNextNodep = NULL; +} + +// +// LLLocalIDHashMapIter +// +template +class LLLocalIDHashMap; + +template +class LLLocalIDHashMapIter +{ +public: + LLLocalIDHashMapIter(LLLocalIDHashMap *hash_mapp); + ~LLLocalIDHashMapIter(); + + void setMap(LLLocalIDHashMap *hash_mapp); + inline void first(); + inline void next(); + inline DATA_TYPE& current(); // *NOTE: Deprecate? Phoenix 2005-04-15 + inline BOOL done() const; + inline S32 currentBin() const; + inline void setBin(S32 bin); + + DATA_TYPE& operator*() const + { + return mCurHashNodep->mData[mCurHashNodeKey]; + } + DATA_TYPE* operator->() const + { + return &(operator*()); + } + + LLLocalIDHashMap *mHashMapp; + LLLocalIDHashNode *mCurHashNodep; + + S32 mCurHashMapNodeNum; + S32 mCurHashNodeKey; + + DATA_TYPE mNull; + + S32 mIterID; +}; + + + +template +class LLLocalIDHashMap +{ +public: + friend class LLLocalIDHashMapIter; + + LLLocalIDHashMap(); // DO NOT use this unless you explicitly setNull, or the data type constructs a "null" + // object by default + // basic constructor including sorter + LLLocalIDHashMap(const DATA_TYPE &null_data); + // Hack, this should really be a const ref, but I'm not doing it that way because the sim + // usually uses pointers. + ~LLLocalIDHashMap(); + + inline DATA_TYPE &get(const U32 local_id); + inline BOOL check(const U32 local_id) const; + inline DATA_TYPE &set(const U32 local_id, const DATA_TYPE data); + inline BOOL remove(const U32 local_id); + void removeAll(); + + void setNull(const DATA_TYPE data) { mNull = data; } + + inline S32 getLength() const; // Warning, NOT O(1!) + + void dumpIter(); + void dumpBin(U32 bin); + +protected: + // Only used by the iterator. + void addIter(LLLocalIDHashMapIter *iter); + void removeIter(LLLocalIDHashMapIter *iter); + + // Remove the item and shift all items afterward down the list, + // fixing up iterators as we go. + BOOL removeWithShift(const U32 local_id); + +protected: + LLLocalIDHashNode mNodes[256]; + + S32 mIterCount; + LLLocalIDHashMapIter *mIters[MAX_ITERS]; + + DATA_TYPE mNull; +}; + + +// +// LLLocalIDHashMap implementation +// + +template +LLLocalIDHashMap::LLLocalIDHashMap() +: mIterCount(0), + mNull() +{ + S32 i; + for (i = 0; i < MAX_ITERS; i++) + { + mIters[i] = NULL; + } +} + +template +LLLocalIDHashMap::LLLocalIDHashMap(const DATA_TYPE &null_data) +: mIterCount(0), + mNull(null_data) +{ + S32 i; + for (i = 0; i < MAX_ITERS; i++) + { + mIters[i] = NULL; + } +} + +template +LLLocalIDHashMap::~LLLocalIDHashMap() +{ + S32 i; + for (i = 0; i < MAX_ITERS; i++) + { + if (mIters[i]) + { + mIters[i]->mHashMapp = NULL; + mIterCount--; + } + } + removeAll(); +} + +template +void LLLocalIDHashMap::removeAll() +{ + S32 bin; + for (bin = 0; bin < 256; bin++) + { + LLLocalIDHashNode* nodep = &mNodes[bin]; + + BOOL first = TRUE; + do // First node guaranteed to be there + { + S32 i; + const S32 count = nodep->mCount; + + // Iterate through all members of this node + for (i = 0; i < count; i++) + { + nodep->mData[i] = mNull; + } + + nodep->mCount = 0; + // Done with all objects in this node, go to the next. + + LLLocalIDHashNode* curp = nodep; + nodep = nodep->mNextNodep; + + // Delete the node if it's not the first node + if (first) + { + first = FALSE; + curp->mNextNodep = NULL; + } + else + { + delete curp; + } + } while (nodep); + } +} + +template +void LLLocalIDHashMap::dumpIter() +{ + std::cout << "Hash map with " << mIterCount << " iterators" << std::endl; + + std::cout << "Hash Map Iterators:" << std::endl; + S32 i; + for (i = 0; i < MAX_ITERS; i++) + { + if (mIters[i]) + { + llinfos << i << " " << mIters[i]->mCurHashNodep << " " << mIters[i]->mCurHashNodeKey << llendl; + } + else + { + llinfos << i << "null" << llendl; + } + } +} + +template +void LLLocalIDHashMap::dumpBin(U32 bin) +{ + std::cout << "Dump bin " << bin << std::endl; + + LLLocalIDHashNode* nodep = &mNodes[bin]; + S32 node = 0; + do // First node guaranteed to be there. + { + std::cout << "Bin " << bin + << " node " << node + << " count " << nodep->mCount + << " contains " << std::flush; + + S32 i; + for (i = 0; i < nodep->mCount; i++) + { + std::cout << nodep->mData[i] << " " << std::flush; + } + + std::cout << std::endl; + + nodep = nodep->mNextNodep; + node++; + } while (nodep); +} + +template +inline S32 LLLocalIDHashMap::getLength() const +{ + S32 count = 0; + S32 bin; + for (bin = 0; bin < 256; bin++) + { + const LLLocalIDHashNode* nodep = &mNodes[bin]; + while (nodep) + { + count += nodep->mCount; + nodep = nodep->mNextNodep; + } + } + return count; +} + +template +inline DATA_TYPE &LLLocalIDHashMap::get(const U32 local_id) +{ + LLLocalIDHashNode* nodep = &mNodes[local_id & 0xff]; + + do // First node guaranteed to be there + { + S32 i; + const S32 count = nodep->mCount; + + // Iterate through all members of this node + for (i = 0; i < count; i++) + { + if (nodep->mKey[i] == local_id) + { + // We found it. + return nodep->mData[i]; + } + } + + // Done with all objects in this node, go to the next. + nodep = nodep->mNextNodep; + } while (nodep); + + return mNull; +} + + +template +inline BOOL LLLocalIDHashMap::check(const U32 local_id) const +{ + const LLLocalIDHashNode* nodep = &mNodes[local_id & 0xff]; + + do // First node guaranteed to be there + { + S32 i; + const S32 count = nodep->mCount; + + // Iterate through all members of this node + for (i = 0; i < count; i++) + { + if (nodep->mKey[i] == local_id) + { + // We found it. + return TRUE; + } + } + + // Done with all objects in this node, go to the next. + nodep = nodep->mNextNodep; + } while (nodep); + + // Didn't find anything + return FALSE; +} + + +template +inline DATA_TYPE &LLLocalIDHashMap::set(const U32 local_id, const DATA_TYPE data) +{ + // Set is just like a normal find, except that if we find a match + // we replace it with the input value. + // If we don't find a match, we append to the end of the list. + + LLLocalIDHashNode* nodep = &mNodes[local_id & 0xff]; + + while (1) + { + const S32 count = nodep->mCount; + + S32 i; + for (i = 0; i < count; i++) + { + if (nodep->mKey[i] == local_id) + { + // We found a match for this key, replace the data with + // the incoming data. + nodep->mData[i] = data; + return nodep->mData[i]; + } + } + if (!nodep->mNextNodep) + { + // We've iterated through all of the keys without finding a match + if (i < SIZE) + { + // There's still some space on this node, append + // the key and data to it. + nodep->mKey[i] = local_id; + nodep->mData[i] = data; + nodep->mCount++; + + return nodep->mData[i]; + } + else + { + // This node is full, append a new node to the end. + nodep->mNextNodep = new LLLocalIDHashNode; + nodep->mNextNodep->mKey[0] = local_id; + nodep->mNextNodep->mData[0] = data; + nodep->mNextNodep->mCount = 1; + + return nodep->mNextNodep->mData[0]; + } + } + + // No match on this node, go to the next + nodep = nodep->mNextNodep; + } +} + + +template +inline BOOL LLLocalIDHashMap::remove(const U32 local_id) +{ + // Remove is the trickiest operation. + // What we want to do is swap the last element of the last + // node if we find the one that we want to remove, but we have + // to deal with deleting the node from the tail if it's empty, but + // NOT if it's the only node left. + + const S32 node_index = local_id & 0xff; + + LLLocalIDHashNode* nodep = &mNodes[node_index]; + + // A modification of the standard search algorithm. + do // First node guaranteed to be there + { + const S32 count = nodep->mCount; + + S32 i; + for (i = 0; i < count; i++) + { + if (nodep->mKey[i] == local_id) + { + // If we're removing the item currently pointed to by one + // or more iterators, we can just swap in the last item + // and back the iterator(s) up by one. + // Otherwise, we need to do a slow and safe shift of all + // items back to one position to fill the hole and fix up + // all iterators we find. + BOOL need_shift = FALSE; + S32 cur_iter; + if (mIterCount) + { + for (cur_iter = 0; cur_iter < MAX_ITERS; cur_iter++) + { + if (mIters[cur_iter]) + { + // We only care if the hash map node is on the one + // that we're working on. If it's before, we've already + // traversed it, if it's after, changing the order doesn't + // matter. + if (mIters[cur_iter]->mCurHashMapNodeNum == node_index) + { + if ((mIters[cur_iter]->mCurHashNodep == nodep) + && (mIters[cur_iter]->mCurHashNodeKey == i)) + { + // it's on the one we're deleting, we'll + // fix the iterator quickly below. + } + else + { + // We're trying to remove an item on this + // iterator's chain that this + // iterator doesn't point to! We need to do + // the slow remove-and-shift-down case. + need_shift = TRUE; + } + } + } + } + } + + // Removing an item that isn't pointed to by all iterators + if (need_shift) + { + return removeWithShift(local_id); + } + + // Fix the iterators that point to this node/i pair, the + // one we're deleting + for (cur_iter = 0; cur_iter < MAX_ITERS; cur_iter++) + { + if (mIters[cur_iter]) + { + // We only care if the hash map node is on the one + // that we're working on. If it's before, we've already + // traversed it, if it's after, changing the order doesn't + // matter. + if (mIters[cur_iter]->mCurHashMapNodeNum == node_index) + { + if ((mIters[cur_iter]->mCurHashNodep == nodep) + && (mIters[cur_iter]->mCurHashNodeKey == i)) + { + // We can handle the case where we're deleting + // the element we're on trivially (sort of). + if (nodep->mCount > 1) + { + // If we're not going to delete this node, + // it's OK. + mIters[cur_iter]->mCurHashNodeKey--; + } + else + { + // We're going to delete this node, because this + // is the last element on it. + + // Find the next node, and then back up one. + mIters[cur_iter]->next(); + mIters[cur_iter]->mCurHashNodeKey--; + } + } + } + } + } + + // We found the node that we want to remove. + // Find the last (and next-to-last) node, and the index of the last + // element. We could conceviably start from the node we're on, + // but that makes it more complicated, this is easier. + + LLLocalIDHashNode *prevp = &mNodes[node_index]; + LLLocalIDHashNode *lastp = prevp; + + // Find the last and next-to-last + while (lastp->mNextNodep) + { + prevp = lastp; + lastp = lastp->mNextNodep; + } + + // First, swap in the last to the current location. + nodep->mKey[i] = lastp->mKey[lastp->mCount - 1]; + nodep->mData[i] = lastp->mData[lastp->mCount - 1]; + + // Now, we delete the entry + lastp->mCount--; + lastp->mData[lastp->mCount] = mNull; + + if (!lastp->mCount) + { + // We deleted the last element! + if (lastp != &mNodes[local_id & 0xff]) + { + // Only blitz the node if it's not the head + // Set the previous node to point to NULL, then + // blitz the empty last node + prevp->mNextNodep = NULL; + delete lastp; + } + } + + return TRUE; + } + } + + // Iterate to the next node, we've scanned all the entries in this one. + nodep = nodep->mNextNodep; + } while (nodep); + + return FALSE; +} + +template +BOOL LLLocalIDHashMap::removeWithShift(const U32 local_id) +{ + const S32 node_index = local_id & 0xFF; + LLLocalIDHashNode* nodep = &mNodes[node_index]; + LLLocalIDHashNode* prevp = NULL; + BOOL found = FALSE; + + do // First node guaranteed to be there + { + const S32 count = nodep->mCount; + S32 i; + for (i = 0; i < count; i++) + { + if (nodep->mKey[i] == local_id) + { + // Found the item. Start shifting items from later + // in the list over this item. + found = TRUE; + } + + if (found) + { + // If there is an iterator on this node, we need to + // back it up. + S32 cur_iter; + for (cur_iter = 0; cur_iter * iter; + iter = mIters[cur_iter]; + // If an iterator is on this node,i pair, then back it up. + if (iter + && iter->mCurHashMapNodeNum == node_index + && iter->mCurHashNodep == nodep + && iter->mCurHashNodeKey == i) + { + if (i > 0) + { + // Don't need to move iterator nodep, since + // we're in the same node. + iter->mCurHashNodeKey--; + } + else if (prevp) + { + // need to go the previous node, last item + iter->mCurHashNodep = prevp; + iter->mCurHashNodeKey = prevp->mCount - 1; + } + else + { + // we're on the first item in the list, but + // need to go back anyhow. + + // BUG: If this deletion empties the list, + // iter->done() will be wrong until + // iter->next() is called. + iter->mCurHashNodeKey = -1; + } + } + } + + // Copy data from the next position into this position. + if (i < count-1) + { + // we're not on the last item in the node, + // so we can copy within the node + nodep->mKey[i] = nodep->mKey[i+1]; + nodep->mData[i] = nodep->mData[i+1]; + } + else if (nodep->mNextNodep) + { + // we're on the last item in the node, + // but there's a next node we can copy from + nodep->mKey[i] = nodep->mNextNodep->mKey[0]; + nodep->mData[i] = nodep->mNextNodep->mData[0]; + } + else + { + // We're on the last position in the list. + // No one to copy from. Replace with nothing. + nodep->mKey[i] = 0; + nodep->mData[i] = mNull; + } + } + } + + // Last node in chain, so delete the last node + if (found + && !nodep->mNextNodep) + { + // delete the last item off the last node + nodep->mCount--; + + if (nodep->mCount == 0) + { + // We deleted the last element! + if (nodep != &mNodes[node_index]) + { + // Always have a prevp if we're not the head. + llassert(prevp); + + // Only blitz the node if it's not the head + // Set the previous node to point to NULL, then + // blitz the empty last node + prevp->mNextNodep = NULL; + delete nodep; + nodep = NULL; + } + } + + // Deleted last item in chain, so we're done. + return found; + } + + prevp = nodep; + nodep = nodep->mNextNodep; + } while (nodep); + + return found; +} + +template +void LLLocalIDHashMap::removeIter(LLLocalIDHashMapIter *iter) +{ + S32 i; + for (i = 0; i < MAX_ITERS; i++) + { + if (mIters[i] == iter) + { + mIters[i] = NULL; + mIterCount--; + return; + } + } + llerrs << "Iterator " << iter << " not found for removal in hash map!" << llendl; +} + +template +void LLLocalIDHashMap::addIter(LLLocalIDHashMapIter *iter) +{ + S32 i; + for (i = 0; i < MAX_ITERS; i++) + { + if (mIters[i] == NULL) + { + mIters[i] = iter; + mIterCount++; + return; + } + } + llerrs << "More than " << MAX_ITERS << " iterating over a map simultaneously!" << llendl; +} + + + +// +// LLLocalIDHashMapIter Implementation +// +template +LLLocalIDHashMapIter::LLLocalIDHashMapIter(LLLocalIDHashMap *hash_mapp) +{ + mHashMapp = NULL; + setMap(hash_mapp); +} + +template +LLLocalIDHashMapIter::~LLLocalIDHashMapIter() +{ + if (mHashMapp) + { + mHashMapp->removeIter(this); + } +} + +template +void LLLocalIDHashMapIter::setMap(LLLocalIDHashMap *hash_mapp) +{ + if (mHashMapp) + { + mHashMapp->removeIter(this); + } + mHashMapp = hash_mapp; + if (mHashMapp) + { + mHashMapp->addIter(this); + } + + mCurHashNodep = NULL; + mCurHashMapNodeNum = -1; + mCurHashNodeKey = 0; +} + +template +inline void LLLocalIDHashMapIter::first() +{ + // Iterate through until we find the first non-empty node; + S32 i; + for (i = 0; i < 256; i++) + { + if (mHashMapp->mNodes[i].mCount) + { + + mCurHashNodep = &mHashMapp->mNodes[i]; + mCurHashMapNodeNum = i; + mCurHashNodeKey = 0; + //return mCurHashNodep->mData[0]; + return; + } + } + + // Completely empty! + mCurHashNodep = NULL; + //return mNull; + return; +} + +template +inline BOOL LLLocalIDHashMapIter::done() const +{ + return mCurHashNodep ? FALSE : TRUE; +} + +template +inline S32 LLLocalIDHashMapIter::currentBin() const +{ + if ( (mCurHashMapNodeNum > 255) + ||(mCurHashMapNodeNum < 0)) + { + return 0; + } + else + { + return mCurHashMapNodeNum; + } +} + +template +inline void LLLocalIDHashMapIter::setBin(S32 bin) +{ + // Iterate through until we find the first non-empty node; + S32 i; + bin = llclamp(bin, 0, 255); + for (i = bin; i < 256; i++) + { + if (mHashMapp->mNodes[i].mCount) + { + + mCurHashNodep = &mHashMapp->mNodes[i]; + mCurHashMapNodeNum = i; + mCurHashNodeKey = 0; + return; + } + } + for (i = 0; i < bin; i++) + { + if (mHashMapp->mNodes[i].mCount) + { + + mCurHashNodep = &mHashMapp->mNodes[i]; + mCurHashMapNodeNum = i; + mCurHashNodeKey = 0; + return; + } + } + // Completely empty! + mCurHashNodep = NULL; +} + +template +inline DATA_TYPE &LLLocalIDHashMapIter::current() +{ + if (!mCurHashNodep) + { + return mNull; + } + return mCurHashNodep->mData[mCurHashNodeKey]; +} + +template +inline void LLLocalIDHashMapIter::next() +{ + // No current entry, this iterator is done + if (!mCurHashNodep) + { + //return mNull; + return; + } + + // Go to the next element + mCurHashNodeKey++; + if (mCurHashNodeKey < mCurHashNodep->mCount) + { + // We're not done with this node, return the current element + //return mCurHashNodep->mData[mCurHashNodeKey]; + return; + } + + // Done with this node, move to the next + mCurHashNodep = mCurHashNodep->mNextNodep; + if (mCurHashNodep) + { + // Return the first element + mCurHashNodeKey = 0; + //return mCurHashNodep->mData[0]; + return; + } + + // Find the next non-empty node (keyed on the first byte) + mCurHashMapNodeNum++; + + S32 i; + for (i = mCurHashMapNodeNum; i < 256; i++) + { + if (mHashMapp->mNodes[i].mCount) + { + // We found one that wasn't empty + mCurHashNodep = &mHashMapp->mNodes[i]; + mCurHashMapNodeNum = i; + mCurHashNodeKey = 0; + //return mCurHashNodep->mData[0]; + return; + } + } + + // OK, we're done, nothing else to iterate + mCurHashNodep = NULL; + mHashMapp->mIterCount--; // Decrement since we're safe to do removes now + //return mNull; + return; +} + +#endif // LL_LLLOCALIDHASHMAP_H diff --git a/indra/llcommon/lllslconstants.h b/indra/llcommon/lllslconstants.h new file mode 100644 index 0000000000..5c9017e43b --- /dev/null +++ b/indra/llcommon/lllslconstants.h @@ -0,0 +1,139 @@ +/** + * @file lllslconstants.h + * @author James Cook + * @brief Constants used in lsl. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLLSLCONSTANTS_H +#define LL_LLLSLCONSTANTS_H + +// LSL: Return flags for llGetAgentInfo +const U32 AGENT_FLYING = 0x0001; +const U32 AGENT_ATTACHMENTS = 0x0002; +const U32 AGENT_SCRIPTED = 0x0004; +const U32 AGENT_MOUSELOOK = 0x0008; +const U32 AGENT_SITTING = 0x0010; +const U32 AGENT_ON_OBJECT = 0x0020; +const U32 AGENT_AWAY = 0x0040; +const U32 AGENT_WALKING = 0x0080; +const U32 AGENT_IN_AIR = 0x0100; +const U32 AGENT_TYPING = 0x0200; +const U32 AGENT_CROUCHING = 0x0400; +const U32 AGENT_BUSY = 0x0800; +const U32 AGENT_ALWAYS_RUN = 0x1000; + +const S32 LSL_REMOTE_DATA_CHANNEL = 1; +const S32 LSL_REMOTE_DATA_REQUEST = 2; +const S32 LSL_REMOTE_DATA_REPLY = 3; + +// Constants used in extended LSL primitive setter and getters +const S32 LSL_PRIM_TYPE_LEGACY = 1; // No longer supported. +const S32 LSL_PRIM_MATERIAL = 2; +const S32 LSL_PRIM_PHYSICS = 3; +const S32 LSL_PRIM_TEMP_ON_REZ = 4; +const S32 LSL_PRIM_PHANTOM = 5; +const S32 LSL_PRIM_POSITION = 6; +const S32 LSL_PRIM_SIZE = 7; +const S32 LSL_PRIM_ROTATION = 8; +const S32 LSL_PRIM_TYPE = 9; // Replacement for LSL_PRIM_TYPE_LEGACY +const S32 LSL_PRIM_TEXTURE = 17; +const S32 LSL_PRIM_COLOR = 18; +const S32 LSL_PRIM_BUMP_SHINY = 19; +const S32 LSL_PRIM_FULLBRIGHT = 20; +const S32 LSL_PRIM_FLEXIBLE = 21; +const S32 LSL_PRIM_TEXGEN = 22; +const S32 LSL_PRIM_POINT_LIGHT = 23; +const S32 LSL_PRIM_CAST_SHADOWS = 24; + +const S32 LSL_PRIM_TYPE_BOX = 0; +const S32 LSL_PRIM_TYPE_CYLINDER= 1; +const S32 LSL_PRIM_TYPE_PRISM = 2; +const S32 LSL_PRIM_TYPE_SPHERE = 3; +const S32 LSL_PRIM_TYPE_TORUS = 4; +const S32 LSL_PRIM_TYPE_TUBE = 5; +const S32 LSL_PRIM_TYPE_RING = 6; + +const S32 LSL_PRIM_HOLE_DEFAULT = 0x00; +const S32 LSL_PRIM_HOLE_CIRCLE = 0x10; +const S32 LSL_PRIM_HOLE_SQUARE = 0x20; +const S32 LSL_PRIM_HOLE_TRIANGLE= 0x30; + +const S32 LSL_PRIM_MATERIAL_STONE = 0; +const S32 LSL_PRIM_MATERIAL_METAL = 1; +const S32 LSL_PRIM_MATERIAL_GLASS = 2; +const S32 LSL_PRIM_MATERIAL_WOOD = 3; +const S32 LSL_PRIM_MATERIAL_FLESH = 4; +const S32 LSL_PRIM_MATERIAL_PLASTIC = 5; +const S32 LSL_PRIM_MATERIAL_RUBBER = 6; +const S32 LSL_PRIM_MATERIAL_LIGHT = 7; + +const S32 LSL_PRIM_SHINY_NONE = 0; +const S32 LSL_PRIM_SHINY_LOW = 1; +const S32 LSL_PRIM_SHINY_MEDIUM = 2; +const S32 LSL_PRIM_SHINY_HIGH = 3; + +const S32 LSL_PRIM_TEXGEN_DEFAULT = 0; +const S32 LSL_PRIM_TEXGEN_PLANAR = 1; + +const S32 LSL_PRIM_BUMP_NONE = 0; +const S32 LSL_PRIM_BUMP_BRIGHT = 1; +const S32 LSL_PRIM_BUMP_DARK = 2; +const S32 LSL_PRIM_BUMP_WOOD = 3; +const S32 LSL_PRIM_BUMP_BARK = 4; +const S32 LSL_PRIM_BUMP_BRICKS = 5; +const S32 LSL_PRIM_BUMP_CHECKER = 6; +const S32 LSL_PRIM_BUMP_CONCRETE = 7; +const S32 LSL_PRIM_BUMP_TILE = 8; +const S32 LSL_PRIM_BUMP_STONE = 9; +const S32 LSL_PRIM_BUMP_DISKS = 10; +const S32 LSL_PRIM_BUMP_GRAVEL = 11; +const S32 LSL_PRIM_BUMP_BLOBS = 12; +const S32 LSL_PRIM_BUMP_SIDING = 13; +const S32 LSL_PRIM_BUMP_LARGETILE = 14; +const S32 LSL_PRIM_BUMP_STUCCO = 15; +const S32 LSL_PRIM_BUMP_SUCTION = 16; +const S32 LSL_PRIM_BUMP_WEAVE = 17; + +const S32 LSL_ALL_SIDES = -1; +const S32 LSL_LINK_ROOT = 1; +const S32 LSL_LINK_FIRST_CHILD = 2; +const S32 LSL_LINK_SET = -1; +const S32 LSL_LINK_ALL_OTHERS = -2; +const S32 LSL_LINK_ALL_CHILDREN = -3; +const S32 LSL_LINK_THIS = -4; + +// LSL constants for llSetForSell +const S32 SELL_NOT = 0; +const S32 SELL_ORIGINAL = 1; +const S32 SELL_COPY = 2; +const S32 SELL_CONTENTS = 3; + +// LSL constants for llSetPayPrice +const S32 PAY_PRICE_HIDE = -1; +const S32 PAY_PRICE_DEFAULT = -2; +const S32 MAX_PAY_BUTTONS = 4; +const S32 PAY_BUTTON_DEFAULT_0 = 1; +const S32 PAY_BUTTON_DEFAULT_1 = 5; +const S32 PAY_BUTTON_DEFAULT_2 = 10; +const S32 PAY_BUTTON_DEFAULT_3 = 20; + +// lsl email registration. +const S32 EMAIL_REG_SUBSCRIBE_OBJECT = 0x01; +const S32 EMAIL_REG_UNSUBSCRIBE_OBJECT = 0x02; +const S32 EMAIL_REG_UNSUBSCRIBE_SIM = 0x04; + +const S32 LIST_STAT_RANGE = 0; +const S32 LIST_STAT_MIN = 1; +const S32 LIST_STAT_MAX = 2; +const S32 LIST_STAT_MEAN = 3; +const S32 LIST_STAT_MEDIAN = 4; +const S32 LIST_STAT_STD_DEV = 5; +const S32 LIST_STAT_SUM = 6; +const S32 LIST_STAT_SUM_SQUARES = 7; +const S32 LIST_STAT_NUM_COUNT = 8; +const S32 LIST_STAT_GEO_MEAN = 9; + +#endif diff --git a/indra/llcommon/llmap.h b/indra/llcommon/llmap.h new file mode 100644 index 0000000000..fc958421da --- /dev/null +++ b/indra/llcommon/llmap.h @@ -0,0 +1,231 @@ +/** + * @file llmap.h + * @brief LLMap class header file + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMAP_H +#define LL_LLMAP_H + +#include +#include +#include + +// llmap uses the fast stl library code in a manner consistant with LLSkipMap, et. al. + +template class LLMap +{ +private: + typedef typename std::map stl_map_t; + typedef typename stl_map_t::iterator stl_iter_t; + typedef typename stl_map_t::value_type stl_value_t; + + stl_map_t mStlMap; + stl_iter_t mCurIter; // *iterator = pair + MAPPED_TYPE dummy_data; + INDEX_TYPE dummy_index; + +public: + LLMap() : mStlMap() + { + memset((void*)(&dummy_data), 0x0, sizeof(MAPPED_TYPE)); + memset((void*)(&dummy_index), 0x0, sizeof(INDEX_TYPE)); + mCurIter = mStlMap.begin(); + } + ~LLMap() + { + mStlMap.clear(); + } + + // use these functions to itterate through a list + void resetMap() + { + mCurIter = mStlMap.begin(); + } + + // get the current data and bump mCurrentp + // This is kind of screwy since it returns a reference; + // We have to have a dummy value for when we reach the end + // or in case we have an empty list. Presumably, this value + // will initialize to some NULL value that will end the iterator. + // We really shouldn't be using getNextData() or getNextKey() anyway... + MAPPED_TYPE &getNextData() + { + if (mCurIter == mStlMap.end()) + { + return dummy_data; + } + else + { + return (*mCurIter++).second; + } + } + + const INDEX_TYPE &getNextKey() + { + if (mCurIter == mStlMap.end()) + { + return dummy_index; + } + else + { + return (*mCurIter++).first; + } + } + + MAPPED_TYPE &getFirstData() + { + resetMap(); + return getNextData(); + } + + const INDEX_TYPE &getFirstKey() + { + resetMap(); + return getNextKey(); + } + + S32 getLength() + { + return mStlMap.size(); + } + + void addData(const INDEX_TYPE &index, MAPPED_TYPE pointed_to) + { + mStlMap.insert(stl_value_t(index, pointed_to)); + } + + void addData(const INDEX_TYPE &index) + { + mStlMap.insert(stl_value_t(index, dummy_data)); + } + + // if index doesn't exist, then insert a new node and return it + MAPPED_TYPE &getData(const INDEX_TYPE &index) + { + std::pair res; + res = mStlMap.insert(stl_value_t(index, dummy_data)); + return res.first->second; + } + + // if index doesn't exist, then insert a new node, return it, and set b_new_entry to true + MAPPED_TYPE &getData(const INDEX_TYPE &index, BOOL &b_new_entry) + { + std::pair res; + res = mStlMap.insert(stl_value_t(index, dummy_data)); + b_new_entry = res.second; + return res.first->second; + } + + // If there, returns the data. + // If not, returns NULL. + // Never adds entries to the map. + MAPPED_TYPE getIfThere(const INDEX_TYPE &index) + { + stl_iter_t iter; + iter = mStlMap.find(index); + if (iter == mStlMap.end()) + { + return (MAPPED_TYPE)0; + } + else + { + return (*iter).second; + } + } + + + // if index doesn't exist, then make a new node and return it + MAPPED_TYPE &operator[](const INDEX_TYPE &index) + { + return getData(index); + } + + // do a reverse look-up, return NULL if failed + INDEX_TYPE reverseLookup(const MAPPED_TYPE data) + { + stl_iter_t iter; + stl_iter_t end_iter; + iter = mStlMap.begin(); + end_iter = mStlMap.end(); + while (iter != end_iter) + { + if ((*iter).second == data) + return (*iter).first; + iter++; + } + return (INDEX_TYPE)0; + } + + BOOL removeData(const INDEX_TYPE &index) + { + mCurIter = mStlMap.find(index); + if (mCurIter == mStlMap.end()) + { + return FALSE; + } + else + { + stl_iter_t iter = mCurIter++; // incrament mCurIter to the next element + mStlMap.erase(iter); + return TRUE; + } + } + + // does this index exist? + BOOL checkData(const INDEX_TYPE &index) + { + stl_iter_t iter; + iter = mStlMap.find(index); + if (iter == mStlMap.end()) + { + return FALSE; + } + else + { + mCurIter = iter; + return TRUE; + } + } + + BOOL deleteData(const INDEX_TYPE &index) + { + mCurIter = mStlMap.find(index); + if (mCurIter == mStlMap.end()) + { + return FALSE; + } + else + { + stl_iter_t iter = mCurIter++; // incrament mCurIter to the next element + delete (*iter).second; + mStlMap.erase(iter); + return TRUE; + } + } + + void deleteAllData() + { + stl_iter_t iter; + stl_iter_t end_iter; + iter = mStlMap.begin(); + end_iter = mStlMap.end(); + while (iter != end_iter) + { + delete (*iter).second; + iter++; + } + mStlMap.clear(); + mCurIter = mStlMap.end(); + } + + void removeAllData() + { + mStlMap.clear(); + } +}; + + +#endif diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp new file mode 100644 index 0000000000..fd2c408fd4 --- /dev/null +++ b/indra/llcommon/llmemory.cpp @@ -0,0 +1,293 @@ +/** + * @file llmemory.cpp + * @brief Very special memory allocation/deallocation stuff here + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llmemory.h" + +//---------------------------------------------------------------------------- + +//static +char* LLMemory::reserveMem = 0; + +//static +void LLMemory::initClass() +{ + if (!reserveMem) + { + reserveMem = new char[16*1024]; // reserve 16K for out of memory error handling + } +} + +//static +void LLMemory::cleanupClass() +{ + delete [] reserveMem; + reserveMem = NULL; +} + +//static +void LLMemory::freeReserve() +{ + delete [] reserveMem; + reserveMem = NULL; +} + + +//---------------------------------------------------------------------------- + +//static +#if MEM_TRACK_TYPE +S32 LLMemType::sCurDepth = 0; +S32 LLMemType::sCurType = LLMemType::MTYPE_INIT; +S32 LLMemType::sType[LLMemType::MTYPE_MAX_DEPTH]; +S32 LLMemType::sMemCount[LLMemType::MTYPE_NUM_TYPES] = { 0 }; +S32 LLMemType::sMaxMemCount[LLMemType::MTYPE_NUM_TYPES] = { 0 }; +S32 LLMemType::sOverheadMem = 0; + +const char* LLMemType::sTypeDesc[LLMemType::MTYPE_NUM_TYPES] = +{ + "INIT", + "STARTUP", + "MAIN", + + "IMAGEBASE", + "IMAGERAW", + "IMAGEFORMATTED", + + "APPFMTIMAGE", + "APPRAWIMAGE", + "APPAUXRAWIMAGE", + + "DRAWABLE", + "OBJECT", + "PIPELINE", + "AVATAR", + "PARTICLES", + "REGIONS", + "INVENTORY", + "ANIMATION", + "NETWORK", + "PHYSICS", + "INTERESTLIST", + + "SCRIPT", + "SCRIPT_RUN", + "SCRIPT_BYTECODE", + + "IO_PUMP", + "IO_TCP", + "IO_BUFFER", + "IO_HTTP_SERVER" + "IO_SD_SERVER", + "IO_SD_CLIENT", + "IO_URL_REQUEST", + + "TEMP1", + "TEMP2", + "TEMP3", + "TEMP4", + "TEMP5", + "TEMP6", + "TEMP7", + "TEMP8", + "TEMP9" +}; + +#endif +S32 LLMemType::sTotalMem = 0; +S32 LLMemType::sMaxTotalMem = 0; + +//static +void LLMemType::printMem() +{ + S32 misc_mem = sTotalMem; +#if MEM_TRACK_TYPE + for (S32 i=0; i>20,sMaxMemCount[i]>>20) << llendl; + } + misc_mem -= sMemCount[i]; + } +#endif + llinfos << llformat("MEM: % 20s %03d MB","MISC",misc_mem>>20) << llendl; + llinfos << llformat("MEM: % 20s %03d MB (Max=%d MB)","TOTAL",sTotalMem>>20,sMaxTotalMem>>20) << llendl; +} + +#if MEM_TRACK_MEM + +void* ll_allocate (size_t size) +{ + if (size == 0) + { + llwarns << "Null allocation" << llendl; + } + + size = (size+3)&~3; + S32 alloc_size = size + 4; +#if MEM_TRACK_TYPE + alloc_size += 4; +#endif + char* p = (char*)malloc(alloc_size); + if (p == NULL) + { + LLMemory::freeReserve(); + llerrs << "Out of memory Error" << llendl; + } + LLMemType::sTotalMem += size; + LLMemType::sMaxTotalMem = llmax(LLMemType::sTotalMem, LLMemType::sMaxTotalMem); + LLMemType::sOverheadMem += 4; + *(size_t*)p = size; + p += 4; +#if MEM_TRACK_TYPE + if (LLMemType::sCurType < 0 || LLMemType::sCurType >= LLMemType::MTYPE_NUM_TYPES) + { + llerrs << "Memory Type Error: new" << llendl; + } + LLMemType::sOverheadMem += 4; + *(S32*)p = LLMemType::sCurType; + p += 4; + LLMemType::sMemCount[LLMemType::sCurType] += size; + if (LLMemType::sMemCount[LLMemType::sCurType] > LLMemType::sMaxMemCount[LLMemType::sCurType]) + { + LLMemType::sMaxMemCount[LLMemType::sCurType] = LLMemType::sMemCount[LLMemType::sCurType]; + } +#endif + return (void*)p; +} + +void ll_release (void *pin) +{ + if (!pin) + { + return; + } + char* p = (char*)pin; +#if MEM_TRACK_TYPE + p -= 4; + S32 type = *(S32*)p; + if (type < 0 || type >= LLMemType::MTYPE_NUM_TYPES) + { + llerrs << "Memory Type Error: delete" << llendl; + } +#endif + p -= 4; + S32 size = *(size_t*)p; + LLMemType::sOverheadMem -= 4; +#if MEM_TRACK_TYPE + LLMemType::sMemCount[type] -= size; + LLMemType::sOverheadMem -= 4; +#endif + LLMemType::sTotalMem -= size; + free(p); +} + +#else + +void* ll_allocate (size_t size) +{ + if (size == 0) + { + llwarns << "Null allocation" << llendl; + } + void *p = malloc(size); + if (p == NULL) + { + LLMemory::freeReserve(); + llerrs << "Out of memory Error" << llendl; + } + return p; +} + +void ll_release (void *p) +{ + free(p); +} + +#endif + +#if MEM_TRACK_MEM + +void* operator new (size_t size) +{ + return ll_allocate(size); +} + +void* operator new[] (size_t size) +{ + return ll_allocate(size); +} + +void operator delete (void *p) +{ + ll_release(p); +} + +void operator delete[] (void *p) +{ + ll_release(p); +} + +#endif + +//---------------------------------------------------------------------------- + +//static +LLMutex* LLThreadSafeRefCount::sMutex = 0; + +//static +void LLThreadSafeRefCount::initClass() +{ + if (!sMutex) + { + sMutex = new LLMutex(0); + } +} + +//static +void LLThreadSafeRefCount::cleanupClass() +{ + delete sMutex; + sMutex = NULL; +} + + +//---------------------------------------------------------------------------- + +LLThreadSafeRefCount::LLThreadSafeRefCount() : + mRef(0) +{ +} + +LLThreadSafeRefCount::~LLThreadSafeRefCount() +{ + if (mRef != 0) + { + llerrs << "deleting non-zero reference" << llendl; + } +} + +//---------------------------------------------------------------------------- + +LLRefCount::LLRefCount() : + mRef(0) +{ +} + +LLRefCount::~LLRefCount() +{ + if (mRef != 0) + { + llerrs << "deleting non-zero reference" << llendl; + } +} + +//---------------------------------------------------------------------------- + diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h new file mode 100644 index 0000000000..905d05254a --- /dev/null +++ b/indra/llcommon/llmemory.h @@ -0,0 +1,300 @@ +/** + * @file llmemory.h + * @brief Memory allocation/deallocation header-stuff goes here. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ +#ifndef LL_MEMORY_H +#define LL_MEMORY_H + +#include +#include + +#include "llerror.h" +#include "llthread.h" +#include "llmemtype.h" + +extern S32 gTotalDAlloc; +extern S32 gTotalDAUse; +extern S32 gDACount; + +const U32 LLREFCOUNT_SENTINEL_VALUE = 0xAAAAAAAA; + +//---------------------------------------------------------------------------- + +class LLMemory +{ +public: + static void initClass(); + static void cleanupClass(); + static void freeReserve(); +private: + static char* reserveMem; +}; + +//---------------------------------------------------------------------------- +// RefCount objects should generally only be accessed by way of LLPointer<>'s +// NOTE: LLPointer x = new LLFoo(); MAY NOT BE THREAD SAFE +// if LLFoo::LLFoo() does anything like put itself in an update queue. +// The queue may get accessed before it gets assigned to x. +// The correct implementation is: +// LLPointer x = new LLFoo; // constructor does not do anything interesting +// x->instantiate(); // does stuff like place x into an update queue + +class LLThreadSafeRefCount +{ +public: + static void initClass(); // creates sMutex + static void cleanupClass(); // destroys sMutex + +private: + static LLMutex* sMutex; + +private: + LLThreadSafeRefCount(const LLThreadSafeRefCount&); // not implemented + LLThreadSafeRefCount&operator=(const LLThreadSafeRefCount&); // not implemented + +protected: + virtual ~LLThreadSafeRefCount(); // use unref() + +public: + LLThreadSafeRefCount(); + + void ref() + { + if (sMutex) sMutex->lock(); + mRef++; + if (sMutex) sMutex->unlock(); + } + + S32 unref() + { + llassert(mRef >= 1); + if (sMutex) sMutex->lock(); + S32 res = --mRef; + if (sMutex) sMutex->unlock(); + if (0 == res) + { + delete this; + res = 0; + } + return res; + } + S32 getNumRefs() const + { + return mRef; + } + +private: + S32 mRef; +}; + +//---------------------------------------------------------------------------- + +class LLRefCount +{ +protected: + LLRefCount(const LLRefCount&); // not implemented +private: + LLRefCount&operator=(const LLRefCount&); // not implemented + +protected: + virtual ~LLRefCount(); // use unref() + +public: + LLRefCount(); + + void ref() + { + mRef++; + } + + S32 unref() + { + llassert(mRef >= 1); + if (0 == --mRef) + { + delete this; + return 0; + } + return mRef; + } + S32 getNumRefs() const + { + return mRef; + } + +private: + S32 mRef; +}; + +//---------------------------------------------------------------------------- + +template class LLPointer +{ +public: + + LLPointer() : + mPointer(NULL) + { + } + + LLPointer(Type* ptr) : + mPointer(ptr) + { + ref(); + } + + LLPointer(const LLPointer& ptr) : + mPointer(ptr.mPointer) + { + ref(); + } + + // support conversion up the type hierarchy. See Item 45 in Effective C++, 3rd Ed. + template + LLPointer(const LLPointer& ptr) : + mPointer(ptr.get()) + { + ref(); + } + + ~LLPointer() + { + unref(); + } + + Type* get() const { return mPointer; } + const Type* operator->() const { return mPointer; } + Type* operator->() { return mPointer; } + const Type& operator*() const { return *mPointer; } + Type& operator*() { return *mPointer; } + + operator BOOL() const { return (mPointer != NULL); } + operator bool() const { return (mPointer != NULL); } + bool operator!() const { return (mPointer == NULL); } + bool isNull() const { return (mPointer == NULL); } + bool notNull() const { return (mPointer != NULL); } + + operator Type*() const { return mPointer; } + operator const Type*() const { return mPointer; } + bool operator !=(Type* ptr) const { return (mPointer != ptr); } + bool operator ==(Type* ptr) const { return (mPointer == ptr); } + bool operator ==(const LLPointer& ptr) const { return (mPointer == ptr.mPointer); } + bool operator < (const LLPointer& ptr) const { return (mPointer < ptr.mPointer); } + bool operator > (const LLPointer& ptr) const { return (mPointer > ptr.mPointer); } + + LLPointer& operator =(Type* ptr) + { + if( mPointer != ptr ) + { + unref(); + mPointer = ptr; + ref(); + } + + return *this; + } + + LLPointer& operator =(const LLPointer& ptr) + { + if( mPointer != ptr.mPointer ) + { + unref(); + mPointer = ptr.mPointer; + ref(); + } + return *this; + } + + // support assignment up the type hierarchy. See Item 45 in Effective C++, 3rd Ed. + template + LLPointer& operator =(const LLPointer& ptr) + { + if( mPointer != ptr.get() ) + { + unref(); + mPointer = ptr.get(); + ref(); + } + return *this; + } + +protected: + void ref() + { + if (mPointer) + { + mPointer->ref(); + } + } + + void unref() + { + if (mPointer) + { + Type *tempp = mPointer; + mPointer = NULL; + tempp->unref(); + if (mPointer != NULL) + { + llwarns << "Unreference did assignment to non-NULL because of destructor" << llendl; + unref(); + } + } + } + +protected: + Type* mPointer; +}; + +// LLInitializedPointer is just a pointer with a default constructor that initializes it to NULL +// NOT a smart pointer like LLPointer<> +// Useful for example in std::map > +// (std::map uses the default constructor for creating new entries) +template class LLInitializedPointer +{ +public: + LLInitializedPointer() : mPointer(NULL) {} + ~LLInitializedPointer() { delete mPointer; } + + const T* operator->() const { return mPointer; } + T* operator->() { return mPointer; } + const T& operator*() const { return *mPointer; } + T& operator*() { return *mPointer; } + operator const T*() const { return mPointer; } + operator T*() { return mPointer; } + T* operator=(T* x) { return (mPointer = x); } + operator bool() const { return mPointer != NULL; } + bool operator!() const { return mPointer == NULL; } + bool operator==(T* rhs) { return mPointer == rhs; } + bool operator==(const LLInitializedPointer* rhs) { return mPointer == rhs.mPointer; } + +protected: + T* mPointer; +}; + +//---------------------------------------------------------------------------- + +// LLSingleton implements the getInstance() method part of the Singleton pattern. It can't make +// the derived class constructors protected, though, so you have to do that yourself. +// The proper way to use LLSingleton is to inherit from it while using the typename that you'd +// like to be static as the template parameter, like so: +// class FooBar: public LLSingleton +// As currently written, it is not thread-safe. +template +class LLSingleton +{ +public: + static T* getInstance() + { + static T instance; + return &instance; + } +}; + +//---------------------------------------------------------------------------- + +#endif + diff --git a/indra/llcommon/llmemorystream.cpp b/indra/llcommon/llmemorystream.cpp new file mode 100644 index 0000000000..dc0ad5aadb --- /dev/null +++ b/indra/llcommon/llmemorystream.cpp @@ -0,0 +1,52 @@ +/** + * @file llmemorystream.cpp + * @author Phoenix + * @date 2005-06-03 + * @brief Buffer and stream for a fixed linear memory segment. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llmemorystream.h" + +LLMemoryStreamBuf::LLMemoryStreamBuf(const U8* start, S32 length) +{ + reset(start, length); +} + +LLMemoryStreamBuf::~LLMemoryStreamBuf() +{ +} + +void LLMemoryStreamBuf::reset(const U8* start, S32 length) +{ + setg((char*)start, (char*)start, (char*)start + length); +} + +int LLMemoryStreamBuf::underflow() +{ + //lldebugs << "LLMemoryStreamBuf::underflow()" << llendl; + if(gptr() < egptr()) + { + return *gptr(); + } + return EOF; +} + +/** + * @class LLMemoryStreamBuf + */ + +LLMemoryStream::LLMemoryStream(const U8* start, S32 length) : + std::istream(&mStreamBuf), + mStreamBuf(start, length) +{ +} + +LLMemoryStream::~LLMemoryStream() +{ +} + + diff --git a/indra/llcommon/llmemorystream.h b/indra/llcommon/llmemorystream.h new file mode 100644 index 0000000000..7553c4a425 --- /dev/null +++ b/indra/llcommon/llmemorystream.h @@ -0,0 +1,63 @@ +/** + * @file llmemorystream.h + * @author Phoenix + * @date 2005-06-03 + * @brief Implementation of a simple fixed memory stream + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMEMORYSTREAM_H +#define LL_LLMEMORYSTREAM_H + +/** + * This is a simple but effective optimization when you want to treat + * a chunk of memory as an istream. I wrote this to avoid turing a + * buffer into a string, and then throwing the string into an + * iostringstream just to parse it into another datatype, eg, LLSD. + */ + +#include + +/** + * @class LLMemoryStreamBuf + * @brief This implements a wrapper around a piece of memory for istreams + * + * The memory passed in is NOT owned by an instance. The caller must + * be careful to always pass in a valid memory location that exists + * for at least as long as this streambuf. + */ +class LLMemoryStreamBuf : public std::streambuf +{ +public: + LLMemoryStreamBuf(const U8* start, S32 length); + ~LLMemoryStreamBuf(); + + void reset(const U8* start, S32 length); + +protected: + int underflow(); + //std::streamsize xsgetn(char* dest, std::streamsize n); +}; + + +/** + * @class LLMemoryStream + * @brief This implements a wrapper around a piece of memory for istreams + * + * The memory passed in is NOT owned by an instance. The caller must + * be careful to always pass in a valid memory location that exists + * for at least as long as this streambuf. + */ +class LLMemoryStream : public std::istream +{ +public: + LLMemoryStream(const U8* start, S32 length); + ~LLMemoryStream(); + +protected: + LLMemoryStreamBuf mStreamBuf; +}; + +#endif // LL_LLMEMORYSTREAM_H diff --git a/indra/llcommon/llmemtype.h b/indra/llcommon/llmemtype.h new file mode 100644 index 0000000000..17afaa6a8a --- /dev/null +++ b/indra/llcommon/llmemtype.h @@ -0,0 +1,135 @@ +/** + * @file llmemtype.h + * @brief Runtime memory usage debugging utilities. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_MEMTYPE_H +#define LL_MEMTYPE_H + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +class LLMemType; + +extern void* ll_allocate (size_t size); +extern void ll_release (void *p); + +#define MEM_TRACK_MEM 0 +#define MEM_TRACK_TYPE (1 && MEM_TRACK_MEM) + +#if MEM_TRACK_TYPE +#define MEM_TYPE_NEW(T) \ +static void* operator new(size_t s) { LLMemType mt(T); return ll_allocate(s); } \ +static void operator delete(void* p) { ll_release(p); } + +#else +#define MEM_TYPE_NEW(T) +#endif // MEM_TRACK_TYPE + + +//---------------------------------------------------------------------------- + +class LLMemType +{ +public: + // Also update sTypeDesc in llmemory.cpp + enum EMemType + { + MTYPE_INIT, + MTYPE_STARTUP, + MTYPE_MAIN, + + MTYPE_IMAGEBASE, + MTYPE_IMAGERAW, + MTYPE_IMAGEFORMATTED, + + MTYPE_APPFMTIMAGE, + MTYPE_APPRAWIMAGE, + MTYPE_APPAUXRAWIMAGE, + + MTYPE_DRAWABLE, + MTYPE_OBJECT, + MTYPE_SPACE_PARTITION, + MTYPE_PIPELINE, + MTYPE_AVATAR, + MTYPE_PARTICLES, + MTYPE_REGIONS, + MTYPE_INVENTORY, + MTYPE_ANIMATION, + MTYPE_NETWORK, + MTYPE_PHYSICS, + MTYPE_INTERESTLIST, + + MTYPE_SCRIPT, + MTYPE_SCRIPT_RUN, + MTYPE_SCRIPT_BYTECODE, + + MTYPE_IO_PUMP, + MTYPE_IO_TCP, + MTYPE_IO_BUFFER, + MTYPE_IO_HTTP_SERVER, + MTYPE_IO_SD_SERVER, + MTYPE_IO_SD_CLIENT, + MTYPE_IO_URL_REQUEST, + + MTYPE_TEMP1, + MTYPE_TEMP2, + MTYPE_TEMP3, + MTYPE_TEMP4, + MTYPE_TEMP5, + MTYPE_TEMP6, + MTYPE_TEMP7, + MTYPE_TEMP8, + MTYPE_TEMP9, + + MTYPE_OTHER, // Special, used by display code + + MTYPE_NUM_TYPES + }; + enum { MTYPE_MAX_DEPTH = 64 }; + +public: + LLMemType(EMemType type) + { +#if MEM_TRACK_TYPE + if (type < 0 || type >= MTYPE_NUM_TYPES) + llerrs << "LLMemType error" << llendl; + if (sCurDepth < 0 || sCurDepth >= MTYPE_MAX_DEPTH) + llerrs << "LLMemType error" << llendl; + sType[sCurDepth] = sCurType; + sCurDepth++; + sCurType = type; +#endif + } + ~LLMemType() + { +#if MEM_TRACK_TYPE + sCurDepth--; + sCurType = sType[sCurDepth]; +#endif + } + + static void reset(); + static void printMem(); + +public: +#if MEM_TRACK_TYPE + static S32 sCurDepth; + static S32 sCurType; + static S32 sType[MTYPE_MAX_DEPTH]; + static S32 sMemCount[MTYPE_NUM_TYPES]; + static S32 sMaxMemCount[MTYPE_NUM_TYPES]; + static S32 sOverheadMem; + static const char* sTypeDesc[MTYPE_NUM_TYPES]; +#endif + static S32 sTotalMem; + static S32 sMaxTotalMem; +}; + +//---------------------------------------------------------------------------- + +#endif + diff --git a/indra/llcommon/llmortician.cpp b/indra/llcommon/llmortician.cpp new file mode 100644 index 0000000000..eddfbb559e --- /dev/null +++ b/indra/llcommon/llmortician.cpp @@ -0,0 +1,51 @@ +/** + * @file llmortician.cpp + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "llmortician.h" + +#include + +std::list gGraveyard; + +BOOL LLMortician::sDestroyImmediate = FALSE; + +LLMortician::~LLMortician() +{ + gGraveyard.remove(this); +} + +void LLMortician::updateClass() +{ + while (!gGraveyard.empty()) + { + LLMortician* dead = gGraveyard.front(); + delete dead; + } +} + +void LLMortician::die() +{ + // It is valid to call die() more than once on something that hasn't died yet + if (sDestroyImmediate) + { + //HACK: we need to do this to ensure destruction order on shutdown + mIsDead = TRUE; + delete this; + return; + } + else if (!mIsDead) + { + mIsDead = TRUE; + gGraveyard.push_back(this); + } +} + +// static +void LLMortician::setZealous(BOOL b) +{ + sDestroyImmediate = b; +} diff --git a/indra/llcommon/llmortician.h b/indra/llcommon/llmortician.h new file mode 100644 index 0000000000..6e69f1af0b --- /dev/null +++ b/indra/llcommon/llmortician.h @@ -0,0 +1,33 @@ +/** + * @file llmortician.h + * @brief Base class for delayed deletions. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LLMORTICIAN_H +#define LLMORTICIAN_H + +#include "stdtypes.h" + +class LLMortician +{ +public: + LLMortician() { mIsDead = FALSE; } + static void updateClass(); + virtual ~LLMortician(); + void die(); + BOOL isDead() { return mIsDead; } + + // sets destroy immediate true + static void setZealous(BOOL b); + +private: + static BOOL sDestroyImmediate; + + BOOL mIsDead; +}; + +#endif + diff --git a/indra/llcommon/llnametable.h b/indra/llcommon/llnametable.h new file mode 100644 index 0000000000..d05885402d --- /dev/null +++ b/indra/llcommon/llnametable.h @@ -0,0 +1,87 @@ +/** + * @file llnametable.h + * @brief LLNameTable class is a table to associate pointers with string names + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLNAMETABLE_H +#define LL_LLNAMETABLE_H + +#include + +#include "string_table.h" + +template +class LLNameTable +{ +public: + LLNameTable() + : mNameMap() + { + } + + ~LLNameTable() + { + } + + void addEntry(const std::string& name, DATA data) + { + addEntry(name.c_str(), data); + } + + void addEntry(const char *name, DATA data) + { + char *tablename = gStringTable.addString(name); + mNameMap[tablename] = data; + } + + BOOL checkName(const std::string& name) const + { + return checkName(name.c_str()); + } + + // "logically const" even though it modifies the global nametable + BOOL checkName(const char *name) const + { + char *tablename = gStringTable.addString(name); + return mNameMap.count(tablename) ? TRUE : FALSE; + } + + DATA resolveName(const std::string& name) const + { + return resolveName(name.c_str()); + } + + // "logically const" even though it modifies the global nametable + DATA resolveName(const char *name) const + { + char *tablename = gStringTable.addString(name); + const_iter_t iter = mNameMap.find(tablename); + if (iter != mNameMap.end()) + return iter->second; + else + return 0; + } + + // O(N)! (currently only used in one place... (newsim/llstate.cpp)) + const char *resolveData(const DATA &data) const + { + const_iter_t iter = mNameMap.begin(); + const_iter_t end = mNameMap.end(); + for (; iter != end; ++iter) + { + if (iter->second == data) + return iter->first; + } + return NULL; + } + + typedef std::map name_map_t; + typedef typename std::map::iterator iter_t; + typedef typename std::map::const_iterator const_iter_t; + name_map_t mNameMap; +}; + +#endif diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h new file mode 100644 index 0000000000..829ae0de21 --- /dev/null +++ b/indra/llcommon/llpreprocessor.h @@ -0,0 +1,99 @@ +/** + * @file llpreprocessor.h + * @brief This file should be included in all Linden Lab files and + * should only contain special preprocessor directives + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LLPREPROCESSOR_H +#define LLPREPROCESSOR_H + +// Figure out endianness of platform +#ifdef LL_LINUX +#define __ENABLE_WSTRING +#include +#endif // LL_LINUX + +#if (defined(LL_WINDOWS) || (defined(LL_LINUX) && (__BYTE_ORDER == __LITTLE_ENDIAN)) || (defined(LL_DARWIN) && defined(__LITTLE_ENDIAN__))) +#define LL_LITTLE_ENDIAN 1 +#else +#define LL_BIG_ENDIAN 1 +#endif + +// Per-OS feature switches. + +#if LL_DARWIN + #define LL_QUICKTIME_ENABLED 1 + #define LL_MOZILLA_ENABLED 0 + #define LL_LIBXUL_ENABLED 1 +#elif LL_WINDOWS + #define LL_QUICKTIME_ENABLED 1 + #define LL_MOZILLA_ENABLED 0 + #define LL_LIBXUL_ENABLED 1 +#elif LL_LINUX + #define LL_QUICKTIME_ENABLED 0 + #define LL_MOZILLA_ENABLED 0 + #define LL_LIBXUL_ENABLED 0 +#endif + +#if LL_LIBXUL_ENABLED && !defined(MOZILLA_INTERNAL_API) + // Without this, nsTAString.h errors out with: + // "Cannot use internal string classes without MOZILLA_INTERNAL_API defined. Use the frozen header nsStringAPI.h instead." + // It might be worth our while to figure out if we can use the frozen apis at some point... + #define MOZILLA_INTERNAL_API 1 +#endif + +// Deal with minor differences on Unixy OSes. +#if LL_DARWIN || LL_LINUX + #define GCC_VERSION (__GNUC__ * 10000 \ + + __GNUC_MINOR__ * 100 \ + + __GNUC_PATCHLEVEL__) + + // Different name, same functionality. + #define stricmp strcasecmp + #define strnicmp strncasecmp + + // Not sure why this is different, but... + #ifndef MAX_PATH + #define MAX_PATH PATH_MAX + #endif // not MAX_PATH + +#endif + +// Deal with the differeneces on Windows +#if defined(LL_WINDOWS) +#define snprintf _snprintf +#endif // LL_WINDOWS + +// Static linking with apr on windows needs to be declared. +#ifdef LL_WINDOWS +#ifndef APR_DECLARE_STATIC +#define APR_DECLARE_STATIC // For APR on Windows +#endif +#ifndef APU_DECLARE_STATIC +#define APU_DECLARE_STATIC // For APR util on Windows +#endif +#endif + +#if defined(LL_WINDOWS) +#define BOOST_REGEX_NO_LIB 1 +#define CURL_STATICLIB 1 +#endif // LL_WINDOWS + + +// Deal with VC6 problems +#if defined(LL_WINDOWS) +#pragma warning( 3 : 4701 ) // "local variable used without being initialized" Treat this as level 3, not level 4. +#pragma warning( 3 : 4702 ) // "unreachable code" Treat this as level 3, not level 4. +#pragma warning( 3 : 4189 ) // "local variable initialized but not referenced" Treat this as level 3, not level 4. +//#pragma warning( 3 : 4018 ) // "signed/unsigned mismatch" Treat this as level 3, not level 4. +#pragma warning( 3 : 4265 ) // "class has virtual functions, but destructor is not virtual" +#pragma warning( disable : 4786 ) // silly MS warning deep inside their include file +#pragma warning( disable : 4284 ) // silly MS warning deep inside their include file +#pragma warning( disable : 4503 ) // 'decorated name length exceeded, name was truncated'. Does not seem to affect compilation. +#pragma warning( disable : 4800 ) // 'BOOL' : forcing value to bool 'true' or 'false' (performance warning) +#endif // LL_WINDOWS + +#endif // not LL_LINDEN_PREPROCESSOR_H diff --git a/indra/llcommon/llpriqueuemap.h b/indra/llcommon/llpriqueuemap.h new file mode 100644 index 0000000000..68fad0d6df --- /dev/null +++ b/indra/llcommon/llpriqueuemap.h @@ -0,0 +1,127 @@ +/** + * @file llpriqueuemap.h + * @brief Priority queue implementation + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ +#ifndef LL_LLPRIQUEUEMAP_H +#define LL_LLPRIQUEUEMAP_H + +#include + +// +// Priority queue, implemented under the hood as a +// map. Needs to be done this way because none of the +// standard STL containers provide a representation +// where it's easy to reprioritize. +// + +template +class LLPQMKey +{ +public: + LLPQMKey(const F32 priority, DATA data) : mPriority(priority), mData(data) + { + } + + bool operator<(const LLPQMKey &b) const + { + if (mPriority > b.mPriority) + { + return TRUE; + } + if (mPriority < b.mPriority) + { + return FALSE; + } + if (mData > b.mData) + { + return TRUE; + } + return FALSE; + } + + F32 mPriority; + DATA mData; +}; + +template +class LLPriQueueMap +{ +public: + typedef typename std::map, DATA_TYPE>::iterator pqm_iter; + typedef std::pair, DATA_TYPE> pqm_pair; + typedef void (*set_pri_fn)(DATA_TYPE &data, const F32 priority); + typedef F32 (*get_pri_fn)(DATA_TYPE &data); + + + LLPriQueueMap(set_pri_fn set_pri, get_pri_fn get_pri) : mSetPriority(set_pri), mGetPriority(get_pri) + { + } + + void push(const F32 priority, DATA_TYPE data) + { +#ifdef _DEBUG + pqm_iter iter = mMap.find(LLPQMKey(priority, data)); + if (iter != mMap.end()) + { + llerrs << "Pushing already existing data onto queue!" << llendl; + } +#endif + mMap.insert(pqm_pair(LLPQMKey(priority, data), data)); + } + + BOOL pop(DATA_TYPE *datap) + { + pqm_iter iter; + iter = mMap.begin(); + if (iter == mMap.end()) + { + return FALSE; + } + *datap = (*(iter)).second; + mMap.erase(iter); + + return TRUE; + } + + void reprioritize(const F32 new_priority, DATA_TYPE data) + { + pqm_iter iter; + F32 cur_priority = mGetPriority(data); + LLPQMKey cur_key(cur_priority, data); + iter = mMap.find(cur_key); + if (iter == mMap.end()) + { + llwarns << "Data not on priority queue!" << llendl; + // OK, try iterating through all of the data and seeing if we just screwed up the priority + // somehow. + for (iter = mMap.begin(); iter != mMap.end(); iter++) + { + if ((*(iter)).second == data) + { + llerrs << "Data on priority queue but priority not matched!" << llendl; + } + } + return; + } + + mMap.erase(iter); + mSetPriority(data, new_priority); + push(new_priority, data); + } + + S32 getLength() const + { + return (S32)mMap.size(); + } + + // Hack: public for use by the transfer manager, ugh. + std::map, DATA_TYPE> mMap; +protected: + void (*mSetPriority)(DATA_TYPE &data, const F32 priority); + F32 (*mGetPriority)(DATA_TYPE &data); +}; + +#endif // LL_LLPRIQUEUEMAP_H diff --git a/indra/llcommon/llprocessor.cpp b/indra/llcommon/llprocessor.cpp new file mode 100644 index 0000000000..04e1bc0839 --- /dev/null +++ b/indra/llcommon/llprocessor.cpp @@ -0,0 +1,2125 @@ +/** + * @file llprocessor.cpp + * @brief Code to figure out the processor. Originally by Benjamin Jurke. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Filename: Processor.cpp +// ======================= +// Author: Benjamin Jurke +// File history: 27.02.2002 - File created. Support for Intel and AMD processors +// 05.03.2002 - Fixed the CPUID bug: On Pre-Pentium CPUs the CPUID +// command is not available +// - The CProcessor::WriteInfoTextFile function do not +// longer use Win32 file functions (-> os independend) +// - Optional include of the windows.h header which is +// still need for CProcessor::GetCPUFrequency. +// 06.03.2002 - My birthday (18th :-)) +// - Replaced the '\r\n' line endings in function +// CProcessor::CPUInfoToText by '\n' +// - Replaced unsigned __int64 by signed __int64 for +// solving some compiler conversion problems +// - Fixed a bug at family=6, model=6 (Celeron -> P2) +////////////////////////////////////////////////////////////////////////////////// + +#include "linden_common.h" + +#include +#include +#include +#include "processor.h" + +#if !LL_DARWIN + +#ifdef PROCESSOR_FREQUENCY_MEASURE_AVAILABLE +// We need the QueryPerformanceCounter and Sleep functions +#define FORCEINLINE __forceinline +#else +#define FORCEINLINE +#endif + + +// Some macros we often need +//////////////////////////// +#define CheckBit(var, bit) ((var & (1 << bit)) ? true : false) + +#ifdef PROCESSOR_FREQUENCY_MEASURE_AVAILABLE +// Delays for the specified amount of milliseconds +static void _Delay(unsigned int ms) +{ + LARGE_INTEGER freq, c1, c2; + __int64 x; + + // Get High-Res Timer frequency + if (!QueryPerformanceFrequency(&freq)) + return; + + // Convert ms to High-Res Timer value + x = freq.QuadPart/1000*ms; + + // Get first snapshot of High-Res Timer value + QueryPerformanceCounter(&c1); + do + { + // Get second snapshot + QueryPerformanceCounter(&c2); + }while(c2.QuadPart-c1.QuadPart < x); + // Loop while (second-first < x) +} +#endif + +// CProcessor::CProcessor +// ====================== +// Class constructor: +///////////////////////// +CProcessor::CProcessor() +{ + uqwFrequency = 0; + memset(&CPUInfo, 0, sizeof(CPUInfo)); +} + +// unsigned __int64 CProcessor::GetCPUFrequency(unsigned int uiMeasureMSecs) +// ========================================================================= +// Function to measure the current CPU frequency +//////////////////////////////////////////////////////////////////////////// +F64 CProcessor::GetCPUFrequency(unsigned int uiMeasureMSecs) +{ +#ifndef PROCESSOR_FREQUENCY_MEASURE_AVAILABLE + return 0; +#else + // If there are invalid measure time parameters, zero msecs for example, + // we've to exit the function + if (uiMeasureMSecs < 1) + { + // If theres already a measured frequency available, we return it + if (uqwFrequency > 0) + return uqwFrequency; + else + return 0; + } + + // Now we check if the CPUID command is available + if (!CheckCPUIDPresence()) + return 0; + + // First we get the CPUID standard level 0x00000001 + unsigned long reg; + __asm + { + mov eax, 1 + cpuid + mov reg, edx + } + + // Then we check, if the RDTSC (Real Date Time Stamp Counter) is available. + // This function is necessary for our measure process. + if (!(reg & (1 << 4))) + return 0; + + // After that we declare some vars and check the frequency of the high + // resolution timer for the measure process. + // If there's no high-res timer, we exit. + __int64 starttime, endtime, timedif, freq, start, end, dif; + if (!QueryPerformanceFrequency((LARGE_INTEGER *) &freq)) + return 0; + + // Now we can init the measure process. We set the process and thread priority + // to the highest available level (Realtime priority). Also we focus the + // first processor in the multiprocessor system. + HANDLE hProcess = GetCurrentProcess(); + HANDLE hThread = GetCurrentThread(); + unsigned long dwCurPriorityClass = GetPriorityClass(hProcess); + int iCurThreadPriority = GetThreadPriority(hThread); + unsigned long dwProcessMask, dwSystemMask, dwNewMask = 1; + GetProcessAffinityMask(hProcess, &dwProcessMask, &dwSystemMask); + + SetPriorityClass(hProcess, REALTIME_PRIORITY_CLASS); + SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL); + SetProcessAffinityMask(hProcess, dwNewMask); + + // Now we call a CPUID to ensure, that all other prior called functions are + // completed now (serialization) + __asm cpuid + + // We ask the high-res timer for the start time + QueryPerformanceCounter((LARGE_INTEGER *) &starttime); + + // Then we get the current cpu clock and store it + __asm + { + rdtsc + mov dword ptr [start+4], edx + mov dword ptr [start], eax + } + + // Now we wart for some msecs + _Delay(uiMeasureMSecs); +// Sleep(uiMeasureMSecs); + + // We ask for the end time + QueryPerformanceCounter((LARGE_INTEGER *) &endtime); + + // And also for the end cpu clock + __asm + { + rdtsc + mov dword ptr [end+4], edx + mov dword ptr [end], eax + } + + // Now we can restore the default process and thread priorities + SetProcessAffinityMask(hProcess, dwProcessMask); + SetThreadPriority(hThread, iCurThreadPriority); + SetPriorityClass(hProcess, dwCurPriorityClass); + + // Then we calculate the time and clock differences + dif = end - start; + timedif = endtime - starttime; + + // And finally the frequency is the clock difference divided by the time + // difference. + uqwFrequency = (F64)dif / (((F64)timedif) / freq); + + // At last we just return the frequency that is also stored in the call + // member var uqwFrequency + return uqwFrequency; +#endif +} + +// bool CProcessor::AnalyzeIntelProcessor() +// ======================================== +// Private class function for analyzing an Intel processor +////////////////////////////////////////////////////////// +bool CProcessor::AnalyzeIntelProcessor() +{ +#if LL_WINDOWS + unsigned long eaxreg, ebxreg, edxreg; + + // First we check if the CPUID command is available + if (!CheckCPUIDPresence()) + return false; + + // Now we get the CPUID standard level 0x00000001 + __asm + { + mov eax, 1 + cpuid + mov eaxreg, eax + mov ebxreg, ebx + mov edxreg, edx + } + + // Then get the cpu model, family, type, stepping and brand id by masking + // the eax and ebx register + CPUInfo.uiStepping = eaxreg & 0xF; + CPUInfo.uiModel = (eaxreg >> 4) & 0xF; + CPUInfo.uiFamily = (eaxreg >> 8) & 0xF; + CPUInfo.uiType = (eaxreg >> 12) & 0x3; + CPUInfo.uiBrandID = ebxreg & 0xF; + + // Now we can translate the type number to a more understandable string format + switch (CPUInfo.uiType) + { + case 0: // Type = 0: Original OEM processor + strcpy(CPUInfo.strType, "Original OEM"); /* Flawfinder: ignore */ + strcpy(strCPUName, CPUInfo.strType); /* Flawfinder: ignore */ + strcat(strCPUName, " "); /* Flawfinder: ignore */ + break; + case 1: // Type = 1: Overdrive processor + strcpy(CPUInfo.strType, "Overdrive"); /* Flawfinder: ignore */ + strcpy(strCPUName, CPUInfo.strType); /* Flawfinder: ignore */ + strcat(strCPUName, " "); /* Flawfinder: ignore */ + break; + case 2: // Type = 2: Dual-capable processor + strcpy(CPUInfo.strType, "Dual-capable"); /* Flawfinder: ignore */ + strcpy(strCPUName, CPUInfo.strType); /* Flawfinder: ignore */ + strcat(strCPUName, " "); /* Flawfinder: ignore */ + break; + case 3: // Type = 3: Reserved for future use + strcpy(CPUInfo.strType, "Reserved"); /* Flawfinder: ignore */ + break; + default: // This should be never called, cause we just mask 2 bits --> [0..3] + strcpy(CPUInfo.strType, "Unknown"); /* Flawfinder: ignore */ + break; + } + + // Then we translate the brand id: + switch (CPUInfo.uiBrandID) + { + case 0: // Brand id = 0: Brand id not supported on this processor + strcpy(CPUInfo.strBrandID, "Not supported"); /* Flawfinder: ignore */ + break; + case 1: // Brand id = 1: Intel Celeron (0.18 micron) processor + strcpy(CPUInfo.strBrandID, "0.18 micron Intel Celeron"); /* Flawfinder: ignore */ + break; + case 2: // Brand id = 2: Intel Pentium III (0.18 micron) processor + strcpy(CPUInfo.strBrandID, "0.18 micron Intel Pentium III"); /* Flawfinder: ignore */ + break; + case 3: // Brand id = 3: Model dependent + if (CPUInfo.uiModel == 6) // If the cpu model is Celeron (well, I'm NOT SURE!!!) + strcpy(CPUInfo.strBrandID, "0.13 micron Intel Celeron"); /* Flawfinder: ignore */ + else + strcpy(CPUInfo.strBrandID, "0.18 micron Intel Pentium III Xeon"); /* Flawfinder: ignore */ + break; + case 4: // Brand id = 4: Intel Pentium III Tualatin (0.13 micron) processor + strcpy(CPUInfo.strBrandID, "0.13 micron Intel Pentium III"); /* Flawfinder: ignore */ + break; + case 6: // Brand id = 6: Intel Pentium III mobile (0.13 micron) processor + strcpy(CPUInfo.strBrandID, "0.13 micron Intel Pentium III mobile"); /* Flawfinder: ignore */ + break; + case 7: // Brand id = 7: Intel Celeron mobile (0.13 micron) processor + strcpy(CPUInfo.strBrandID, "0.13 micron Intel Celeron mobile"); /* Flawfinder: ignore */ + break; + case 8: // Brand id = 8: Intel Pentium 4 Willamette (0.18 micron) processor + strcpy(CPUInfo.strBrandID, "0.18 micron Intel Pentium 4"); /* Flawfinder: ignore */ + break; + case 9: // Brand id = 9: Intel Pentium 4 Northwood (0.13 micron) processor + strcpy(CPUInfo.strBrandID, "0.13 micron Intel Pentium 4"); /* Flawfinder: ignore */ + break; + case 0xA: // Brand id = 0xA: Intel Pentium 4 Northwood (0.13 micron processor) + strcpy(CPUInfo.strBrandID, "0.13 micron Intel Pentium 4"); /* Flawfinder: ignore */ + break; // No idea, where the difference to id=9 is + case 0xB: // Brand id = 0xB: Intel Pentium 4 Northwood Xeon (0.13 micron processor) + strcpy(CPUInfo.strBrandID, "0.13 micron Intel Pentium 4 Xeon"); /* Flawfinder: ignore */ + break; + case 0xE: // Brand id = 0xE: Intel Pentium 4 Willamette Xeon (0.18 micron processor) + strcpy(CPUInfo.strBrandID, "0.18 micron Intel Pentium 4 Xeon"); /* Flawfinder: ignore */ + break; + default: // Should be never called, but sure is sure + strcpy(CPUInfo.strBrandID, "Unknown"); /* Flawfinder: ignore */ + break; + } + + // Then we translate the cpu family + switch (CPUInfo.uiFamily) + { + case 3: // Family = 3: i386 (80386) processor family + strcpy(CPUInfo.strFamily, "Intel i386"); /* Flawfinder: ignore */ + break; + case 4: // Family = 4: i486 (80486) processor family + strcpy(CPUInfo.strFamily, "Intel i486"); /* Flawfinder: ignore */ + break; + case 5: // Family = 5: Pentium (80586) processor family + strcpy(CPUInfo.strFamily, "Intel Pentium"); /* Flawfinder: ignore */ + break; + case 6: // Family = 6: Pentium Pro (80686) processor family + strcpy(CPUInfo.strFamily, "Intel Pentium Pro"); /* Flawfinder: ignore */ + break; + case 15: // Family = 15: Extended family specific + // Masking the extended family + CPUInfo.uiExtendedFamily = (eaxreg >> 20) & 0xFF; + switch (CPUInfo.uiExtendedFamily) + { + case 0: // Family = 15, Ext. Family = 0: Pentium 4 (80786 ??) processor family + strcpy(CPUInfo.strFamily, "Intel Pentium 4"); /* Flawfinder: ignore */ + break; + case 1: // Family = 15, Ext. Family = 1: McKinley (64-bit) processor family + strcpy(CPUInfo.strFamily, "Intel McKinley (IA-64)"); /* Flawfinder: ignore */ + break; + default: // Sure is sure + strcpy(CPUInfo.strFamily, "Unknown Intel Pentium 4+"); /* Flawfinder: ignore */ + break; + } + break; + default: // Failsave + strcpy(CPUInfo.strFamily, "Unknown"); /* Flawfinder: ignore */ + break; + } + + // Now we come to the big deal, the exact model name + switch (CPUInfo.uiFamily) + { + case 3: // i386 (80386) processor family + strcpy(CPUInfo.strModel, "Unknown Intel i386"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel i386", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + break; + case 4: // i486 (80486) processor family + switch (CPUInfo.uiModel) + { + case 0: // Model = 0: i486 DX-25/33 processor model + strcpy(CPUInfo.strModel, "Intel i486 DX-25/33"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel i486 DX-25/33", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + break; + case 1: // Model = 1: i486 DX-50 processor model + strcpy(CPUInfo.strModel, "Intel i486 DX-50"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel i486 DX-50", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + break; + case 2: // Model = 2: i486 SX processor model + strcpy(CPUInfo.strModel, "Intel i486 SX"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel i486 SX", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + break; + case 3: // Model = 3: i486 DX2 (with i487 numeric coprocessor) processor model + strcpy(CPUInfo.strModel, "Intel i486 487/DX2"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel i486 DX2 with i487 numeric coprocessor", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + break; + case 4: // Model = 4: i486 SL processor model (never heard ?!?) + strcpy(CPUInfo.strModel, "Intel i486 SL"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel i486 SL", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + break; + case 5: // Model = 5: i486 SX2 processor model + strcpy(CPUInfo.strModel, "Intel i486 SX2"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel i486 SX2", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + break; + case 7: // Model = 7: i486 write-back enhanced DX2 processor model + strcpy(CPUInfo.strModel, "Intel i486 write-back enhanced DX2"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel i486 write-back enhanced DX2", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + break; + case 8: // Model = 8: i486 DX4 processor model + strcpy(CPUInfo.strModel, "Intel i486 DX4"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel i486 DX4", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + break; + case 9: // Model = 9: i486 write-back enhanced DX4 processor model + strcpy(CPUInfo.strModel, "Intel i486 write-back enhanced DX4"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel i486 DX4", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + break; + default: // ... + strcpy(CPUInfo.strModel, "Unknown Intel i486"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel i486 (Unknown model)", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + break; + } + break; + case 5: // Pentium (80586) processor family + switch (CPUInfo.uiModel) + { + case 0: // Model = 0: Pentium (P5 A-Step) processor model + strcpy(CPUInfo.strModel, "Intel Pentium (P5 A-Step)"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel Pentium (P5 A-Step core)", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + break; // Famous for the DIV bug, as far as I know + case 1: // Model = 1: Pentium 60/66 processor model + strcpy(CPUInfo.strModel, "Intel Pentium 60/66 (P5)"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel Pentium 60/66 (P5 core)", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + break; + case 2: // Model = 2: Pentium 75-200 (P54C) processor model + strcpy(CPUInfo.strModel, "Intel Pentium 75-200 (P54C)"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel Pentium 75-200 (P54C core)", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + break; + case 3: // Model = 3: Pentium overdrive for 486 systems processor model + strcpy(CPUInfo.strModel, "Intel Pentium for 486 system (P24T Overdrive)"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel Pentium for 486 (P24T overdrive core)", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + case 4: // Model = 4: Pentium MMX processor model + strcpy(CPUInfo.strModel, "Intel Pentium MMX (P55C)"); /*Flawfinder: ignore*/ + strncat(strCPUName, "Intel Pentium MMX (P55C core)", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + case 7: // Model = 7: Pentium processor model (don't know difference to Model=2) + strcpy(CPUInfo.strModel, "Intel Pentium (P54C)"); /*Flawfinder: ignore*/ + strncat(strCPUName, "Intel Pentium (P54C core)", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + case 8: // Model = 8: Pentium MMX (0.25 micron) processor model + strcpy(CPUInfo.strModel, "Intel Pentium MMX (P55C), 0.25 micron"); /*Flawfinder: ignore*/ + strncat(strCPUName, "Intel Pentium MMX (P55C core), 0.25 micron", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + default: // ... + strcpy(CPUInfo.strModel, "Unknown Intel Pentium"); /*Flawfinder: ignore*/ + strncat(strCPUName, "Intel Pentium (Unknown P5-model)", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + } + break; + case 6: // Pentium Pro (80686) processor family + switch (CPUInfo.uiModel) + { + case 0: // Model = 0: Pentium Pro (P6 A-Step) processor model + strcpy(CPUInfo.strModel, "Intel Pentium Pro (P6 A-Step)"); /*Flawfinder: ignore*/ + strncat(strCPUName, "Intel Pentium Pro (P6 A-Step core)", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + case 1: // Model = 1: Pentium Pro + strcpy(CPUInfo.strModel, "Intel Pentium Pro (P6)"); /*Flawfinder: ignore*/ + strncat(strCPUName, "Intel Pentium Pro (P6 core)", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + case 3: // Model = 3: Pentium II (66 MHz FSB, I think) processor model + strcpy(CPUInfo.strModel, "Intel Pentium II Model 3, 0.28 micron"); /*Flawfinder: ignore*/ + strncat(strCPUName, "Intel Pentium II (Model 3 core, 0.28 micron process)", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + case 5: // Model = 5: Pentium II/Xeon/Celeron (0.25 micron) processor model + strcpy(CPUInfo.strModel, "Intel Pentium II Model 5/Xeon/Celeron, 0.25 micron"); /*Flawfinder: ignore*/ + strncat(strCPUName, "Intel Pentium II/Xeon/Celeron (Model 5 core, 0.25 micron process)", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + case 6: // Model = 6: Pentium II with internal L2 cache + strcpy(CPUInfo.strModel, "Intel Pentium II - internal L2 cache"); /*Flawfinder: ignore*/ + strncat(strCPUName, "Intel Pentium II with internal L2 cache", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + case 7: // Model = 7: Pentium III/Xeon (extern L2 cache) processor model + strcpy(CPUInfo.strModel, "Intel Pentium III/Pentium III Xeon - external L2 cache, 0.25 micron"); /*Flawfinder: ignore*/ + strncat(strCPUName, "Intel Pentium III/Pentium III Xeon (0.25 micron process) with external L2 cache", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + case 8: // Model = 8: Pentium III/Xeon/Celeron (256 KB on-die L2 cache) processor model + strcpy(CPUInfo.strModel, "Intel Pentium III/Celeron/Pentium III Xeon - internal L2 cache, 0.18 micron"); /*Flawfinder: ignore*/ + // We want to know it exactly: + switch (CPUInfo.uiBrandID) + { + case 1: // Model = 8, Brand id = 1: Celeron (on-die L2 cache) processor model + strncat(strCPUName, "Intel Celeron (0.18 micron process) with internal L2 cache", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + case 2: // Model = 8, Brand id = 2: Pentium III (on-die L2 cache) processor model (my current cpu :-)) + strncat(strCPUName, "Intel Pentium III (0.18 micron process) with internal L2 cache", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + case 3: // Model = 8, Brand id = 3: Pentium III Xeon (on-die L2 cache) processor model + strncat(strCPUName, "Intel Pentium III Xeon (0.18 micron process) with internal L2 cache", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + default: // ...² + strncat(strCPUName, "Intel Pentium III core (unknown model, 0.18 micron process) with internal L2 cache", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + } + break; + case 0xA: // Model = 0xA: Pentium III/Xeon/Celeron (1 or 2 MB on-die L2 cache) processor model + strcpy(CPUInfo.strModel, "Intel Pentium III/Celeron/Pentium III Xeon - internal L2 cache, 0.18 micron"); /*Flawfinder: ignore*/ + // Exact detection: + switch (CPUInfo.uiBrandID) + { + case 1: // Model = 0xA, Brand id = 1: Celeron (1 or 2 MB on-die L2 cache (does it exist??)) processor model + strncat(strCPUName, "Intel Celeron (0.18 micron process) with internal L2 cache", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + case 2: // Model = 0xA, Brand id = 2: Pentium III (1 or 2 MB on-die L2 cache (never seen...)) processor model + strncat(strCPUName, "Intel Pentium III (0.18 micron process) with internal L2 cache", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + case 3: // Model = 0xA, Brand id = 3: Pentium III Xeon (1 or 2 MB on-die L2 cache) processor model + strncat(strCPUName, "Intel Pentium III Xeon (0.18 micron process) with internal L2 cache", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + default: // Getting bored of this............ + strncat(strCPUName, "Intel Pentium III core (unknown model, 0.18 micron process) with internal L2 cache", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + } + break; + case 0xB: // Model = 0xB: Pentium III/Xeon/Celeron (Tualatin core, on-die cache) processor model + strcpy(CPUInfo.strModel, "Intel Pentium III/Celeron/Pentium III Xeon - internal L2 cache, 0.13 micron"); /*Flawfinder: ignore*/ + // Omniscient: ;-) + switch (CPUInfo.uiBrandID) + { + case 3: // Model = 0xB, Brand id = 3: Celeron (Tualatin core) processor model + strncat(strCPUName, "Intel Celeron (Tualatin core, 0.13 micron process) with internal L2 cache", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + case 4: // Model = 0xB, Brand id = 4: Pentium III (Tualatin core) processor model + strncat(strCPUName, "Intel Pentium III (Tualatin core, 0.13 micron process) with internal L2 cache", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + case 7: // Model = 0xB, Brand id = 7: Celeron mobile (Tualatin core) processor model + strncat(strCPUName, "Intel Celeron mobile (Tualatin core, 0.13 micron process) with internal L2 cache", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + default: // *bored* + strncat(strCPUName, "Intel Pentium III Tualatin core (unknown model, 0.13 micron process) with internal L2 cache", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + } + break; + default: // *more bored* + strcpy(CPUInfo.strModel, "Unknown Intel Pentium Pro"); /*Flawfinder: ignore*/ + strncat(strCPUName, "Intel Pentium Pro (Unknown model)", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + break; + } + break; + case 15: // Extended processor family + // Masking the extended model + CPUInfo.uiExtendedModel = (eaxreg >> 16) & 0xFF; + switch (CPUInfo.uiModel) + { + case 0: // Model = 0: Pentium 4 Willamette (A-Step) core + if ((CPUInfo.uiBrandID) == 8) // Brand id = 8: P4 Willamette + { + strcpy(CPUInfo.strModel, "Intel Pentium 4 Willamette (A-Step)"); /*Flawfinder: ignore*/ + strncat(strCPUName, "Intel Pentium 4 Willamette (A-Step)", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ + } + else // else Xeon + { + strcpy(CPUInfo.strModel, "Intel Pentium 4 Willamette Xeon (A-Step)"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel Pentium 4 Willamette Xeon (A-Step)", sizeof(strCPUName) - strlen(strCPUName) - 1); /* Flawfinder: ignore */ + } + break; + case 1: // Model = 1: Pentium 4 Willamette core + if ((CPUInfo.uiBrandID) == 8) // Brand id = 8: P4 Willamette + { + strcpy(CPUInfo.strModel, "Intel Pentium 4 Willamette"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel Pentium 4 Willamette", sizeof(strCPUName) - strlen(strCPUName) - 1); /* Flawfinder: ignore */ + } + else // else Xeon + { + strcpy(CPUInfo.strModel, "Intel Pentium 4 Willamette Xeon"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel Pentium 4 Willamette Xeon", sizeof(strCPUName) - strlen(strCPUName) - 1); /* Flawfinder: ignore */ + } + break; + case 2: // Model = 2: Pentium 4 Northwood core + if (((CPUInfo.uiBrandID) == 9) || ((CPUInfo.uiBrandID) == 0xA)) // P4 Willamette + { + strcpy(CPUInfo.strModel, "Intel Pentium 4 Northwood"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel Pentium 4 Northwood", sizeof(strCPUName) - strlen(strCPUName) - 1); /* Flawfinder: ignore */ + } + else // Xeon + { + strcpy(CPUInfo.strModel, "Intel Pentium 4 Northwood Xeon"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel Pentium 4 Northwood Xeon", sizeof(strCPUName) - strlen(strCPUName) - 1); /* Flawfinder: ignore */ + } + break; + default: // Silly stupid never used failsave option + strcpy(CPUInfo.strModel, "Unknown Intel Pentium 4"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel Pentium 4 (Unknown model)", sizeof(strCPUName) - strlen(strCPUName) - 1); /* Flawfinder: ignore */ + break; + } + break; + default: // *grmpf* + strcpy(CPUInfo.strModel, "Unknown Intel model"); /* Flawfinder: ignore */ + strncat(strCPUName, "Intel (Unknown model)", sizeof(strCPUName) - strlen(strCPUName) - 1); /* Flawfinder: ignore */ + break; + } + + // After the long processor model block we now come to the processors serial + // number. + // First of all we check if the processor supports the serial number + if (CPUInfo.MaxSupportedLevel >= 3) + { + // If it supports the serial number CPUID level 0x00000003 we read the data + unsigned long sig1, sig2, sig3; + __asm + { + mov eax, 1 + cpuid + mov sig1, eax + mov eax, 3 + cpuid + mov sig2, ecx + mov sig3, edx + } + // Then we convert the data to a readable string + snprintf( + CPUInfo.strProcessorSerial, + sizeof(CPUInfo.strProcessorSerial), + "%04lX-%04lX-%04lX-%04lX-%04lX-%04lX", + sig1 >> 16, + sig1 & 0xFFFF, + sig3 >> 16, + sig3 & 0xFFFF, + sig2 >> 16, sig2 & 0xFFFF); /* Flawfinder: ignore */ + } + else + { + // If there's no serial number support we just put "No serial number" + snprintf( + CPUInfo.strProcessorSerial, + sizeof(CPUInfo.strProcessorSerial), + "No Processor Serial Number"); /* Flawfinder: ignore */ + } + + // Now we get the standard processor extensions + GetStandardProcessorExtensions(); + + // And finally the processor configuration (caches, TLBs, ...) and translate + // the data to readable strings + GetStandardProcessorConfiguration(); + TranslateProcessorConfiguration(); + + // At last... + return true; +#else + return FALSE; +#endif +} + +// bool CProcessor::AnalyzeAMDProcessor() +// ====================================== +// Private class function for analyzing an AMD processor +//////////////////////////////////////////////////////// +bool CProcessor::AnalyzeAMDProcessor() +{ +#if LL_WINDOWS + unsigned long eaxreg, ebxreg, ecxreg, edxreg; + + // First of all we check if the CPUID command is available + if (!CheckCPUIDPresence()) + return 0; + + // Now we get the CPUID standard level 0x00000001 + __asm + { + mov eax, 1 + cpuid + mov eaxreg, eax + mov ebxreg, ebx + mov edxreg, edx + } + + // Then we mask the model, family, stepping and type (AMD does not support brand id) + CPUInfo.uiStepping = eaxreg & 0xF; + CPUInfo.uiModel = (eaxreg >> 4) & 0xF; + CPUInfo.uiFamily = (eaxreg >> 8) & 0xF; + CPUInfo.uiType = (eaxreg >> 12) & 0x3; + + // After that, we translate the processor type (see CProcessor::AnalyzeIntelProcessor() + // for further comments on this) + switch (CPUInfo.uiType) + { + case 0: + strcpy(CPUInfo.strType, "Original OEM"); /* Flawfinder: ignore */ + strcpy(strCPUName, CPUInfo.strType); /* Flawfinder: ignore */ + strcat(strCPUName, " "); /*Flawfinder: ignore*/ + break; + case 1: + strcpy(CPUInfo.strType, "Overdrive"); /* Flawfinder: ignore */ + strcpy(strCPUName, CPUInfo.strType); /* Flawfinder: ignore */ + strcat(strCPUName, " "); /*Flawfinder: ignore*/ + break; + case 2: + strcpy(CPUInfo.strType, "Dual-capable"); /* Flawfinder: ignore */ + strcpy(strCPUName, CPUInfo.strType); /* Flawfinder: ignore */ + strcat(strCPUName, " "); /*Flawfinder: ignore*/ + break; + case 3: + strcpy(CPUInfo.strType, "Reserved"); /* Flawfinder: ignore */ + break; + default: + strcpy(CPUInfo.strType, "Unknown"); /* Flawfinder: ignore */ + break; + } + + // Now we check if the processor supports the brand id string extended CPUID level + if (CPUInfo.MaxSupportedExtendedLevel >= 0x80000004) + { + // If it supports the extended CPUID level 0x80000004 we read the data + char tmp[52]; /* Flawfinder: ignore */ + memset(tmp, 0, sizeof(tmp)); + __asm + { + mov eax, 0x80000002 + cpuid + mov dword ptr [tmp], eax + mov dword ptr [tmp+4], ebx + mov dword ptr [tmp+8], ecx + mov dword ptr [tmp+12], edx + mov eax, 0x80000003 + cpuid + mov dword ptr [tmp+16], eax + mov dword ptr [tmp+20], ebx + mov dword ptr [tmp+24], ecx + mov dword ptr [tmp+28], edx + mov eax, 0x80000004 + cpuid + mov dword ptr [tmp+32], eax + mov dword ptr [tmp+36], ebx + mov dword ptr [tmp+40], ecx + mov dword ptr [tmp+44], edx + } + // And copy it to the brand id string + strncpy(CPUInfo.strBrandID, tmp,sizeof(CPUInfo.strBrandID-1)); /* Flawfinder: ignore */ + CPUInfo.strBrandID[sizeof(CPUInfo.strBrandID-1)]='\0'; + } + else + { + // Or just tell there is no brand id string support + strcpy(CPUInfo.strBrandID, "Not supported"); /* Flawfinder: ignore */ + } + + // After that we translate the processor family + switch(CPUInfo.uiFamily) + { + case 4: // Family = 4: 486 (80486) or 5x86 (80486) processor family + switch (CPUInfo.uiModel) + { + case 3: // Thanks to AMD for this nice form of family + case 7: // detection.... *grmpf* + case 8: + case 9: + strcpy(CPUInfo.strFamily, "AMD 80486"); /* Flawfinder: ignore */ + break; + case 0xE: + case 0xF: + strcpy(CPUInfo.strFamily, "AMD 5x86"); /* Flawfinder: ignore */ + break; + default: + strcpy(CPUInfo.strFamily, "Unknown family"); /* Flawfinder: ignore */ + break; + } + break; + case 5: // Family = 5: K5 or K6 processor family + switch (CPUInfo.uiModel) + { + case 0: + case 1: + case 2: + case 3: + strcpy(CPUInfo.strFamily, "AMD K5"); /* Flawfinder: ignore */ + break; + case 6: + case 7: + case 8: + case 9: + strcpy(CPUInfo.strFamily, "AMD K6"); /* Flawfinder: ignore */ + break; + default: + strcpy(CPUInfo.strFamily, "Unknown family"); /* Flawfinder: ignore */ + break; + } + break; + case 6: // Family = 6: K7 (Athlon, ...) processor family + strcpy(CPUInfo.strFamily, "AMD K7"); /* Flawfinder: ignore */ + break; + default: // For security + strcpy(CPUInfo.strFamily, "Unknown family"); /* Flawfinder: ignore */ + break; + } + + // After the family detection we come to the specific processor model + // detection + switch (CPUInfo.uiFamily) + { + case 4: // Family = 4: 486 (80486) or 5x85 (80486) processor family + switch (CPUInfo.uiModel) + { + case 3: // Model = 3: 80486 DX2 + strcpy(CPUInfo.strModel, "AMD 80486 DX2"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD 80486 DX2", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 7: // Model = 7: 80486 write-back enhanced DX2 + strcpy(CPUInfo.strModel, "AMD 80486 write-back enhanced DX2"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD 80486 write-back enhanced DX2", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 8: // Model = 8: 80486 DX4 + strcpy(CPUInfo.strModel, "AMD 80486 DX4"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD 80486 DX4", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 9: // Model = 9: 80486 write-back enhanced DX4 + strcpy(CPUInfo.strModel, "AMD 80486 write-back enhanced DX4"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD 80486 write-back enhanced DX4", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 0xE: // Model = 0xE: 5x86 + strcpy(CPUInfo.strModel, "AMD 5x86"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD 5x86", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 0xF: // Model = 0xF: 5x86 write-back enhanced (oh my god.....) + strcpy(CPUInfo.strModel, "AMD 5x86 write-back enhanced"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD 5x86 write-back enhanced", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + default: // ... + strcpy(CPUInfo.strModel, "Unknown AMD 80486 or 5x86 model"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD 80486 or 5x86 (Unknown model)", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + } + break; + case 5: // Family = 5: K5 / K6 processor family + switch (CPUInfo.uiModel) + { + case 0: // Model = 0: K5 SSA 5 (Pentium Rating *ggg* 75, 90 and 100 Mhz) + strcpy(CPUInfo.strModel, "AMD K5 SSA5 (PR75, PR90, PR100)"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD K5 SSA5 (PR75, PR90, PR100)", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 1: // Model = 1: K5 5k86 (PR 120 and 133 MHz) + strcpy(CPUInfo.strModel, "AMD K5 5k86 (PR120, PR133)"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD K5 5k86 (PR120, PR133)", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 2: // Model = 2: K5 5k86 (PR 166 MHz) + strcpy(CPUInfo.strModel, "AMD K5 5k86 (PR166)"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD K5 5k86 (PR166)", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 3: // Model = 3: K5 5k86 (PR 200 MHz) + strcpy(CPUInfo.strModel, "AMD K5 5k86 (PR200)"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD K5 5k86 (PR200)", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 6: // Model = 6: K6 + strcpy(CPUInfo.strModel, "AMD K6 (0.30 micron)"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD K6 (0.30 micron)", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 7: // Model = 7: K6 (0.25 micron) + strcpy(CPUInfo.strModel, "AMD K6 (0.25 micron)"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD K6 (0.25 micron)", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 8: // Model = 8: K6-2 + strcpy(CPUInfo.strModel, "AMD K6-2"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD K6-2", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 9: // Model = 9: K6-III + strcpy(CPUInfo.strModel, "AMD K6-III"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD K6-III", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 0xD: // Model = 0xD: K6-2+ / K6-III+ + strcpy(CPUInfo.strModel, "AMD K6-2+ or K6-III+ (0.18 micron)"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD K6-2+ or K6-III+ (0.18 micron)", sizeof(strCPUName) - strlen(strCPUName) -1); + break; + default: // ... + strcpy(CPUInfo.strModel, "Unknown AMD K5 or K6 model"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD K5 or K6 (Unknown model)", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + } + break; + case 6: // Family = 6: K7 processor family (AMDs first good processors) + switch (CPUInfo.uiModel) + { + case 1: // Athlon + strcpy(CPUInfo.strModel, "AMD Athlon (0.25 micron)"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD Athlon (0.25 micron)", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 2: // Athlon (0.18 micron) + strcpy(CPUInfo.strModel, "AMD Athlon (0.18 micron)"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD Athlon (0.18 micron)", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 3: // Duron (Spitfire core) + strcpy(CPUInfo.strModel, "AMD Duron (Spitfire)"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD Duron (Spitfire core)", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 4: // Athlon (Thunderbird core) + strcpy(CPUInfo.strModel, "AMD Athlon (Thunderbird)"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD Athlon (Thunderbird core)", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 6: // Athlon MP / Mobile Athlon (Palomino core) + strcpy(CPUInfo.strModel, "AMD Athlon MP/Mobile Athlon (Palomino)"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD Athlon MP/Mobile Athlon (Palomino core)", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + case 7: // Mobile Duron (Morgan core) + strcpy(CPUInfo.strModel, "AMD Mobile Duron (Morgan)"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD Mobile Duron (Morgan core)", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + default: // ... + strcpy(CPUInfo.strModel, "Unknown AMD K7 model"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD K7 (Unknown model)", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + } + break; + default: // ... + strcpy(CPUInfo.strModel, "Unknown AMD model"); /* Flawfinder: ignore */ + strncat(strCPUName, "AMD (Unknown model)", sizeof(strCPUName) - strlen(strCPUName) -1); /* Flawfinder: ignore */ + break; + } + + // Now we read the standard processor extension that are stored in the same + // way the Intel standard extensions are + GetStandardProcessorExtensions(); + + // Then we check if theres an extended CPUID level support + if (CPUInfo.MaxSupportedExtendedLevel >= 0x80000001) + { + // If we can access the extended CPUID level 0x80000001 we get the + // edx register + __asm + { + mov eax, 0x80000001 + cpuid + mov edxreg, edx + } + + // Now we can mask some AMD specific cpu extensions + CPUInfo._Ext.EMMX_MultimediaExtensions = CheckBit(edxreg, 22); + CPUInfo._Ext.AA64_AMD64BitArchitecture = CheckBit(edxreg, 29); + CPUInfo._Ext._E3DNOW_InstructionExtensions = CheckBit(edxreg, 30); + CPUInfo._Ext._3DNOW_InstructionExtensions = CheckBit(edxreg, 31); + } + + // After that we check if the processor supports the ext. CPUID level + // 0x80000006 + if (CPUInfo.MaxSupportedExtendedLevel >= 0x80000006) + { + // If it's present, we read it out + __asm + { + mov eax, 0x80000005 + cpuid + mov eaxreg, eax + mov ebxreg, ebx + mov ecxreg, ecx + mov edxreg, edx + } + + // Then we mask the L1 Data TLB information + if ((ebxreg >> 16) && (eaxreg >> 16)) + { + CPUInfo._Data.bPresent = true; + strcpy(CPUInfo._Data.strPageSize, "4 KB / 2 MB / 4MB"); /*Flawfinder: ignore*/ + CPUInfo._Data.uiAssociativeWays = (eaxreg >> 24) & 0xFF; + CPUInfo._Data.uiEntries = (eaxreg >> 16) & 0xFF; + } + else if (eaxreg >> 16) + { + CPUInfo._Data.bPresent = true; + strcpy(CPUInfo._Data.strPageSize, "2 MB / 4MB"); /*Flawfinder: ignore*/ + CPUInfo._Data.uiAssociativeWays = (eaxreg >> 24) & 0xFF; + CPUInfo._Data.uiEntries = (eaxreg >> 16) & 0xFF; + } + else if (ebxreg >> 16) + { + CPUInfo._Data.bPresent = true; + strcpy(CPUInfo._Data.strPageSize, "4 KB"); /*Flawfinder: ignore*/ + CPUInfo._Data.uiAssociativeWays = (ebxreg >> 24) & 0xFF; + CPUInfo._Data.uiEntries = (ebxreg >> 16) & 0xFF; + } + if (CPUInfo._Data.uiAssociativeWays == 0xFF) + CPUInfo._Data.uiAssociativeWays = (unsigned int) -1; + + // Now the L1 Instruction/Code TLB information + if ((ebxreg & 0xFFFF) && (eaxreg & 0xFFFF)) + { + CPUInfo._Instruction.bPresent = true; + strcpy(CPUInfo._Instruction.strPageSize, "4 KB / 2 MB / 4MB"); /*Flawfinder: ignore*/ + CPUInfo._Instruction.uiAssociativeWays = (eaxreg >> 8) & 0xFF; + CPUInfo._Instruction.uiEntries = eaxreg & 0xFF; + } + else if (eaxreg & 0xFFFF) + { + CPUInfo._Instruction.bPresent = true; + strcpy(CPUInfo._Instruction.strPageSize, "2 MB / 4MB"); /*Flawfinder: ignore*/ + CPUInfo._Instruction.uiAssociativeWays = (eaxreg >> 8) & 0xFF; + CPUInfo._Instruction.uiEntries = eaxreg & 0xFF; + } + else if (ebxreg & 0xFFFF) + { + CPUInfo._Instruction.bPresent = true; + strcpy(CPUInfo._Instruction.strPageSize, "4 KB"); /*Flawfinder: ignore*/ + CPUInfo._Instruction.uiAssociativeWays = (ebxreg >> 8) & 0xFF; + CPUInfo._Instruction.uiEntries = ebxreg & 0xFF; + } + if (CPUInfo._Instruction.uiAssociativeWays == 0xFF) + CPUInfo._Instruction.uiAssociativeWays = (unsigned int) -1; + + // Then we read the L1 data cache information + if ((ecxreg >> 24) > 0) + { + CPUInfo._L1.Data.bPresent = true; + snprintf(CPUInfo._L1.Data.strSize, sizeof(CPUInfo._L1.Data.strSize), "%d KB", ecxreg >> 24); /*Flawfinder: ignore*/ + CPUInfo._L1.Data.uiAssociativeWays = (ecxreg >> 15) & 0xFF; + CPUInfo._L1.Data.uiLineSize = ecxreg & 0xFF; + } + // After that we read the L2 instruction/code cache information + if ((edxreg >> 24) > 0) + { + CPUInfo._L1.Instruction.bPresent = true; + snprintf(CPUInfo._L1.Instruction.strSize, sizeof(CPUInfo._L1.Instruction.strSize), "%d KB", edxreg >> 24); /*Flawfinder: ignore*/ + CPUInfo._L1.Instruction.uiAssociativeWays = (edxreg >> 15) & 0xFF; + CPUInfo._L1.Instruction.uiLineSize = edxreg & 0xFF; + } + + // Note: I'm not absolutely sure that the L1 page size code (the + // 'if/else if/else if' structs above) really detects the real page + // size for the TLB. Somebody should check it.... + + // Now we read the ext. CPUID level 0x80000006 + __asm + { + mov eax, 0x80000006 + cpuid + mov eaxreg, eax + mov ebxreg, ebx + mov ecxreg, ecx + } + + // We only mask the unified L2 cache masks (never heard of an + // L2 cache that is divided in data and code parts) + if (((ecxreg >> 12) & 0xF) > 0) + { + CPUInfo._L2.bPresent = true; + snprintf(CPUInfo._L2.strSize, sizeof(CPUInfo._L2.strSize), "%d KB", ecxreg >> 16); /*Flawfinder: ignore*/ + switch ((ecxreg >> 12) & 0xF) + { + case 1: + CPUInfo._L2.uiAssociativeWays = 1; + break; + case 2: + CPUInfo._L2.uiAssociativeWays = 2; + break; + case 4: + CPUInfo._L2.uiAssociativeWays = 4; + break; + case 6: + CPUInfo._L2.uiAssociativeWays = 8; + break; + case 8: + CPUInfo._L2.uiAssociativeWays = 16; + break; + case 0xF: + CPUInfo._L2.uiAssociativeWays = (unsigned int) -1; + break; + default: + CPUInfo._L2.uiAssociativeWays = 0; + break; + } + CPUInfo._L2.uiLineSize = ecxreg & 0xFF; + } + } + else + { + // If we could not detect the ext. CPUID level 0x80000006 we + // try to read the standard processor configuration. + GetStandardProcessorConfiguration(); + } + // After reading we translate the configuration to strings + TranslateProcessorConfiguration(); + + // And finally exit + return true; +#else + return FALSE; +#endif +} + +// bool CProcessor::AnalyzeUnknownProcessor() +// ========================================== +// Private class function to analyze an unknown (No Intel or AMD) processor +/////////////////////////////////////////////////////////////////////////// +bool CProcessor::AnalyzeUnknownProcessor() +{ +#if LL_WINDOWS + unsigned long eaxreg, ebxreg; + + // We check if the CPUID command is available + if (!CheckCPUIDPresence()) + return false; + + // First of all we read the standard CPUID level 0x00000001 + // This level should be available on every x86-processor clone + __asm + { + mov eax, 1 + cpuid + mov eaxreg, eax + mov ebxreg, ebx + } + // Then we mask the processor model, family, type and stepping + CPUInfo.uiStepping = eaxreg & 0xF; + CPUInfo.uiModel = (eaxreg >> 4) & 0xF; + CPUInfo.uiFamily = (eaxreg >> 8) & 0xF; + CPUInfo.uiType = (eaxreg >> 12) & 0x3; + + // To have complete information we also mask the brand id + CPUInfo.uiBrandID = ebxreg & 0xF; + + // Then we get the standard processor extensions + GetStandardProcessorExtensions(); + + // Now we mark everything we do not know as unknown + strcpy(strCPUName, "Unknown"); /*Flawfinder: ignore*/ + + strcpy(CPUInfo._Data.strTLB, "Unknown"); /*Flawfinder: ignore*/ + strcpy(CPUInfo._Instruction.strTLB, "Unknown"); /*Flawfinder: ignore*/ + + strcpy(CPUInfo._Trace.strCache, "Unknown"); /*Flawfinder: ignore*/ + strcpy(CPUInfo._L1.Data.strCache, "Unknown"); /*Flawfinder: ignore*/ + strcpy(CPUInfo._L1.Instruction.strCache, "Unknown"); /*Flawfinder: ignore*/ + strcpy(CPUInfo._L2.strCache, "Unknown"); /*Flawfinder: ignore*/ + strcpy(CPUInfo._L3.strCache, "Unknown"); /*Flawfinder: ignore*/ + + strcpy(CPUInfo.strProcessorSerial, "Unknown / Not supported"); /*Flawfinder: ignore*/ + + // For the family, model and brand id we can only print the numeric value + snprintf(CPUInfo.strBrandID, sizeof(CPUInfo.strBrandID), "Brand-ID number %d", CPUInfo.uiBrandID); /*Flawfinder: ignore*/ + snprintf(CPUInfo.strFamily, sizeof(CPUInfo.strFamily), "Family number %d", CPUInfo.uiFamily); /*Flawfinder: ignore*/ + snprintf(CPUInfo.strModel, sizeof(CPUInfo.strModel), "Model number %d", CPUInfo.uiModel); /*Flawfinder: ignore*/ + + // Nevertheless we can determine the processor type + switch (CPUInfo.uiType) + { + case 0: + strcpy(CPUInfo.strType, "Original OEM"); /*Flawfinder: ignore*/ + break; + case 1: + strcpy(CPUInfo.strType, "Overdrive"); /*Flawfinder: ignore*/ + break; + case 2: + strcpy(CPUInfo.strType, "Dual-capable"); /*Flawfinder: ignore*/ + break; + case 3: + strcpy(CPUInfo.strType, "Reserved"); /*Flawfinder: ignore*/ + break; + default: + strcpy(CPUInfo.strType, "Unknown"); /*Flawfinder: ignore*/ + break; + } + + // And thats it + return true; +#else + return FALSE; +#endif +} + +// bool CProcessor::CheckCPUIDPresence() +// ===================================== +// This function checks if the CPUID command is available on the current +// processor +//////////////////////////////////////////////////////////////////////// +bool CProcessor::CheckCPUIDPresence() +{ +#if LL_WINDOWS + unsigned long BitChanged; + + // We've to check if we can toggle the flag register bit 21 + // If we can't the processor does not support the CPUID command + __asm + { + pushfd + pop eax + mov ebx, eax + xor eax, 0x00200000 + push eax + popfd + pushfd + pop eax + xor eax,ebx + mov BitChanged, eax + } + + return ((BitChanged) ? true : false); +#else + return FALSE; +#endif +} + +// void CProcessor::DecodeProcessorConfiguration(unsigned int cfg) +// =============================================================== +// This function (or switch ?!) just translates a one-byte processor configuration +// byte to understandable values +////////////////////////////////////////////////////////////////////////////////// +void CProcessor::DecodeProcessorConfiguration(unsigned int cfg) +{ + // First we ensure that there's only one single byte + cfg &= 0xFF; + + // Then we do a big switch + switch(cfg) + { + case 0: // cfg = 0: Unused + break; + case 0x1: // cfg = 0x1: code TLB present, 4 KB pages, 4 ways, 32 entries + CPUInfo._Instruction.bPresent = true; + strcpy(CPUInfo._Instruction.strPageSize, "4 KB"); /*Flawfinder: ignore*/ + CPUInfo._Instruction.uiAssociativeWays = 4; + CPUInfo._Instruction.uiEntries = 32; + break; + case 0x2: // cfg = 0x2: code TLB present, 4 MB pages, fully associative, 2 entries + CPUInfo._Instruction.bPresent = true; + strcpy(CPUInfo._Instruction.strPageSize, "4 MB"); /*Flawfinder: ignore*/ + CPUInfo._Instruction.uiAssociativeWays = 4; + CPUInfo._Instruction.uiEntries = 2; + break; + case 0x3: // cfg = 0x3: data TLB present, 4 KB pages, 4 ways, 64 entries + CPUInfo._Data.bPresent = true; + strcpy(CPUInfo._Data.strPageSize, "4 KB"); /*Flawfinder: ignore*/ + CPUInfo._Data.uiAssociativeWays = 4; + CPUInfo._Data.uiEntries = 64; + break; + case 0x4: // cfg = 0x4: data TLB present, 4 MB pages, 4 ways, 8 entries + CPUInfo._Data.bPresent = true; + strcpy(CPUInfo._Data.strPageSize, "4 MB"); /*Flawfinder: ignore*/ + CPUInfo._Data.uiAssociativeWays = 4; + CPUInfo._Data.uiEntries = 8; + break; + case 0x6: // cfg = 0x6: code L1 cache present, 8 KB, 4 ways, 32 byte lines + CPUInfo._L1.Instruction.bPresent = true; + strcpy(CPUInfo._L1.Instruction.strSize, "8 KB"); /*Flawfinder: ignore*/ + CPUInfo._L1.Instruction.uiAssociativeWays = 4; + CPUInfo._L1.Instruction.uiLineSize = 32; + break; + case 0x8: // cfg = 0x8: code L1 cache present, 16 KB, 4 ways, 32 byte lines + CPUInfo._L1.Instruction.bPresent = true; + strcpy(CPUInfo._L1.Instruction.strSize, "16 KB"); /*Flawfinder: ignore*/ + CPUInfo._L1.Instruction.uiAssociativeWays = 4; + CPUInfo._L1.Instruction.uiLineSize = 32; + break; + case 0xA: // cfg = 0xA: data L1 cache present, 8 KB, 2 ways, 32 byte lines + CPUInfo._L1.Data.bPresent = true; + strcpy(CPUInfo._L1.Data.strSize, "8 KB"); /*Flawfinder: ignore*/ + CPUInfo._L1.Data.uiAssociativeWays = 2; + CPUInfo._L1.Data.uiLineSize = 32; + break; + case 0xC: // cfg = 0xC: data L1 cache present, 16 KB, 4 ways, 32 byte lines + CPUInfo._L1.Data.bPresent = true; + strcpy(CPUInfo._L1.Data.strSize, "16 KB"); /*Flawfinder: ignore*/ + CPUInfo._L1.Data.uiAssociativeWays = 4; + CPUInfo._L1.Data.uiLineSize = 32; + break; + case 0x22: // cfg = 0x22: code and data L3 cache present, 512 KB, 4 ways, 64 byte lines, sectored + CPUInfo._L3.bPresent = true; + strcpy(CPUInfo._L3.strSize, "512 KB"); /*Flawfinder: ignore*/ + CPUInfo._L3.uiAssociativeWays = 4; + CPUInfo._L3.uiLineSize = 64; + CPUInfo._L3.bSectored = true; + break; + case 0x23: // cfg = 0x23: code and data L3 cache present, 1024 KB, 8 ways, 64 byte lines, sectored + CPUInfo._L3.bPresent = true; + strcpy(CPUInfo._L3.strSize, "1024 KB"); /*Flawfinder: ignore*/ + CPUInfo._L3.uiAssociativeWays = 8; + CPUInfo._L3.uiLineSize = 64; + CPUInfo._L3.bSectored = true; + break; + case 0x25: // cfg = 0x25: code and data L3 cache present, 2048 KB, 8 ways, 64 byte lines, sectored + CPUInfo._L3.bPresent = true; + strcpy(CPUInfo._L3.strSize, "2048 KB"); /*Flawfinder: ignore*/ + CPUInfo._L3.uiAssociativeWays = 8; + CPUInfo._L3.uiLineSize = 64; + CPUInfo._L3.bSectored = true; + break; + case 0x29: // cfg = 0x29: code and data L3 cache present, 4096 KB, 8 ways, 64 byte lines, sectored + CPUInfo._L3.bPresent = true; + strcpy(CPUInfo._L3.strSize, "4096 KB"); /*Flawfinder: ignore*/ + CPUInfo._L3.uiAssociativeWays = 8; + CPUInfo._L3.uiLineSize = 64; + CPUInfo._L3.bSectored = true; + break; + case 0x40: // cfg = 0x40: no integrated L2 cache (P6 core) or L3 cache (P4 core) + break; + case 0x41: // cfg = 0x41: code and data L2 cache present, 128 KB, 4 ways, 32 byte lines + CPUInfo._L2.bPresent = true; + strcpy(CPUInfo._L2.strSize, "128 KB"); /*Flawfinder: ignore*/ + CPUInfo._L2.uiAssociativeWays = 4; + CPUInfo._L2.uiLineSize = 32; + break; + case 0x42: // cfg = 0x42: code and data L2 cache present, 256 KB, 4 ways, 32 byte lines + CPUInfo._L2.bPresent = true; + strcpy(CPUInfo._L2.strSize, "256 KB"); /*Flawfinder: ignore*/ + CPUInfo._L2.uiAssociativeWays = 4; + CPUInfo._L2.uiLineSize = 32; + break; + case 0x43: // cfg = 0x43: code and data L2 cache present, 512 KB, 4 ways, 32 byte lines + CPUInfo._L2.bPresent = true; + strcpy(CPUInfo._L2.strSize, "512 KB"); /* Flawfinder: ignore */ + CPUInfo._L2.uiAssociativeWays = 4; + CPUInfo._L2.uiLineSize = 32; + break; + case 0x44: // cfg = 0x44: code and data L2 cache present, 1024 KB, 4 ways, 32 byte lines + CPUInfo._L2.bPresent = true; + strcpy(CPUInfo._L2.strSize, "1 MB"); /* Flawfinder: ignore */ + CPUInfo._L2.uiAssociativeWays = 4; + CPUInfo._L2.uiLineSize = 32; + break; + case 0x45: // cfg = 0x45: code and data L2 cache present, 2048 KB, 4 ways, 32 byte lines + CPUInfo._L2.bPresent = true; + strcpy(CPUInfo._L2.strSize, "2 MB"); /* Flawfinder: ignore */ + CPUInfo._L2.uiAssociativeWays = 4; + CPUInfo._L2.uiLineSize = 32; + break; + case 0x50: // cfg = 0x50: code TLB present, 4 KB / 4 MB / 2 MB pages, fully associative, 64 entries + CPUInfo._Instruction.bPresent = true; + strcpy(CPUInfo._Instruction.strPageSize, "4 KB / 2 MB / 4 MB"); /* Flawfinder: ignore */ + CPUInfo._Instruction.uiAssociativeWays = (unsigned int) -1; + CPUInfo._Instruction.uiEntries = 64; + break; + case 0x51: // cfg = 0x51: code TLB present, 4 KB / 4 MB / 2 MB pages, fully associative, 128 entries + CPUInfo._Instruction.bPresent = true; + strcpy(CPUInfo._Instruction.strPageSize, "4 KB / 2 MB / 4 MB"); /* Flawfinder: ignore */ + CPUInfo._Instruction.uiAssociativeWays = (unsigned int) -1; + CPUInfo._Instruction.uiEntries = 128; + break; + case 0x52: // cfg = 0x52: code TLB present, 4 KB / 4 MB / 2 MB pages, fully associative, 256 entries + CPUInfo._Instruction.bPresent = true; + strcpy(CPUInfo._Instruction.strPageSize, "4 KB / 2 MB / 4 MB"); /* Flawfinder: ignore */ + CPUInfo._Instruction.uiAssociativeWays = (unsigned int) -1; + CPUInfo._Instruction.uiEntries = 256; + break; + case 0x5B: // cfg = 0x5B: data TLB present, 4 KB / 4 MB pages, fully associative, 64 entries + CPUInfo._Data.bPresent = true; + strcpy(CPUInfo._Data.strPageSize, "4 KB / 4 MB"); /* Flawfinder: ignore */ + CPUInfo._Data.uiAssociativeWays = (unsigned int) -1; + CPUInfo._Data.uiEntries = 64; + break; + case 0x5C: // cfg = 0x5C: data TLB present, 4 KB / 4 MB pages, fully associative, 128 entries + CPUInfo._Data.bPresent = true; + strcpy(CPUInfo._Data.strPageSize, "4 KB / 4 MB"); /* Flawfinder: ignore */ + CPUInfo._Data.uiAssociativeWays = (unsigned int) -1; + CPUInfo._Data.uiEntries = 128; + break; + case 0x5d: // cfg = 0x5D: data TLB present, 4 KB / 4 MB pages, fully associative, 256 entries + CPUInfo._Data.bPresent = true; + strcpy(CPUInfo._Data.strPageSize, "4 KB / 4 MB"); /* Flawfinder: ignore */ + CPUInfo._Data.uiAssociativeWays = (unsigned int) -1; + CPUInfo._Data.uiEntries = 256; + break; + case 0x66: // cfg = 0x66: data L1 cache present, 8 KB, 4 ways, 64 byte lines, sectored + CPUInfo._L1.Data.bPresent = true; + strcpy(CPUInfo._L1.Data.strSize, "8 KB"); /* Flawfinder: ignore */ + CPUInfo._L1.Data.uiAssociativeWays = 4; + CPUInfo._L1.Data.uiLineSize = 64; + break; + case 0x67: // cfg = 0x67: data L1 cache present, 16 KB, 4 ways, 64 byte lines, sectored + CPUInfo._L1.Data.bPresent = true; + strcpy(CPUInfo._L1.Data.strSize, "16 KB"); /* Flawfinder: ignore */ + CPUInfo._L1.Data.uiAssociativeWays = 4; + CPUInfo._L1.Data.uiLineSize = 64; + break; + case 0x68: // cfg = 0x68: data L1 cache present, 32 KB, 4 ways, 64 byte lines, sectored + CPUInfo._L1.Data.bPresent = true; + strcpy(CPUInfo._L1.Data.strSize, "32 KB"); /* Flawfinder: ignore */ + CPUInfo._L1.Data.uiAssociativeWays = 4; + CPUInfo._L1.Data.uiLineSize = 64; + break; + case 0x70: // cfg = 0x70: trace L1 cache present, 12 KµOPs, 4 ways + CPUInfo._Trace.bPresent = true; + strcpy(CPUInfo._Trace.strSize, "12 K-micro-ops"); /* Flawfinder: ignore */ + CPUInfo._Trace.uiAssociativeWays = 4; + break; + case 0x71: // cfg = 0x71: trace L1 cache present, 16 KµOPs, 4 ways + CPUInfo._Trace.bPresent = true; + strcpy(CPUInfo._Trace.strSize, "16 K-micro-ops"); /* Flawfinder: ignore */ + CPUInfo._Trace.uiAssociativeWays = 4; + break; + case 0x72: // cfg = 0x72: trace L1 cache present, 32 KµOPs, 4 ways + CPUInfo._Trace.bPresent = true; + strcpy(CPUInfo._Trace.strSize, "32 K-micro-ops"); /* Flawfinder: ignore */ + CPUInfo._Trace.uiAssociativeWays = 4; + break; + case 0x79: // cfg = 0x79: code and data L2 cache present, 128 KB, 8 ways, 64 byte lines, sectored + CPUInfo._L2.bPresent = true; + strcpy(CPUInfo._L2.strSize, "128 KB"); /* Flawfinder: ignore */ + CPUInfo._L2.uiAssociativeWays = 8; + CPUInfo._L2.uiLineSize = 64; + CPUInfo._L2.bSectored = true; + break; + case 0x7A: // cfg = 0x7A: code and data L2 cache present, 256 KB, 8 ways, 64 byte lines, sectored + CPUInfo._L2.bPresent = true; + strcpy(CPUInfo._L2.strSize, "256 KB"); /* Flawfinder: ignore */ + CPUInfo._L2.uiAssociativeWays = 8; + CPUInfo._L2.uiLineSize = 64; + CPUInfo._L2.bSectored = true; + break; + case 0x7B: // cfg = 0x7B: code and data L2 cache present, 512 KB, 8 ways, 64 byte lines, sectored + CPUInfo._L2.bPresent = true; + strcpy(CPUInfo._L2.strSize, "512 KB"); /* Flawfinder: ignore */ + CPUInfo._L2.uiAssociativeWays = 8; + CPUInfo._L2.uiLineSize = 64; + CPUInfo._L2.bSectored = true; + break; + case 0x7C: // cfg = 0x7C: code and data L2 cache present, 1024 KB, 8 ways, 64 byte lines, sectored + CPUInfo._L2.bPresent = true; + strcpy(CPUInfo._L2.strSize, "1 MB"); /* Flawfinder: ignore */ + CPUInfo._L2.uiAssociativeWays = 8; + CPUInfo._L2.uiLineSize = 64; + CPUInfo._L2.bSectored = true; + break; + case 0x81: // cfg = 0x81: code and data L2 cache present, 128 KB, 8 ways, 32 byte lines + CPUInfo._L2.bPresent = true; + strcpy(CPUInfo._L2.strSize, "128 KB"); /* Flawfinder: ignore */ + CPUInfo._L2.uiAssociativeWays = 8; + CPUInfo._L2.uiLineSize = 32; + break; + case 0x82: // cfg = 0x82: code and data L2 cache present, 256 KB, 8 ways, 32 byte lines + CPUInfo._L2.bPresent = true; + strcpy(CPUInfo._L2.strSize, "256 KB"); /* Flawfinder: ignore */ + CPUInfo._L2.uiAssociativeWays = 8; + CPUInfo._L2.uiLineSize = 32; + break; + case 0x83: // cfg = 0x83: code and data L2 cache present, 512 KB, 8 ways, 32 byte lines + CPUInfo._L2.bPresent = true; + strcpy(CPUInfo._L2.strSize, "512 KB"); /* Flawfinder: ignore */ + CPUInfo._L2.uiAssociativeWays = 8; + CPUInfo._L2.uiLineSize = 32; + break; + case 0x84: // cfg = 0x84: code and data L2 cache present, 1024 KB, 8 ways, 32 byte lines + CPUInfo._L2.bPresent = true; + strcpy(CPUInfo._L2.strSize, "1 MB"); /* Flawfinder: ignore */ + CPUInfo._L2.uiAssociativeWays = 8; + CPUInfo._L2.uiLineSize = 32; + break; + case 0x85: // cfg = 0x85: code and data L2 cache present, 2048 KB, 8 ways, 32 byte lines + CPUInfo._L2.bPresent = true; + strcpy(CPUInfo._L2.strSize, "2 MB"); /* Flawfinder: ignore */ + CPUInfo._L2.uiAssociativeWays = 8; + CPUInfo._L2.uiLineSize = 32; + break; + } +} + +FORCEINLINE static char *TranslateAssociativeWays(unsigned int uiWays, char *buf) +{ + // We define 0xFFFFFFFF (= -1) as fully associative + if (uiWays == ((unsigned int) -1)) + strcpy(buf, "fully associative"); /* Flawfinder: ignore */ + else + { + if (uiWays == 1) // A one way associative cache is just direct mapped + strcpy(buf, "direct mapped"); /* Flawfinder: ignore */ + else if (uiWays == 0) // This should not happen... + strcpy(buf, "unknown associative ways"); /* Flawfinder: ignore */ + else // The x-way associative cache + sprintf(buf, "%d ways associative", uiWays); /* Flawfinder: ignore */ + } + // To ease the function use we return the buffer + return buf; +} +FORCEINLINE static void TranslateTLB(ProcessorTLB *tlb) +{ + char buf[64]; /* Flawfinder: ignore */ + + // We just check if the TLB is present + if (tlb->bPresent) + snprintf(tlb->strTLB,sizeof(tlb->strTLB), "%s page size, %s, %d entries", tlb->strPageSize, TranslateAssociativeWays(tlb->uiAssociativeWays, buf), tlb->uiEntries); /* Flawfinder: ignore */ + else + strcpy(tlb->strTLB, "Not present"); /* Flawfinder: ignore */ +} +FORCEINLINE static void TranslateCache(ProcessorCache *cache) +{ + char buf[64]; /* Flawfinder: ignore */ + + // We just check if the cache is present + if (cache->bPresent) + { + // If present we construct the string + snprintf(cache->strCache, sizeof(cache->strCache), "%s cache size, %s, %d bytes line size", cache->strSize, TranslateAssociativeWays(cache->uiAssociativeWays, buf), cache->uiLineSize); /* Flawfinder: ignore */ + if (cache->bSectored) + strncat(cache->strCache, ", sectored", sizeof(cache->strCache)-strlen(cache->strCache)-1); /* Flawfinder: ignore */ + } + else + { + // Else we just say "Not present" + strcpy(cache->strCache, "Not present"); /* Flawfinder: ignore */ + } +} + +// void CProcessor::TranslateProcessorConfiguration() +// ================================================== +// Private class function to translate the processor configuration values +// to strings +///////////////////////////////////////////////////////////////////////// +void CProcessor::TranslateProcessorConfiguration() +{ + // We just call the small functions defined above + TranslateTLB(&CPUInfo._Data); + TranslateTLB(&CPUInfo._Instruction); + + TranslateCache(&CPUInfo._Trace); + + TranslateCache(&CPUInfo._L1.Instruction); + TranslateCache(&CPUInfo._L1.Data); + TranslateCache(&CPUInfo._L2); + TranslateCache(&CPUInfo._L3); +} + +// void CProcessor::GetStandardProcessorConfiguration() +// ==================================================== +// Private class function to read the standard processor configuration +////////////////////////////////////////////////////////////////////// +void CProcessor::GetStandardProcessorConfiguration() +{ +#if LL_WINDOWS + unsigned long eaxreg, ebxreg, ecxreg, edxreg; + + // We check if the CPUID function is available + if (!CheckCPUIDPresence()) + return; + + // First we check if the processor supports the standard + // CPUID level 0x00000002 + if (CPUInfo.MaxSupportedLevel >= 2) + { + // Now we go read the std. CPUID level 0x00000002 the first time + unsigned long count, num = 255; + for (count = 0; count < num; count++) + { + __asm + { + mov eax, 2 + cpuid + mov eaxreg, eax + mov ebxreg, ebx + mov ecxreg, ecx + mov edxreg, edx + } + // We have to repeat this reading for 'num' times + num = eaxreg & 0xFF; + + // Then we call the big decode switch function + DecodeProcessorConfiguration(eaxreg >> 8); + DecodeProcessorConfiguration(eaxreg >> 16); + DecodeProcessorConfiguration(eaxreg >> 24); + + // If ebx contains additional data we also decode it + if ((ebxreg & 0x80000000) == 0) + { + DecodeProcessorConfiguration(ebxreg); + DecodeProcessorConfiguration(ebxreg >> 8); + DecodeProcessorConfiguration(ebxreg >> 16); + DecodeProcessorConfiguration(ebxreg >> 24); + } + // And also the ecx register + if ((ecxreg & 0x80000000) == 0) + { + DecodeProcessorConfiguration(ecxreg); + DecodeProcessorConfiguration(ecxreg >> 8); + DecodeProcessorConfiguration(ecxreg >> 16); + DecodeProcessorConfiguration(ecxreg >> 24); + } + // At last the edx processor register + if ((edxreg & 0x80000000) == 0) + { + DecodeProcessorConfiguration(edxreg); + DecodeProcessorConfiguration(edxreg >> 8); + DecodeProcessorConfiguration(edxreg >> 16); + DecodeProcessorConfiguration(edxreg >> 24); + } + } + } +#endif +} + +// void CProcessor::GetStandardProcessorExtensions() +// ================================================= +// Private class function to read the standard processor extensions +/////////////////////////////////////////////////////////////////// +void CProcessor::GetStandardProcessorExtensions() +{ +#if LL_WINDOWS + unsigned long ebxreg, edxreg; + + // We check if the CPUID command is available + if (!CheckCPUIDPresence()) + return; + // We just get the standard CPUID level 0x00000001 which should be + // available on every x86 processor + __asm + { + mov eax, 1 + cpuid + mov ebxreg, ebx + mov edxreg, edx + } + + // Then we mask some bits + CPUInfo._Ext.FPU_FloatingPointUnit = CheckBit(edxreg, 0); + CPUInfo._Ext.VME_Virtual8086ModeEnhancements = CheckBit(edxreg, 1); + CPUInfo._Ext.DE_DebuggingExtensions = CheckBit(edxreg, 2); + CPUInfo._Ext.PSE_PageSizeExtensions = CheckBit(edxreg, 3); + CPUInfo._Ext.TSC_TimeStampCounter = CheckBit(edxreg, 4); + CPUInfo._Ext.MSR_ModelSpecificRegisters = CheckBit(edxreg, 5); + CPUInfo._Ext.PAE_PhysicalAddressExtension = CheckBit(edxreg, 6); + CPUInfo._Ext.MCE_MachineCheckException = CheckBit(edxreg, 7); + CPUInfo._Ext.CX8_COMPXCHG8B_Instruction = CheckBit(edxreg, 8); + CPUInfo._Ext.APIC_AdvancedProgrammableInterruptController = CheckBit(edxreg, 9); + CPUInfo._Ext.APIC_ID = (ebxreg >> 24) & 0xFF; + CPUInfo._Ext.SEP_FastSystemCall = CheckBit(edxreg, 11); + CPUInfo._Ext.MTRR_MemoryTypeRangeRegisters = CheckBit(edxreg, 12); + CPUInfo._Ext.PGE_PTE_GlobalFlag = CheckBit(edxreg, 13); + CPUInfo._Ext.MCA_MachineCheckArchitecture = CheckBit(edxreg, 14); + CPUInfo._Ext.CMOV_ConditionalMoveAndCompareInstructions = CheckBit(edxreg, 15); + CPUInfo._Ext.FGPAT_PageAttributeTable = CheckBit(edxreg, 16); + CPUInfo._Ext.PSE36_36bitPageSizeExtension = CheckBit(edxreg, 17); + CPUInfo._Ext.PN_ProcessorSerialNumber = CheckBit(edxreg, 18); + CPUInfo._Ext.CLFSH_CFLUSH_Instruction = CheckBit(edxreg, 19); + CPUInfo._Ext.CLFLUSH_InstructionCacheLineSize = (ebxreg >> 8) & 0xFF; + CPUInfo._Ext.DS_DebugStore = CheckBit(edxreg, 21); + CPUInfo._Ext.ACPI_ThermalMonitorAndClockControl = CheckBit(edxreg, 22); + CPUInfo._Ext.MMX_MultimediaExtensions = CheckBit(edxreg, 23); + CPUInfo._Ext.FXSR_FastStreamingSIMD_ExtensionsSaveRestore = CheckBit(edxreg, 24); + CPUInfo._Ext.SSE_StreamingSIMD_Extensions = CheckBit(edxreg, 25); + CPUInfo._Ext.SSE2_StreamingSIMD2_Extensions = CheckBit(edxreg, 26); + CPUInfo._Ext.SS_SelfSnoop = CheckBit(edxreg, 27); + CPUInfo._Ext.HT_HyperThreading = CheckBit(edxreg, 28); + CPUInfo._Ext.HT_HyterThreadingSiblings = (ebxreg >> 16) & 0xFF; + CPUInfo._Ext.TM_ThermalMonitor = CheckBit(edxreg, 29); + CPUInfo._Ext.IA64_Intel64BitArchitecture = CheckBit(edxreg, 30); +#endif +} + +// const ProcessorInfo *CProcessor::GetCPUInfo() +// ============================================= +// Calls all the other detection function to create an detailed +// processor information +/////////////////////////////////////////////////////////////// +const ProcessorInfo *CProcessor::GetCPUInfo() +{ +#if LL_WINDOWS + unsigned long eaxreg, ebxreg, ecxreg, edxreg; + + // First of all we check if the CPUID command is available + if (!CheckCPUIDPresence()) + return NULL; + + // We read the standard CPUID level 0x00000000 which should + // be available on every x86 processor + __asm + { + mov eax, 0 + cpuid + mov eaxreg, eax + mov ebxreg, ebx + mov edxreg, edx + mov ecxreg, ecx + } + // Then we connect the single register values to the vendor string + *((unsigned long *) CPUInfo.strVendor) = ebxreg; + *((unsigned long *) (CPUInfo.strVendor+4)) = edxreg; + *((unsigned long *) (CPUInfo.strVendor+8)) = ecxreg; + + // We can also read the max. supported standard CPUID level + CPUInfo.MaxSupportedLevel = eaxreg & 0xFFFF; + + // Then we read the ext. CPUID level 0x80000000 + __asm + { + mov eax, 0x80000000 + cpuid + mov eaxreg, eax + } + // ...to check the max. supportted extended CPUID level + CPUInfo.MaxSupportedExtendedLevel = eaxreg; + + // Then we switch to the specific processor vendors + switch (ebxreg) + { + case 0x756E6547: // GenuineIntel + AnalyzeIntelProcessor(); + break; + case 0x68747541: // AuthenticAMD + AnalyzeAMDProcessor(); + break; + case 0x69727943: // CyrixInstead + // I really do not know anyone owning such a piece of crab + // So we analyze it as an unknown processor *ggggg* + default: + AnalyzeUnknownProcessor(); + break; + } + +#endif + // After all we return the class CPUInfo member var + return (&CPUInfo); +} + +#else +// LL_DARWIN + +#include +#include + +static char *TranslateAssociativeWays(unsigned int uiWays, char *buf) +{ + // We define 0xFFFFFFFF (= -1) as fully associative + if (uiWays == ((unsigned int) -1)) + strcpy(buf, "fully associative"); /* Flawfinder: ignore */ + else + { + if (uiWays == 1) // A one way associative cache is just direct mapped + strcpy(buf, "direct mapped"); /* Flawfinder: ignore */ + else if (uiWays == 0) // This should not happen... + strcpy(buf, "unknown associative ways"); /* Flawfinder: ignore */ + else // The x-way associative cache + sprintf(buf, "%d ways associative", uiWays); /* Flawfinder: ignore */ + } + // To ease the function use we return the buffer + return buf; +} +static void TranslateTLB(ProcessorTLB *tlb) +{ + char buf[64]; /* Flawfinder: ignore */ + + // We just check if the TLB is present + if (tlb->bPresent) + snprintf(tlb->strTLB, sizeof(tlb->strTLB), "%s page size, %s, %d entries", tlb->strPageSize, TranslateAssociativeWays(tlb->uiAssociativeWays, buf), tlb->uiEntries); /* Flawfinder: ignore */ + else + strcpy(tlb->strTLB, "Not present"); /* Flawfinder: ignore */ +} +static void TranslateCache(ProcessorCache *cache) +{ + char buf[64]; /* Flawfinder: ignore */ + + // We just check if the cache is present + if (cache->bPresent) + { + // If present we construct the string + snprintf(cache->strCache,sizeof(cache->strCache), "%s cache size, %s, %d bytes line size", cache->strSize, TranslateAssociativeWays(cache->uiAssociativeWays, buf), cache->uiLineSize); /* Flawfinder: ignore */ + if (cache->bSectored) + strncat(cache->strCache, ", sectored", sizeof(cache->strCache)-strlen(cache->strCache)-1); /* Flawfinder: ignore */ + } + else + { + // Else we just say "Not present" + strcpy(cache->strCache, "Not present"); /* Flawfinder: ignore */ + } +} + +// void CProcessor::TranslateProcessorConfiguration() +// ================================================== +// Private class function to translate the processor configuration values +// to strings +///////////////////////////////////////////////////////////////////////// +void CProcessor::TranslateProcessorConfiguration() +{ + // We just call the small functions defined above + TranslateTLB(&CPUInfo._Data); + TranslateTLB(&CPUInfo._Instruction); + + TranslateCache(&CPUInfo._Trace); + + TranslateCache(&CPUInfo._L1.Instruction); + TranslateCache(&CPUInfo._L1.Data); + TranslateCache(&CPUInfo._L2); + TranslateCache(&CPUInfo._L3); +} + +// CProcessor::CProcessor +// ====================== +// Class constructor: +///////////////////////// +CProcessor::CProcessor() +{ + uqwFrequency = 0; + memset(&CPUInfo, 0, sizeof(CPUInfo)); +} + +// unsigned __int64 CProcessor::GetCPUFrequency(unsigned int uiMeasureMSecs) +// ========================================================================= +// Function to query the current CPU frequency +//////////////////////////////////////////////////////////////////////////// +F64 CProcessor::GetCPUFrequency(unsigned int /*uiMeasureMSecs*/) +{ + U64 frequency = 0; + size_t len = sizeof(frequency); + + if(sysctlbyname("hw.cpufrequency", &frequency, &len, NULL, 0) == 0) + { + uqwFrequency = (F64)frequency; + } + + return uqwFrequency; +} + +static bool hasFeature(const char *name) +{ + bool result = false; + int val = 0; + size_t len = sizeof(val); + + if(sysctlbyname(name, &val, &len, NULL, 0) == 0) + { + if(val != 0) + result = true; + } + + return result; +} + +// const ProcessorInfo *CProcessor::GetCPUInfo() +// ============================================= +// Calls all the other detection function to create an detailed +// processor information +/////////////////////////////////////////////////////////////// +const ProcessorInfo *CProcessor::GetCPUInfo() +{ + int pagesize = 0; + int cachelinesize = 0; + int l1icachesize = 0; + int l1dcachesize = 0; + int l2settings = 0; + int l2cachesize = 0; + int l3settings = 0; + int l3cachesize = 0; + int ncpu = 0; + int cpusubtype = 0; + + // sysctl knows all. + int mib[2]; + size_t len; + mib[0] = CTL_HW; + + mib[1] = HW_PAGESIZE; + len = sizeof(pagesize); + sysctl(mib, 2, &pagesize, &len, NULL, 0); + + mib[1] = HW_CACHELINE; + len = sizeof(cachelinesize); + sysctl(mib, 2, &cachelinesize, &len, NULL, 0); + + mib[1] = HW_L1ICACHESIZE; + len = sizeof(l1icachesize); + sysctl(mib, 2, &l1icachesize, &len, NULL, 0); + + mib[1] = HW_L1DCACHESIZE; + len = sizeof(l1dcachesize); + sysctl(mib, 2, &l1dcachesize, &len, NULL, 0); + + mib[1] = HW_L2SETTINGS; + len = sizeof(l2settings); + sysctl(mib, 2, &l2settings, &len, NULL, 0); + + mib[1] = HW_L2CACHESIZE; + len = sizeof(l2cachesize); + sysctl(mib, 2, &l2cachesize, &len, NULL, 0); + + mib[1] = HW_L3SETTINGS; + len = sizeof(l3settings); + sysctl(mib, 2, &l3settings, &len, NULL, 0); + + mib[1] = HW_L3CACHESIZE; + len = sizeof(l3cachesize); + sysctl(mib, 2, &l3cachesize, &len, NULL, 0); + + mib[1] = HW_NCPU; + len = sizeof(ncpu); + sysctl(mib, 2, &ncpu, &len, NULL, 0); + + sysctlbyname("hw.cpusubtype", &cpusubtype, &len, NULL, 0); + + strCPUName[0] = 0; + + if((ncpu == 0) || (ncpu == 1)) + { + // Uhhh... + } + else if(ncpu == 2) + { + strncat(strCPUName, "Dual ", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + } + else + { + snprintf(strCPUName, sizeof(strCPUName), "%d x ", ncpu); /* Flawfinder: ignore */ + } + +#if __ppc__ + switch(cpusubtype) + { + case CPU_SUBTYPE_POWERPC_601:// ((cpu_subtype_t) 1) + strncat(strCPUName, "PowerPC 601", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + strncat(CPUInfo.strFamily, "PowerPC", sizeof(CPUInfo.strFamily)-strlen(CPUInfo.strFamily)-1); /* Flawfinder: ignore */ + + break; + case CPU_SUBTYPE_POWERPC_602:// ((cpu_subtype_t) 2) + strncat(strCPUName, "PowerPC 602", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + strncat(CPUInfo.strFamily, "PowerPC", sizeof(CPUInfo.strFamily)-strlen(CPUInfo.strFamily)-1); /* Flawfinder: ignore */ + break; + case CPU_SUBTYPE_POWERPC_603:// ((cpu_subtype_t) 3) + strncat(strCPUName, "PowerPC 603", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + strncat(CPUInfo.strFamily, "PowerPC", sizeof(CPUInfo.strFamily)-strlen(CPUInfo.strFamily)-1); /* Flawfinder: ignore */ + break; + case CPU_SUBTYPE_POWERPC_603e:// ((cpu_subtype_t) 4) + strncat(strCPUName, "PowerPC 603e", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + strncat(CPUInfo.strFamily, "PowerPC", sizeof(CPUInfo.strFamily)-strlen(CPUInfo.strFamily)-1); /* Flawfinder: ignore */ + break; + case CPU_SUBTYPE_POWERPC_603ev:// ((cpu_subtype_t) 5) + strncat(strCPUName, "PowerPC 603ev", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + strncat(CPUInfo.strFamily, "PowerPC", sizeof(CPUInfo.strFamily)-strlen(CPUInfo.strFamily)-1); /* Flawfinder: ignore */ + break; + case CPU_SUBTYPE_POWERPC_604:// ((cpu_subtype_t) 6) + strncat(strCPUName, "PowerPC 604", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + strncat(CPUInfo.strFamily, "PowerPC", sizeof(CPUInfo.strFamily)-strlen(CPUInfo.strFamily)-1); /* Flawfinder: ignore */ + break; + case CPU_SUBTYPE_POWERPC_604e:// ((cpu_subtype_t) 7) + strncat(strCPUName, "PowerPC 604e", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + strncat(CPUInfo.strFamily, "PowerPC", sizeof(CPUInfo.strFamily)-strlen(CPUInfo.strFamily)-1); /* Flawfinder: ignore */ + break; + case CPU_SUBTYPE_POWERPC_620:// ((cpu_subtype_t) 8) + strncat(strCPUName, "PowerPC 620", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + strncat(CPUInfo.strFamily, "PowerPC", sizeof(CPUInfo.strFamily)-strlen(CPUInfo.strFamily)-1); /* Flawfinder: ignore */ + break; + case CPU_SUBTYPE_POWERPC_750:// ((cpu_subtype_t) 9) + strncat(strCPUName, "PowerPC 750", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + strncat(CPUInfo.strFamily, "PowerPC G3", sizeof(CPUInfo.strFamily)-strlen(CPUInfo.strFamily)-1); /* Flawfinder: ignore */ + break; + case CPU_SUBTYPE_POWERPC_7400:// ((cpu_subtype_t) 10) + strncat(strCPUName, "PowerPC 7400", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + strncat(CPUInfo.strFamily, "PowerPC G4", sizeof(CPUInfo.strFamily)-strlen(CPUInfo.strFamily)-1); /* Flawfinder: ignore */ + break; + case CPU_SUBTYPE_POWERPC_7450:// ((cpu_subtype_t) 11) + strncat(strCPUName, "PowerPC 7450", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + strncat(CPUInfo.strFamily, "PowerPC G4", sizeof(CPUInfo.strFamily)-strlen(CPUInfo.strFamily)-1); /* Flawfinder: ignore */ + break; + case CPU_SUBTYPE_POWERPC_970:// ((cpu_subtype_t) 100) + strncat(strCPUName, "PowerPC 970", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + strncat(CPUInfo.strFamily, "PowerPC G5", sizeof(CPUInfo.strFamily)-strlen(CPUInfo.strFamily)-1); /* Flawfinder: ignore */ + break; + + default: + strncat(strCPUName, "PowerPC (Unknown)", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + break; + } + + // It's kinda like MMX or SSE... + CPUInfo._Ext.EMMX_MultimediaExtensions = + CPUInfo._Ext.MMX_MultimediaExtensions = + CPUInfo._Ext.SSE_StreamingSIMD_Extensions = + CPUInfo._Ext.SSE2_StreamingSIMD2_Extensions = hasFeature("hw.optional.altivec"); + +#endif + +#if __i386__ + // MBW -- XXX -- TODO -- make this call AnalyzeIntelProcessor()? + switch(cpusubtype) + { + default: + strncat(strCPUName, "i386 (Unknown)", sizeof(strCPUName)-strlen(strCPUName)-1); /* Flawfinder: ignore */ + break; + } + + CPUInfo._Ext.EMMX_MultimediaExtensions = hasFeature("hw.optional.mmx"); // MBW -- XXX -- this may be wrong... + CPUInfo._Ext.MMX_MultimediaExtensions = hasFeature("hw.optional.mmx"); + CPUInfo._Ext.SSE_StreamingSIMD_Extensions = hasFeature("hw.optional.sse"); + CPUInfo._Ext.SSE2_StreamingSIMD2_Extensions = hasFeature("hw.optional.sse2"); + CPUInfo._Ext.AA64_AMD64BitArchitecture = hasFeature("hw.optional.x86_64"); + +#endif + + // Terse CPU info uses this string... + strncpy(CPUInfo.strBrandID, strCPUName,sizeof(CPUInfo.strBrandID)-1); /* Flawfinder: ignore */ + CPUInfo.strBrandID[sizeof(CPUInfo.strBrandID)-1]='\0'; + + // Fun cache config stuff... + + if(l1dcachesize != 0) + { + CPUInfo._L1.Data.bPresent = true; + snprintf(CPUInfo._L1.Data.strSize, sizeof(CPUInfo._L1.Data.strSize), "%d KB", l1dcachesize / 1024); /* Flawfinder: ignore */ +// CPUInfo._L1.Data.uiAssociativeWays = ???; + CPUInfo._L1.Data.uiLineSize = cachelinesize; + } + + if(l1icachesize != 0) + { + CPUInfo._L1.Instruction.bPresent = true; + snprintf(CPUInfo._L1.Instruction.strSize, sizeof(CPUInfo._L1.Instruction.strSize), "%d KB", l1icachesize / 1024); /* Flawfinder: ignore */ +// CPUInfo._L1.Instruction.uiAssociativeWays = ???; + CPUInfo._L1.Instruction.uiLineSize = cachelinesize; + } + + if(l2cachesize != 0) + { + CPUInfo._L2.bPresent = true; + snprintf(CPUInfo._L2.strSize, sizeof(CPUInfo._L2.strSize), "%d KB", l2cachesize / 1024); /* Flawfinder: ignore */ +// CPUInfo._L2.uiAssociativeWays = ???; + CPUInfo._L2.uiLineSize = cachelinesize; + } + + if(l3cachesize != 0) + { + CPUInfo._L2.bPresent = true; + snprintf(CPUInfo._L2.strSize, sizeof(CPUInfo._L2.strSize), "%d KB", l3cachesize / 1024); /* Flawfinder: ignore */ +// CPUInfo._L2.uiAssociativeWays = ???; + CPUInfo._L2.uiLineSize = cachelinesize; + } + + CPUInfo._Ext.FPU_FloatingPointUnit = hasFeature("hw.optional.floatingpoint"); + +// printf("pagesize = 0x%x\n", pagesize); +// printf("cachelinesize = 0x%x\n", cachelinesize); +// printf("l1icachesize = 0x%x\n", l1icachesize); +// printf("l1dcachesize = 0x%x\n", l1dcachesize); +// printf("l2settings = 0x%x\n", l2settings); +// printf("l2cachesize = 0x%x\n", l2cachesize); +// printf("l3settings = 0x%x\n", l3settings); +// printf("l3cachesize = 0x%x\n", l3cachesize); + + // After reading we translate the configuration to strings + TranslateProcessorConfiguration(); + + // After all we return the class CPUInfo member var + return (&CPUInfo); +} + +#endif // LL_DARWIN + +// bool CProcessor::CPUInfoToText(char *strBuffer, unsigned int uiMaxLen) +// ====================================================================== +// Gets the frequency and processor information and writes it to a string +///////////////////////////////////////////////////////////////////////// +bool CProcessor::CPUInfoToText(char *strBuffer, unsigned int uiMaxLen) +{ +#define LENCHECK len = (unsigned int) strlen(buf); if (len >= uiMaxLen) return false; strcpy(strBuffer, buf); strBuffer += len; /*Flawfinder: ignore*/ +#define COPYADD(str) strcpy(buf, str); LENCHECK; /* Flawfinder: ignore */ +#define FORMATADD(format, var) sprintf(buf, format, var); LENCHECK; /* Flawfinder: ignore */ +#define BOOLADD(str, boolvar) COPYADD(str); if (boolvar) { COPYADD(" Yes\n"); } else { COPYADD(" No\n"); } + + char buf[1024]; /* Flawfinder: ignore */ + unsigned int len; + + // First we have to get the frequency + GetCPUFrequency(50); + + // Then we get the processor information + GetCPUInfo(); + + // Now we construct the string (see the macros at function beginning) + strBuffer[0] = 0; + + COPYADD("// CPU General Information\n//////////////////////////\n"); + FORMATADD("Processor name: %s\n", strCPUName); + FORMATADD("Frequency: %.2f MHz\n\n", (float) uqwFrequency / 1000000.0f); + FORMATADD("Vendor: %s\n", CPUInfo.strVendor); + FORMATADD("Family: %s\n", CPUInfo.strFamily); + FORMATADD("Extended family: %d\n", CPUInfo.uiExtendedFamily); + FORMATADD("Model: %s\n", CPUInfo.strModel); + FORMATADD("Extended model: %d\n", CPUInfo.uiExtendedModel); + FORMATADD("Type: %s\n", CPUInfo.strType); + FORMATADD("Brand ID: %s\n", CPUInfo.strBrandID); + if (CPUInfo._Ext.PN_ProcessorSerialNumber) + { + FORMATADD("Processor Serial: %s\n", CPUInfo.strProcessorSerial); + } + else + { + COPYADD("Processor Serial: Disabled\n"); + } + + COPYADD("\n\n// CPU Configuration\n////////////////////\n"); + FORMATADD("L1 instruction cache: %s\n", CPUInfo._L1.Instruction.strCache); + FORMATADD("L1 data cache: %s\n", CPUInfo._L1.Data.strCache); + FORMATADD("L2 cache: %s\n", CPUInfo._L2.strCache); + FORMATADD("L3 cache: %s\n", CPUInfo._L3.strCache); + FORMATADD("Trace cache: %s\n", CPUInfo._Trace.strCache); + FORMATADD("Instruction TLB: %s\n", CPUInfo._Instruction.strTLB); + FORMATADD("Data TLB: %s\n", CPUInfo._Data.strTLB); + FORMATADD("Max Supported CPUID-Level: 0x%08lX\n", CPUInfo.MaxSupportedLevel); + FORMATADD("Max Supported Ext. CPUID-Level: 0x%08lX\n", CPUInfo.MaxSupportedExtendedLevel); + + COPYADD("\n\n// CPU Extensions\n/////////////////\n"); + BOOLADD("AA64 AMD 64-bit Architecture: ", CPUInfo._Ext.AA64_AMD64BitArchitecture); + BOOLADD("ACPI Thermal Monitor And Clock Control: ", CPUInfo._Ext.ACPI_ThermalMonitorAndClockControl); + BOOLADD("APIC Advanced Programmable Interrupt Controller: ", CPUInfo._Ext.APIC_AdvancedProgrammableInterruptController); + FORMATADD(" APIC-ID: %d\n", CPUInfo._Ext.APIC_ID); + BOOLADD("CLFSH CLFLUSH Instruction Presence: ", CPUInfo._Ext.CLFSH_CFLUSH_Instruction); + FORMATADD(" CLFLUSH Instruction Cache Line Size: %d\n", CPUInfo._Ext.CLFLUSH_InstructionCacheLineSize); + BOOLADD("CMOV Conditional Move And Compare Instructions: ", CPUInfo._Ext.CMOV_ConditionalMoveAndCompareInstructions); + BOOLADD("CX8 COMPXCHG8B Instruction: ", CPUInfo._Ext.CX8_COMPXCHG8B_Instruction); + BOOLADD("DE Debugging Extensions: ", CPUInfo._Ext.DE_DebuggingExtensions); + BOOLADD("DS Debug Store: ", CPUInfo._Ext.DS_DebugStore); + BOOLADD("FGPAT Page Attribute Table: ", CPUInfo._Ext.FGPAT_PageAttributeTable); + BOOLADD("FPU Floating Point Unit: ", CPUInfo._Ext.FPU_FloatingPointUnit); + BOOLADD("FXSR Fast Streaming SIMD Extensions Save/Restore:", CPUInfo._Ext.FXSR_FastStreamingSIMD_ExtensionsSaveRestore); + BOOLADD("HT Hyper Threading: ", CPUInfo._Ext.HT_HyperThreading); + BOOLADD("IA64 Intel 64-Bit Architecture: ", CPUInfo._Ext.IA64_Intel64BitArchitecture); + BOOLADD("MCA Machine Check Architecture: ", CPUInfo._Ext.MCA_MachineCheckArchitecture); + BOOLADD("MCE Machine Check Exception: ", CPUInfo._Ext.MCE_MachineCheckException); + BOOLADD("MMX Multimedia Extensions: ", CPUInfo._Ext.MMX_MultimediaExtensions); + BOOLADD("MMX+ Multimedia Extensions: ", CPUInfo._Ext.EMMX_MultimediaExtensions); + BOOLADD("MSR Model Specific Registers: ", CPUInfo._Ext.MSR_ModelSpecificRegisters); + BOOLADD("MTRR Memory Type Range Registers: ", CPUInfo._Ext.MTRR_MemoryTypeRangeRegisters); + BOOLADD("PAE Physical Address Extension: ", CPUInfo._Ext.PAE_PhysicalAddressExtension); + BOOLADD("PGE PTE Global Flag: ", CPUInfo._Ext.PGE_PTE_GlobalFlag); + if (CPUInfo._Ext.PN_ProcessorSerialNumber) + { + FORMATADD("PN Processor Serial Number: %s\n", CPUInfo.strProcessorSerial); + } + else + { + COPYADD("PN Processor Serial Number: Disabled\n"); + } + BOOLADD("PSE Page Size Extensions: ", CPUInfo._Ext.PSE_PageSizeExtensions); + BOOLADD("PSE36 36-bit Page Size Extension: ", CPUInfo._Ext.PSE36_36bitPageSizeExtension); + BOOLADD("SEP Fast System Call: ", CPUInfo._Ext.SEP_FastSystemCall); + BOOLADD("SS Self Snoop: ", CPUInfo._Ext.SS_SelfSnoop); + BOOLADD("SSE Streaming SIMD Extensions: ", CPUInfo._Ext.SSE_StreamingSIMD_Extensions); + BOOLADD("SSE2 Streaming SIMD 2 Extensions: ", CPUInfo._Ext.SSE2_StreamingSIMD2_Extensions); + BOOLADD("TM Thermal Monitor: ", CPUInfo._Ext.TM_ThermalMonitor); + BOOLADD("TSC Time Stamp Counter: ", CPUInfo._Ext.TSC_TimeStampCounter); + BOOLADD("VME Virtual 8086 Mode Enhancements: ", CPUInfo._Ext.VME_Virtual8086ModeEnhancements); + BOOLADD("3DNow! Instructions: ", CPUInfo._Ext._3DNOW_InstructionExtensions); + BOOLADD("Enhanced 3DNow! Instructions: ", CPUInfo._Ext._E3DNOW_InstructionExtensions); + + // Yippie!!! + return true; +} + +// bool CProcessor::WriteInfoTextFile(const char *strFilename) +// =========================================================== +// Takes use of CProcessor::CPUInfoToText and saves the string to a +// file +/////////////////////////////////////////////////////////////////// +bool CProcessor::WriteInfoTextFile(const char *strFilename) +{ + char buf[16384]; /* Flawfinder: ignore */ + + // First we get the string + if (!CPUInfoToText(buf, 16383)) + return false; + + // Then we create a new file (CREATE_ALWAYS) + FILE *file = LLFile::fopen(strFilename, "w"); /* Flawfinder: ignore */ + if (!file) + return false; + + // After that we write the string to the file + unsigned long dwBytesToWrite, dwBytesWritten; + dwBytesToWrite = (unsigned long) strlen(buf); /*Flawfinder: ignore*/ + dwBytesWritten = (unsigned long) fwrite(buf, 1, dwBytesToWrite, file); + fclose(file); + if (dwBytesToWrite != dwBytesWritten) + return false; + + // Done + return true; +} diff --git a/indra/llcommon/llprocessor.h b/indra/llcommon/llprocessor.h new file mode 100644 index 0000000000..ad44e2ccb3 --- /dev/null +++ b/indra/llcommon/llprocessor.h @@ -0,0 +1,158 @@ +/** + * @file llprocessor.h + * @brief Code to figure out the processor. Originally by Benjamin Jurke. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Author: Benjamin Jurke +// File history: 27.02.2002 File created. +/////////////////////////////////////////// + + +#ifndef PROCESSOR_H +#define PROCESSOR_H + +// Options: +/////////// +#if LL_WINDOWS +#define PROCESSOR_FREQUENCY_MEASURE_AVAILABLE +#endif +// Includes --> code gets os-dependend (Win32) + + +typedef struct ProcessorExtensions +{ + bool FPU_FloatingPointUnit; + bool VME_Virtual8086ModeEnhancements; + bool DE_DebuggingExtensions; + bool PSE_PageSizeExtensions; + bool TSC_TimeStampCounter; + bool MSR_ModelSpecificRegisters; + bool PAE_PhysicalAddressExtension; + bool MCE_MachineCheckException; + bool CX8_COMPXCHG8B_Instruction; + bool APIC_AdvancedProgrammableInterruptController; + unsigned int APIC_ID; + bool SEP_FastSystemCall; + bool MTRR_MemoryTypeRangeRegisters; + bool PGE_PTE_GlobalFlag; + bool MCA_MachineCheckArchitecture; + bool CMOV_ConditionalMoveAndCompareInstructions; + bool FGPAT_PageAttributeTable; + bool PSE36_36bitPageSizeExtension; + bool PN_ProcessorSerialNumber; + bool CLFSH_CFLUSH_Instruction; + unsigned int CLFLUSH_InstructionCacheLineSize; + bool DS_DebugStore; + bool ACPI_ThermalMonitorAndClockControl; + bool EMMX_MultimediaExtensions; + bool MMX_MultimediaExtensions; + bool FXSR_FastStreamingSIMD_ExtensionsSaveRestore; + bool SSE_StreamingSIMD_Extensions; + bool SSE2_StreamingSIMD2_Extensions; + bool SS_SelfSnoop; + bool HT_HyperThreading; + unsigned int HT_HyterThreadingSiblings; + bool TM_ThermalMonitor; + bool IA64_Intel64BitArchitecture; + bool _3DNOW_InstructionExtensions; + bool _E3DNOW_InstructionExtensions; + bool AA64_AMD64BitArchitecture; +} ProcessorExtensions; + +typedef struct ProcessorCache +{ + bool bPresent; + char strSize[32]; /* Flawfinder: ignore */ + unsigned int uiAssociativeWays; + unsigned int uiLineSize; + bool bSectored; + char strCache[128]; /* Flawfinder: ignore */ +} ProcessorCache; + +typedef struct ProcessorL1Cache +{ + ProcessorCache Instruction; + ProcessorCache Data; +} ProcessorL1Cache; + +typedef struct ProcessorTLB +{ + bool bPresent; + char strPageSize[32]; /* Flawfinder: ignore */ + unsigned int uiAssociativeWays; + unsigned int uiEntries; + char strTLB[128]; /* Flawfinder: ignore */ +} ProcessorTLB; + +typedef struct ProcessorInfo +{ + char strVendor[16]; /* Flawfinder: ignore */ + unsigned int uiFamily; + unsigned int uiExtendedFamily; + char strFamily[64]; /* Flawfinder: ignore */ + unsigned int uiModel; + unsigned int uiExtendedModel; + char strModel[128]; /* Flawfinder: ignore */ + unsigned int uiStepping; + unsigned int uiType; + char strType[64]; /* Flawfinder: ignore */ + unsigned int uiBrandID; + char strBrandID[64]; /* Flawfinder: ignore */ + char strProcessorSerial[64]; /* Flawfinder: ignore */ + unsigned long MaxSupportedLevel; + unsigned long MaxSupportedExtendedLevel; + ProcessorExtensions _Ext; + ProcessorL1Cache _L1; + ProcessorCache _L2; + ProcessorCache _L3; + ProcessorCache _Trace; + ProcessorTLB _Instruction; + ProcessorTLB _Data; +} ProcessorInfo; + + +// CProcessor +// ========== +// Class for detecting the processor name, type and available +// extensions as long as it's speed. +///////////////////////////////////////////////////////////// +class CProcessor +{ +// Constructor / Destructor: +//////////////////////////// +public: + CProcessor(); + +// Private vars: +//////////////// +public: + F64 uqwFrequency; + char strCPUName[128]; /* Flawfinder: ignore */ + ProcessorInfo CPUInfo; + +// Private functions: +///////////////////// +private: + bool AnalyzeIntelProcessor(); + bool AnalyzeAMDProcessor(); + bool AnalyzeUnknownProcessor(); + bool CheckCPUIDPresence(); + void DecodeProcessorConfiguration(unsigned int cfg); + void TranslateProcessorConfiguration(); + void GetStandardProcessorConfiguration(); + void GetStandardProcessorExtensions(); + +// Public functions: +//////////////////// +public: + F64 GetCPUFrequency(unsigned int uiMeasureMSecs); + const ProcessorInfo *GetCPUInfo(); + bool CPUInfoToText(char *strBuffer, unsigned int uiMaxLen); + bool WriteInfoTextFile(const char *strFilename); +}; + + +#endif diff --git a/indra/llcommon/llptrskiplist.h b/indra/llcommon/llptrskiplist.h new file mode 100644 index 0000000000..fd4dcdf87b --- /dev/null +++ b/indra/llcommon/llptrskiplist.h @@ -0,0 +1,704 @@ +/** + * @file llptrskiplist.h + * @brief Skip list implementation. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLPTRSKIPLIST_H +#define LL_LLPTRSKIPLIST_H + +#include "llerror.h" +//#include "vmath.h" + +///////////////////////////////////////////// +// +// LLPtrSkipList implementation - skip list for pointers to objects +// + +template +class LLPtrSkipList +{ +public: + friend class LLPtrSkipNode; + + // basic constructor + LLPtrSkipList(); + // basic constructor including sorter + LLPtrSkipList(BOOL (*insert_first)(DATA_TYPE *first, DATA_TYPE *second), + BOOL (*equals)(DATA_TYPE *first, DATA_TYPE *second)); + ~LLPtrSkipList(); + + inline void setInsertFirst(BOOL (*insert_first)(const DATA_TYPE *first, const DATA_TYPE *second)); + inline void setEquals(BOOL (*equals)(const DATA_TYPE *first, const DATA_TYPE *second)); + + inline BOOL addData(DATA_TYPE *data); + + inline BOOL checkData(const DATA_TYPE *data); + + inline S32 getLength(); // returns number of items in the list - NOT constant time! + + inline BOOL removeData(const DATA_TYPE *data); + + // note that b_sort is ignored + inline BOOL moveData(const DATA_TYPE *data, LLPtrSkipList *newlist, BOOL b_sort); + + inline BOOL moveCurrentData(LLPtrSkipList *newlist, BOOL b_sort); + + // resort -- use when the value we're sorting by changes + /* IW 12/6/02 - This doesn't work! + Instead, remove the data BEFORE you change it + Then re-insert it after you change it + BOOL resortData(DATA_TYPE *data) + */ + + // remove all nodes from the list but do not delete data + inline void removeAllNodes(); + + inline BOOL deleteData(const DATA_TYPE *data); + + // remove all nodes from the list and delete data + inline void deleteAllData(); + + // place mCurrentp on first node + inline void resetList(); + + // return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp + inline DATA_TYPE *getCurrentData(); + + // same as getCurrentData() but a more intuitive name for the operation + inline DATA_TYPE *getNextData(); + + // remove the Node at mCurentOperatingp + // leave mCurrentp and mCurentOperatingp on the next entry + inline void removeCurrentData(); + + // delete the Node at mCurentOperatingp + // leave mCurrentp and mCurentOperatingp on the next entry + inline void deleteCurrentData(); + + // reset the list and return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp + inline DATA_TYPE *getFirstData(); + + // TRUE if nodes are not in sorted order + inline BOOL corrupt(); + +protected: + class LLPtrSkipNode + { + public: + LLPtrSkipNode() + : mData(NULL) + { + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mForward[i] = NULL; + } + } + + LLPtrSkipNode(DATA_TYPE *data) + : mData(data) + { + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mForward[i] = NULL; + } + } + + ~LLPtrSkipNode() + { + if (mData) + { + llerror("Attempting to call LLPtrSkipNode destructor with a non-null mDatap!", 1); + } + } + + // delete associated data and NULLs out pointer + void deleteData() + { + delete mData; + mData = NULL; + } + + // NULLs out pointer + void removeData() + { + mData = NULL; + } + + DATA_TYPE *mData; + LLPtrSkipNode *mForward[BINARY_DEPTH]; + }; + + static BOOL defaultEquals(const DATA_TYPE *first, const DATA_TYPE *second) + { + return first == second; + } + + + LLPtrSkipNode mHead; + LLPtrSkipNode *mUpdate[BINARY_DEPTH]; + LLPtrSkipNode *mCurrentp; + LLPtrSkipNode *mCurrentOperatingp; + S32 mLevel; + BOOL (*mInsertFirst)(const DATA_TYPE *first, const DATA_TYPE *second); + BOOL (*mEquals)(const DATA_TYPE *first, const DATA_TYPE *second); +}; + + +// basic constructor +template +LLPtrSkipList::LLPtrSkipList() + : mInsertFirst(NULL), mEquals(defaultEquals) +{ + if (BINARY_DEPTH < 2) + { + llerrs << "Trying to create skip list with too little depth, " + "must be 2 or greater" << llendl; + } + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mUpdate[i] = NULL; + } + mLevel = 1; + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + +// basic constructor including sorter +template +LLPtrSkipList::LLPtrSkipList(BOOL (*insert_first)(DATA_TYPE *first, DATA_TYPE *second), + BOOL (*equals)(DATA_TYPE *first, DATA_TYPE *second)) + :mInsertFirst(insert_first), mEquals(equals) +{ + if (BINARY_DEPTH < 2) + { + llerrs << "Trying to create skip list with too little depth, " + "must be 2 or greater" << llendl; + } + mLevel = 1; + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mHead.mForward[i] = NULL; + mUpdate[i] = NULL; + } + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + +template +inline LLPtrSkipList::~LLPtrSkipList() +{ + removeAllNodes(); +} + +template +inline void LLPtrSkipList::setInsertFirst(BOOL (*insert_first)(const DATA_TYPE *first, const DATA_TYPE *second)) +{ + mInsertFirst = insert_first; +} + +template +inline void LLPtrSkipList::setEquals(BOOL (*equals)(const DATA_TYPE *first, const DATA_TYPE *second)) +{ + mEquals = equals; +} + +template +inline BOOL LLPtrSkipList::addData(DATA_TYPE *data) +{ + S32 level; + LLPtrSkipNode *current = &mHead; + LLPtrSkipNode *temp; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mData, data))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mData < data)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + // now add the new node + S32 newlevel; + for (newlevel = 1; newlevel <= mLevel && newlevel < BINARY_DEPTH; newlevel++) + { + if (frand(1.f) < 0.5f) + break; + } + + LLPtrSkipNode *snode = new LLPtrSkipNode(data); + + if (newlevel > mLevel) + { + mHead.mForward[mLevel] = NULL; + mUpdate[mLevel] = &mHead; + mLevel = newlevel; + } + + for (level = 0; level < newlevel; level++) + { + snode->mForward[level] = mUpdate[level]->mForward[level]; + mUpdate[level]->mForward[level] = snode; + } + return TRUE; +} + +template +inline BOOL LLPtrSkipList::checkData(const DATA_TYPE *data) +{ + S32 level; + LLPtrSkipNode *current = &mHead; + LLPtrSkipNode *temp; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mData, data))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mData < data)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + if (current) + { + return mEquals(current->mData, data); + } + else + { + return FALSE; + } +} + +// returns number of items in the list +template +inline S32 LLPtrSkipList::getLength() +{ + U32 length = 0; + for (LLPtrSkipNode* temp = *(mHead.mForward); temp != NULL; temp = temp->mForward[0]) + { + length++; + } + return length; +} + +template +inline BOOL LLPtrSkipList::removeData(const DATA_TYPE *data) +{ + S32 level; + LLPtrSkipNode *current = &mHead; + LLPtrSkipNode *temp; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mData, data))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mData < data)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + if (!current) + { + // empty list or beyond the end! + return FALSE; + } + + // is this the one we want? + if (!mEquals(current->mData, data)) + { + // nope! + return FALSE; + } + else + { + // yes it is! change pointers as required + for (level = 0; level < mLevel; level++) + { + if (mUpdate[level]->mForward[level] != current) + { + // cool, we've fixed all the pointers! + break; + } + mUpdate[level]->mForward[level] = current->mForward[level]; + } + + // clean up cuurent + current->removeData(); + delete current; + + // clean up mHead + while ( (mLevel > 1) + &&(!mHead.mForward[mLevel - 1])) + { + mLevel--; + } + } + return TRUE; +} + +// note that b_sort is ignored +template +inline BOOL LLPtrSkipList::moveData(const DATA_TYPE *data, LLPtrSkipList *newlist, BOOL b_sort) +{ + BOOL removed = removeData(data); + BOOL added = newlist->addData(data); + return removed && added; +} + +template +inline BOOL LLPtrSkipList::moveCurrentData(LLPtrSkipList *newlist, BOOL b_sort) +{ + if (mCurrentOperatingp) + { + mCurrentp = mCurrentOperatingp->mForward[0]; + BOOL removed = removeData(mCurrentOperatingp); + BOOL added = newlist->addData(mCurrentOperatingp); + mCurrentOperatingp = mCurrentp; + return removed && added; + } + return FALSE; +} + +// resort -- use when the value we're sorting by changes +/* IW 12/6/02 - This doesn't work! + Instead, remove the data BEFORE you change it + Then re-insert it after you change it +BOOL resortData(DATA_TYPE *data) +{ + removeData(data); + addData(data); +} +*/ + +// remove all nodes from the list but do not delete data +template +inline void LLPtrSkipList::removeAllNodes() +{ + LLPtrSkipNode *temp; + // reset mCurrentp + mCurrentp = *(mHead.mForward); + + while (mCurrentp) + { + temp = mCurrentp->mForward[0]; + mCurrentp->removeData(); + delete mCurrentp; + mCurrentp = temp; + } + + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mHead.mForward[i] = NULL; + mUpdate[i] = NULL; + } + + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + +template +inline BOOL LLPtrSkipList::deleteData(const DATA_TYPE *data) +{ + S32 level; + LLPtrSkipNode *current = &mHead; + LLPtrSkipNode *temp; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mData, data))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mData < data)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + if (!current) + { + // empty list or beyond the end! + return FALSE; + } + + // is this the one we want? + if (!mEquals(current->mData, data)) + { + // nope! + return FALSE; + } + else + { + // do we need to fix current or currentop? + if (current == mCurrentp) + { + mCurrentp = current->mForward[0]; + } + + if (current == mCurrentOperatingp) + { + mCurrentOperatingp = current->mForward[0]; + } + + // yes it is! change pointers as required + for (level = 0; level < mLevel; level++) + { + if (mUpdate[level]->mForward[level] != current) + { + // cool, we've fixed all the pointers! + break; + } + mUpdate[level]->mForward[level] = current->mForward[level]; + } + + // clean up cuurent + current->deleteData(); + delete current; + + // clean up mHead + while ( (mLevel > 1) + &&(!mHead.mForward[mLevel - 1])) + { + mLevel--; + } + } + return TRUE; +} + +// remove all nodes from the list and delete data +template +inline void LLPtrSkipList::deleteAllData() +{ + LLPtrSkipNode *temp; + // reset mCurrentp + mCurrentp = *(mHead.mForward); + + while (mCurrentp) + { + temp = mCurrentp->mForward[0]; + mCurrentp->deleteData(); + delete mCurrentp; + mCurrentp = temp; + } + + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mHead.mForward[i] = NULL; + mUpdate[i] = NULL; + } + + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + +// place mCurrentp on first node +template +inline void LLPtrSkipList::resetList() +{ + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + +// return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp +template +inline DATA_TYPE *LLPtrSkipList::getCurrentData() +{ + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = *mCurrentp->mForward; + return mCurrentOperatingp->mData; + } + else + { + //return NULL; // causes compile warning + return 0; // equivalent, but no warning + } +} + +// same as getCurrentData() but a more intuitive name for the operation +template +inline DATA_TYPE *LLPtrSkipList::getNextData() +{ + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = *mCurrentp->mForward; + return mCurrentOperatingp->mData; + } + else + { + //return NULL; // causes compile warning + return 0; // equivalent, but no warning + } +} + +// remove the Node at mCurentOperatingp +// leave mCurrentp and mCurentOperatingp on the next entry +template +inline void LLPtrSkipList::removeCurrentData() +{ + if (mCurrentOperatingp) + { + removeData(mCurrentOperatingp->mData); + } +} + +// delete the Node at mCurentOperatingp +// leave mCurrentp and mCurentOperatingp on the next entry +template +inline void LLPtrSkipList::deleteCurrentData() +{ + if (mCurrentOperatingp) + { + deleteData(mCurrentOperatingp->mData); + } +} + +// reset the list and return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp +template +inline DATA_TYPE *LLPtrSkipList::getFirstData() +{ + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mForward[0]; + return mCurrentOperatingp->mData; + } + else + { + //return NULL; // causes compile warning + return 0; // equivalent, but no warning + } +} + +template +inline BOOL LLPtrSkipList::corrupt() +{ + LLPtrSkipNode *previous = mHead.mForward[0]; + + // Empty lists are not corrupt. + if (!previous) return FALSE; + + LLPtrSkipNode *current = previous->mForward[0]; + while(current) + { + if (!mInsertFirst(previous->mData, current->mData)) + { + // prev shouldn't be in front of cur! + return TRUE; + } + current = current->mForward[0]; + } + return FALSE; +} + +#endif diff --git a/indra/llcommon/llptrskipmap.h b/indra/llcommon/llptrskipmap.h new file mode 100644 index 0000000000..63668c34fe --- /dev/null +++ b/indra/llcommon/llptrskipmap.h @@ -0,0 +1,1219 @@ +/** + * @file llptrskipmap.h + * @brief Just like a LLSkipMap, but since it's pointers, you can call + * deleteAllData + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ +#ifndef LL_LLPTRSKIPMAP_H +#define LL_LLPTRSKIPMAP_H + +#include "llerror.h" +#include "llrand.h" + +template +class LLPtrSkipMapNode +{ +public: + LLPtrSkipMapNode() + { + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mForward[i] = NULL; + } + + U8 *zero = (U8 *)&mIndex; + + for (i = 0; i < (S32)sizeof(INDEX_T); i++) + { + *(zero + i) = 0; + } + + zero = (U8 *)&mData; + + for (i = 0; i < (S32)sizeof(DATA_T); i++) + { + *(zero + i) = 0; + } + } + + LLPtrSkipMapNode(const INDEX_T &index) + : mIndex(index) + { + + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mForward[i] = NULL; + } + + U8 *zero = (U8 *)&mData; + + for (i = 0; i < (S32)sizeof(DATA_T); i++) + { + *(zero + i) = 0; + } + } + + LLPtrSkipMapNode(const INDEX_T &index, DATA_T datap) + : mIndex(index) + { + + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mForward[i] = NULL; + } + + mData = datap; + } + + ~LLPtrSkipMapNode() + { + } + + // delete associated data and NULLs out pointer + void deleteData() + { + delete mData; + mData = 0; + } + + // NULLs out pointer + void removeData() + { + mData = 0; + } + + INDEX_T mIndex; + DATA_T mData; + LLPtrSkipMapNode *mForward[BINARY_DEPTH]; + +private: + // Disallow copying of LLPtrSkipMapNodes by not implementing these methods. + LLPtrSkipMapNode(const LLPtrSkipMapNode &); + LLPtrSkipMapNode &operator=(const LLPtrSkipMapNode &rhs); +}; + +//--------------------------------------------------------------------------- + +template +class LLPtrSkipMap +{ +public: + typedef BOOL (*compare)(const DATA_T& first, const DATA_T& second); + typedef compare insert_func; + typedef compare equals_func; + + void init(); + + // basic constructor + LLPtrSkipMap(); + + // basic constructor including sorter + LLPtrSkipMap(insert_func insert_first, equals_func equals); + + ~LLPtrSkipMap(); + + void setInsertFirst(insert_func insert_first); + void setEquals(equals_func equals); + + DATA_T &addData(const INDEX_T &index, DATA_T datap); + DATA_T &addData(const INDEX_T &index); + DATA_T &getData(const INDEX_T &index); + DATA_T &operator[](const INDEX_T &index); + + // If index present, returns data. + // If index not present, adds and returns NULL. + DATA_T &getData(const INDEX_T &index, BOOL &b_new_entry); + + // returns data entry before and after index + BOOL getInterval(const INDEX_T &index, INDEX_T &index_before, INDEX_T &index_after, + DATA_T &data_before, DATA_T &data_after ); + + // Returns TRUE if data present in map. + BOOL checkData(const INDEX_T &index); + + // Returns TRUE if key is present in map. This is useful if you + // are potentially storing NULL pointers in the map + BOOL checkKey(const INDEX_T &index); + + // If there, returns the data. + // If not, returns NULL. + // Never adds entries to the map. + DATA_T getIfThere(const INDEX_T &index); + + INDEX_T reverseLookup(const DATA_T datap); + + // returns number of items in the list + S32 getLength(); // WARNING! getLength is O(n), not O(1)! + + BOOL removeData(const INDEX_T &index); + BOOL deleteData(const INDEX_T &index); + + // remove all nodes from the list but do not delete data + void removeAllData(); + void deleteAllData(); + + // place mCurrentp on first node + void resetList(); + + // return the data currently pointed to + DATA_T getCurrentDataWithoutIncrement(); + + // return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp + DATA_T getCurrentData(); + + // same as getCurrentData() but a more intuitive name for the operation + DATA_T getNextData(); + + INDEX_T getNextKey(); + + // return the key currently pointed to + INDEX_T getCurrentKeyWithoutIncrement(); + + // remove the Node at mCurentOperatingp + // leave mCurrentp and mCurentOperatingp on the next entry + void removeCurrentData(); + + void deleteCurrentData(); + + // reset the list and return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp + DATA_T getFirstData(); + + INDEX_T getFirstKey(); + + static BOOL defaultEquals(const INDEX_T &first, const INDEX_T &second) + { + return first == second; + } + +private: + // don't generate implicit copy constructor or copy assignment + LLPtrSkipMap(const LLPtrSkipMap &); + LLPtrSkipMap &operator=(const LLPtrSkipMap &); + +private: + LLPtrSkipMapNode mHead; + LLPtrSkipMapNode *mUpdate[BINARY_DEPTH]; + LLPtrSkipMapNode *mCurrentp; + LLPtrSkipMapNode *mCurrentOperatingp; + S32 mLevel; + BOOL (*mInsertFirst)(const INDEX_T &first, const INDEX_T &second); + BOOL (*mEquals)(const INDEX_T &first, const INDEX_T &second); + S32 mNumberOfSteps; +}; + +////////////////////////////////////////////////// +// +// LLPtrSkipMap implementation +// +// + +template +inline LLPtrSkipMap::LLPtrSkipMap() + : mInsertFirst(NULL), + mEquals(defaultEquals) +{ + if (BINARY_DEPTH < 2) + { + llerrs << "Trying to create skip list with too little depth, " + "must be 2 or greater" << llendl; + } + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mUpdate[i] = NULL; + } + mLevel = 1; + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + +template +inline LLPtrSkipMap::LLPtrSkipMap(insert_func insert_first, + equals_func equals) +: mInsertFirst(insert_first), + mEquals(equals) +{ + if (BINARY_DEPTH < 2) + { + llerrs << "Trying to create skip list with too little depth, " + "must be 2 or greater" << llendl; + } + mLevel = 1; + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mHead.mForward[i] = NULL; + mUpdate[i] = NULL; + } + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + +template +inline LLPtrSkipMap::~LLPtrSkipMap() +{ + removeAllData(); +} + +template +inline void LLPtrSkipMap::setInsertFirst(insert_func insert_first) +{ + mInsertFirst = insert_first; +} + +template +inline void LLPtrSkipMap::setEquals(equals_func equals) +{ + mEquals = equals; +} + +template +inline DATA_T &LLPtrSkipMap::addData(const INDEX_T &index, DATA_T datap) +{ + S32 level; + LLPtrSkipMapNode *current = &mHead; + LLPtrSkipMapNode *temp; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + // replace the existing data if a node is already there + if ( (current) + &&(mEquals(current->mIndex, index))) + { + current->mData = datap; + return current->mData; + } + + // now add the new node + S32 newlevel; + for (newlevel = 1; newlevel <= mLevel && newlevel < BINARY_DEPTH; newlevel++) + { + if (frand(1.f) < 0.5f) + { + break; + } + } + + LLPtrSkipMapNode *snode + = new LLPtrSkipMapNode(index, datap); + + if (newlevel > mLevel) + { + mHead.mForward[mLevel] = NULL; + mUpdate[mLevel] = &mHead; + mLevel = newlevel; + } + + for (level = 0; level < newlevel; level++) + { + snode->mForward[level] = mUpdate[level]->mForward[level]; + mUpdate[level]->mForward[level] = snode; + } + return snode->mData; +} + +template +inline DATA_T &LLPtrSkipMap::addData(const INDEX_T &index) +{ + S32 level; + LLPtrSkipMapNode *current = &mHead; + LLPtrSkipMapNode *temp; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + // now add the new node + S32 newlevel; + for (newlevel = 1; newlevel <= mLevel && newlevel < BINARY_DEPTH; newlevel++) + { + if (frand(1.f) < 0.5f) + break; + } + + LLPtrSkipMapNode *snode + = new LLPtrSkipMapNode(index); + + if (newlevel > mLevel) + { + mHead.mForward[mLevel] = NULL; + mUpdate[mLevel] = &mHead; + mLevel = newlevel; + } + + for (level = 0; level < newlevel; level++) + { + snode->mForward[level] = mUpdate[level]->mForward[level]; + mUpdate[level]->mForward[level] = snode; + } + return snode->mData; +} + +template +inline DATA_T &LLPtrSkipMap::getData(const INDEX_T &index) +{ + S32 level; + LLPtrSkipMapNode *current = &mHead; + LLPtrSkipMapNode *temp; + + mNumberOfSteps = 0; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + mNumberOfSteps++; + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + mNumberOfSteps++; + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + mNumberOfSteps++; + + if ( (current) + &&(mEquals(current->mIndex, index))) + { + + return current->mData; + } + + // now add the new node + S32 newlevel; + for (newlevel = 1; newlevel <= mLevel && newlevel < BINARY_DEPTH; newlevel++) + { + if (frand(1.f) < 0.5f) + break; + } + + LLPtrSkipMapNode *snode + = new LLPtrSkipMapNode(index); + + if (newlevel > mLevel) + { + mHead.mForward[mLevel] = NULL; + mUpdate[mLevel] = &mHead; + mLevel = newlevel; + } + + for (level = 0; level < newlevel; level++) + { + snode->mForward[level] = mUpdate[level]->mForward[level]; + mUpdate[level]->mForward[level] = snode; + } + + return snode->mData; +} + +template +inline BOOL LLPtrSkipMap::getInterval(const INDEX_T &index, + INDEX_T &index_before, + INDEX_T &index_after, + DATA_T &data_before, + DATA_T &data_after) +{ + S32 level; + LLPtrSkipMapNode *current = &mHead; + LLPtrSkipMapNode *temp; + + mNumberOfSteps = 0; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + mNumberOfSteps++; + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + mNumberOfSteps++; + } + *(mUpdate + level) = current; + } + } + + BOOL both_found = TRUE; + + if (current != &mHead) + { + index_before = current->mIndex; + data_before = current->mData; + } + else + { + data_before = 0; + index_before = 0; + both_found = FALSE; + } + + // we're now just in front of where we want to be . . . take one step forward + mNumberOfSteps++; + current = *current->mForward; + + if (current) + { + data_after = current->mData; + index_after = current->mIndex; + } + else + { + data_after = 0; + index_after = 0; + both_found = FALSE; + } + + return both_found; +} + + +template +inline DATA_T &LLPtrSkipMap::operator[](const INDEX_T &index) +{ + + return getData(index); +} + +// If index present, returns data. +// If index not present, adds and returns NULL. +template +inline DATA_T &LLPtrSkipMap::getData(const INDEX_T &index, BOOL &b_new_entry) +{ + S32 level; + LLPtrSkipMapNode *current = &mHead; + LLPtrSkipMapNode *temp; + + mNumberOfSteps = 0; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + mNumberOfSteps++; + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + mNumberOfSteps++; + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + mNumberOfSteps++; + current = *current->mForward; + + if ( (current) + &&(mEquals(current->mIndex, index))) + { + + return current->mData; + } + b_new_entry = TRUE; + addData(index); + + return current->mData; +} + +// Returns TRUE if data present in map. +template +inline BOOL LLPtrSkipMap::checkData(const INDEX_T &index) +{ + S32 level; + LLPtrSkipMapNode *current = &mHead; + LLPtrSkipMapNode *temp; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + if (current) + { + // Gets rid of some compiler ambiguity for the LLPointer<> templated class. + if (current->mData) + { + return mEquals(current->mIndex, index); + } + } + + return FALSE; +} + +// Returns TRUE if key is present in map. This is useful if you +// are potentially storing NULL pointers in the map +template +inline BOOL LLPtrSkipMap::checkKey(const INDEX_T &index) +{ + S32 level; + LLPtrSkipMapNode *current = &mHead; + LLPtrSkipMapNode *temp; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + if (current) + { + return mEquals(current->mIndex, index); + } + + return FALSE; +} + +// If there, returns the data. +// If not, returns NULL. +// Never adds entries to the map. +template +inline DATA_T LLPtrSkipMap::getIfThere(const INDEX_T &index) +{ + S32 level; + LLPtrSkipMapNode *current = &mHead; + LLPtrSkipMapNode *temp; + + mNumberOfSteps = 0; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + mNumberOfSteps++; + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + mNumberOfSteps++; + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + mNumberOfSteps++; + current = *current->mForward; + + if (current) + { + if (mEquals(current->mIndex, index)) + { + return current->mData; + } + } + + // Avoid Linux compiler warning on returning NULL. + return (DATA_T)0; +} + +template +inline INDEX_T LLPtrSkipMap::reverseLookup(const DATA_T datap) +{ + LLPtrSkipMapNode *current = &mHead; + + while (current) + { + if (datap == current->mData) + { + + return current->mIndex; + } + current = *current->mForward; + } + + // not found! return NULL + return INDEX_T(); +} + +// returns number of items in the list +template +inline S32 LLPtrSkipMap::getLength() +{ + U32 length = 0; + LLPtrSkipMapNode* temp; + for (temp = *(mHead.mForward); temp != NULL; temp = temp->mForward[0]) + { + length++; + } + + return length; +} + +template +inline BOOL LLPtrSkipMap::removeData(const INDEX_T &index) +{ + S32 level; + LLPtrSkipMapNode *current = &mHead; + LLPtrSkipMapNode *temp; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + if (!current) + { + // empty list or beyond the end! + + return FALSE; + } + + // is this the one we want? + if (!mEquals(current->mIndex, index)) + { + // nope! + + return FALSE; + } + else + { + // do we need to fix current or currentop? + if (current == mCurrentp) + { + mCurrentp = *current->mForward; + } + + if (current == mCurrentOperatingp) + { + mCurrentOperatingp = *current->mForward; + } + // yes it is! change pointers as required + for (level = 0; level < mLevel; level++) + { + if (*((*(mUpdate + level))->mForward + level) != current) + { + // cool, we've fixed all the pointers! + break; + } + *((*(mUpdate + level))->mForward + level) = *(current->mForward + level); + } + + // clean up cuurent + current->removeData(); + delete current; + + // clean up mHead + while ( (mLevel > 1) + &&(!*(mHead.mForward + mLevel - 1))) + { + mLevel--; + } + } + + return TRUE; +} + +template +inline BOOL LLPtrSkipMap::deleteData(const INDEX_T &index) +{ + S32 level; + LLPtrSkipMapNode *current = &mHead; + LLPtrSkipMapNode *temp; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + if (!current) + { + // empty list or beyond the end! + + return FALSE; + } + + // is this the one we want? + if (!mEquals(current->mIndex, index)) + { + // nope! + + return FALSE; + } + else + { + // do we need to fix current or currentop? + if (current == mCurrentp) + { + mCurrentp = *current->mForward; + } + + if (current == mCurrentOperatingp) + { + mCurrentOperatingp = *current->mForward; + } + // yes it is! change pointers as required + for (level = 0; level < mLevel; level++) + { + if (*((*(mUpdate + level))->mForward + level) != current) + { + // cool, we've fixed all the pointers! + break; + } + *((*(mUpdate + level))->mForward + level) = *(current->mForward + level); + } + + // clean up cuurent + current->deleteData(); + delete current; + + // clean up mHead + while ( (mLevel > 1) + &&(!*(mHead.mForward + mLevel - 1))) + { + mLevel--; + } + } + + return TRUE; +} + +// remove all nodes from the list but do not delete data +template +void LLPtrSkipMap::removeAllData() +{ + LLPtrSkipMapNode *temp; + // reset mCurrentp + mCurrentp = *(mHead.mForward); + + while (mCurrentp) + { + temp = mCurrentp->mForward[0]; + mCurrentp->removeData(); + delete mCurrentp; + mCurrentp = temp; + } + + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mHead.mForward[i] = NULL; + mUpdate[i] = NULL; + } + + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + +template +inline void LLPtrSkipMap::deleteAllData() +{ + LLPtrSkipMapNode *temp; + // reset mCurrentp + mCurrentp = *(mHead.mForward); + + while (mCurrentp) + { + temp = mCurrentp->mForward[0]; + mCurrentp->deleteData(); + delete mCurrentp; + mCurrentp = temp; + } + + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mHead.mForward[i] = NULL; + mUpdate[i] = NULL; + } + + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + +// place mCurrentp on first node +template +inline void LLPtrSkipMap::resetList() +{ + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + + +// return the data currently pointed to +template +inline DATA_T LLPtrSkipMap::getCurrentDataWithoutIncrement() +{ + if (mCurrentOperatingp) + { + return mCurrentOperatingp->mData; + } + else + { + //return NULL; // causes warning + return (DATA_T)0; // equivalent, but no warning + } +} + +// return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp +template +inline DATA_T LLPtrSkipMap::getCurrentData() +{ + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mForward[0]; + return mCurrentOperatingp->mData; + } + else + { + //return NULL; // causes warning + return (DATA_T)0; // equivalent, but no warning + } +} + +// same as getCurrentData() but a more intuitive name for the operation +template +inline DATA_T LLPtrSkipMap::getNextData() +{ + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mForward[0]; + return mCurrentOperatingp->mData; + } + else + { + //return NULL; // causes compile warning + return (DATA_T)0; // equivalent, but removes warning + } +} + +template +inline INDEX_T LLPtrSkipMap::getNextKey() +{ + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mForward[0]; + return mCurrentOperatingp->mIndex; + } + else + { + return mHead.mIndex; + } +} + +// return the key currently pointed to +template +inline INDEX_T LLPtrSkipMap::getCurrentKeyWithoutIncrement() +{ + if (mCurrentOperatingp) + { + return mCurrentOperatingp->mIndex; + } + else + { + //return NULL; // causes compile warning + return (INDEX_T)0; // equivalent, but removes warning + } +} + + +// remove the Node at mCurentOperatingp +// leave mCurrentp and mCurentOperatingp on the next entry +template +inline void LLPtrSkipMap::removeCurrentData() +{ + if (mCurrentOperatingp) + { + removeData(mCurrentOperatingp->mIndex); + } +} + +template +inline void LLPtrSkipMap::deleteCurrentData() +{ + if (mCurrentOperatingp) + { + deleteData(mCurrentOperatingp->mIndex); + } +} + +// reset the list and return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp +template +inline DATA_T LLPtrSkipMap::getFirstData() +{ + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mForward[0]; + return mCurrentOperatingp->mData; + } + else + { + //return NULL; // causes compile warning + return (DATA_T)0; // equivalent, but removes warning + } +} + +template +inline INDEX_T LLPtrSkipMap::getFirstKey() +{ + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mForward[0]; + return mCurrentOperatingp->mIndex; + } + else + { + return mHead.mIndex; + } +} + +#endif diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp new file mode 100644 index 0000000000..bc41927d38 --- /dev/null +++ b/indra/llcommon/llqueuedthread.cpp @@ -0,0 +1,491 @@ +/** + * @file llqueuedthread.cpp + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llqueuedthread.h" +#include "llstl.h" + +//============================================================================ + +// MAIN THREAD +LLQueuedThread::LLQueuedThread(const std::string& name, bool threaded, bool runalways) : + LLThread(name), + mThreaded(threaded), + mRunAlways(runalways), + mIdleThread(TRUE), + mNextHandle(0) +{ + if (mThreaded) + { + start(); + } +} + +// MAIN THREAD +LLQueuedThread::~LLQueuedThread() +{ + setQuitting(); + + unpause(); // MAIN THREAD + if (mThreaded) + { + S32 timeout = 100; + for ( ; timeout>0; timeout--) + { + if (isStopped()) + { + break; + } + ms_sleep(100); + LLThread::yield(); + } + if (timeout == 0) + { + llwarns << "~LLQueuedThread (" << mName << ") timed out!" << llendl; + } + } + else + { + mStatus = STOPPED; + } + + QueuedRequest* req; + while ( (req = (QueuedRequest*)mRequestHash.pop_element()) ) + { + req->deleteRequest(); + } + + // ~LLThread() will be called here +} + +//---------------------------------------------------------------------------- + +// MAIN THREAD +void LLQueuedThread::update(U32 ms_elapsed) +{ + updateQueue(0); +} + +void LLQueuedThread::updateQueue(S32 inc) +{ + // If mRunAlways == TRUE, unpause the thread whenever we put something into the queue. + // If mRunAlways == FALSE, we only unpause the thread when updateQueue() is called from the main loop (i.e. between rendered frames) + + if (inc == 0) // Frame Update + { + if (mThreaded) + { + unpause(); + wake(); // Wake the thread up if necessary. + } + else + { + while (processNextRequest() > 0) + ; + } + } + else + { + // Something has been added to the queue + if (mRunAlways) + { + if (mThreaded) + { + wake(); // Wake the thread up if necessary. + } + else + { + while(processNextRequest() > 0) + ; + } + } + } +} + +//virtual +// May be called from any thread +S32 LLQueuedThread::getPending(bool child_thread) +{ + S32 res; + lockData(); + res = mRequestQueue.size(); + unlockData(); + return res; +} + +// MAIN thread +void LLQueuedThread::waitOnPending() +{ + while(1) + { + updateQueue(0); + + if (mIdleThread) + { + break; + } + if (mThreaded) + { + yield(); + } + } + return; +} + +// MAIN thread +void LLQueuedThread::printQueueStats() +{ + lockData(); + if (!mRequestQueue.empty()) + { + QueuedRequest *req = *mRequestQueue.begin(); + llinfos << llformat("Pending Requests:%d Current status:%d", mRequestQueue.size(), req->getStatus()) << llendl; + } + else + { + llinfos << "Queued Thread Idle" << llendl; + } + unlockData(); +} + +// MAIN thread +LLQueuedThread::handle_t LLQueuedThread::generateHandle() +{ + lockData(); + while ((mNextHandle == nullHandle()) || (mRequestHash.find(mNextHandle))) + { + mNextHandle++; + } + unlockData(); + return mNextHandle++; +} + +// MAIN thread +bool LLQueuedThread::addRequest(QueuedRequest* req) +{ + if (mStatus == QUITTING) + { + return false; + } + + lockData(); + req->setStatus(STATUS_QUEUED); + mRequestQueue.insert(req); + mRequestHash.insert(req); +#if _DEBUG +// llinfos << llformat("LLQueuedThread::Added req [%08d]",handle) << llendl; +#endif + unlockData(); + + updateQueue(1); + + return true; +} + +// MAIN thread +bool LLQueuedThread::waitForResult(LLQueuedThread::handle_t handle, bool auto_complete) +{ + llassert (handle != nullHandle()) + bool res = false; + bool waspaused = isPaused(); + bool done = false; + while(!done) + { + updateQueue(0); // unpauses + lockData(); + QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); + if (!req) + { + done = true; // request does not exist + } + else if (req->getStatus() == STATUS_COMPLETE) + { + res = true; + if (auto_complete) + { + mRequestHash.erase(handle); + req->deleteRequest(); +// check(); + } + done = true; + } + unlockData(); + + if (!done && mThreaded) + { + yield(); + } + } + if (waspaused) + { + pause(); + } + return res; +} + +// MAIN thread +LLQueuedThread::QueuedRequest* LLQueuedThread::getRequest(handle_t handle) +{ + if (handle == nullHandle()) + { + return 0; + } + lockData(); + QueuedRequest* res = (QueuedRequest*)mRequestHash.find(handle); + unlockData(); + return res; +} + +LLQueuedThread::status_t LLQueuedThread::getRequestStatus(handle_t handle) +{ + status_t res = STATUS_EXPIRED; + lockData(); + QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); + if (req) + { + res = req->getStatus(); + } + unlockData(); + return res; +} + +LLQueuedThread::status_t LLQueuedThread::abortRequest(handle_t handle, U32 flags) +{ + status_t res = STATUS_EXPIRED; + lockData(); + QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); + if (req) + { + res = req->abortRequest(flags); + if ((flags & AUTO_COMPLETE) && (res == STATUS_COMPLETE)) + { + mRequestHash.erase(handle); + req->deleteRequest(); +// check(); + } +#if _DEBUG +// llinfos << llformat("LLQueuedThread::Aborted req [%08d]",handle) << llendl; +#endif + } + unlockData(); + return res; +} + +// MAIN thread +LLQueuedThread::status_t LLQueuedThread::setFlags(handle_t handle, U32 flags) +{ + status_t res = STATUS_EXPIRED; + lockData(); + QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); + if (req) + { + res = req->setFlags(flags); + } + unlockData(); + return res; +} + +void LLQueuedThread::setPriority(handle_t handle, U32 priority) +{ + lockData(); + QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); + if (req && (req->getStatus() == STATUS_QUEUED)) + { + llverify(mRequestQueue.erase(req) == 1); + req->setPriority(priority); + mRequestQueue.insert(req); + } + unlockData(); +} + +bool LLQueuedThread::completeRequest(handle_t handle) +{ + bool res = false; + lockData(); + QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); + if (req) + { + llassert(req->getStatus() != STATUS_QUEUED && req->getStatus() != STATUS_ABORT); +#if _DEBUG +// llinfos << llformat("LLQueuedThread::Completed req [%08d]",handle) << llendl; +#endif + mRequestHash.erase(handle); + req->deleteRequest(); +// check(); + res = true; + } + unlockData(); + return res; +} + +bool LLQueuedThread::check() +{ +#if 0 // not a reliable check once mNextHandle wraps, just for quick and dirty debugging + for (int i=0; i* entry = mRequestHash.get_element_at_index(i); + while (entry) + { + if (entry->getHashKey() > mNextHandle) + { + llerrs << "Hash Error" << llendl; + return false; + } + entry = entry->getNextEntry(); + } + } +#endif + return true; +} + +//============================================================================ +// Runs on its OWN thread + +int LLQueuedThread::processNextRequest() +{ + QueuedRequest *req = 0; + // Get next request from pool + lockData(); + while(1) + { + if (!mRequestQueue.empty()) + { + req = *mRequestQueue.begin(); + mRequestQueue.erase(mRequestQueue.begin()); + } + if (req && req->getStatus() == STATUS_ABORT) + { + req->setStatus(STATUS_ABORTED); + req = 0; + } + else + { + llassert (!req || req->getStatus() == STATUS_QUEUED) + break; + } + } + if (req) + { + req->setStatus(STATUS_INPROGRESS); + } + unlockData(); + + // This is the only place we will cal req->setStatus() after + // it has initially been seet to STATUS_QUEUED, so it is + // safe to access req. + if (req) + { + // process request + bool complete = processRequest(req); + + if (complete) + { + lockData(); + req->setStatus(STATUS_COMPLETE); + req->finishRequest(); + if (req->getFlags() & AUTO_COMPLETE) + { + llverify(mRequestHash.erase(req)) + req->deleteRequest(); +// check(); + } + unlockData(); + } + else + { + lockData(); + req->setStatus(STATUS_QUEUED); + mRequestQueue.insert(req); + unlockData(); + } + } + + int res; + if (getPending(true) == 0) + { + if (isQuitting()) + { + res = -1; // exit thread + } + else + { + res = 0; + } + } + else + { + res = 1; + } + return res; +} + +bool LLQueuedThread::runCondition() +{ + // mRunCondition must be locked here + return (mRequestQueue.empty() && mIdleThread) ? FALSE : TRUE; +} + +void LLQueuedThread::run() +{ + llinfos << "QUEUED THREAD STARTING" << llendl; + + while (1) + { + // this will block on the condition until runCondition() returns true, the thread is unpaused, or the thread leaves the RUNNING state. + checkPause(); + + if(isQuitting()) + break; + + //llinfos << "QUEUED THREAD RUNNING, queue size = " << mRequestQueue.size() << llendl; + + mIdleThread = FALSE; + + int res = processNextRequest(); + if (res == 0) + { + mIdleThread = TRUE; + } + + if (res < 0) // finished working and want to exit + { + break; + } + } + + llinfos << "QUEUED THREAD " << mName << " EXITING." << llendl; +} + +//============================================================================ + +LLQueuedThread::QueuedRequest::QueuedRequest(LLQueuedThread::handle_t handle, U32 priority, U32 flags) : + LLSimpleHashEntry(handle), + mStatus(STATUS_UNKNOWN), + mPriority(priority), + mFlags(flags) +{ +} + +LLQueuedThread::QueuedRequest::~QueuedRequest() +{ + if (mStatus != STATUS_DELETE) + { + llerrs << "Attemt to directly delete a LLQueuedThread::QueuedRequest; use deleteRequest()" << llendl; + } +} + +//virtual +void LLQueuedThread::QueuedRequest::finishRequest() +{ +} + +//virtual +void LLQueuedThread::QueuedRequest::deleteRequest() +{ + setStatus(STATUS_DELETE); + delete this; +} diff --git a/indra/llcommon/llqueuedthread.h b/indra/llcommon/llqueuedthread.h new file mode 100644 index 0000000000..beff473f52 --- /dev/null +++ b/indra/llcommon/llqueuedthread.h @@ -0,0 +1,202 @@ +/** + * @file llqueuedthread.h + * @brief + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLQUEUEDTHREAD_H +#define LL_LLQUEUEDTHREAD_H + +#include +#include +#include +#include + +#include "llapr.h" + +#include "llthread.h" +#include "llsimplehash.h" + +//============================================================================ +// Note: ~LLQueuedThread is O(N) N=# of queued threads, assumed to be small +// It is assumed that LLQueuedThreads are rarely created/destroyed. + +class LLQueuedThread : public LLThread +{ + //------------------------------------------------------------------------ +public: + enum priority_t { + PRIORITY_IMMEDIATE = 0x7FFFFFFF, + PRIORITY_URGENT = 0x40000000, + PRIORITY_HIGH = 0x30000000, + PRIORITY_NORMAL = 0x20000000, + PRIORITY_LOW = 0x10000000, + PRIORITY_LOWBITS = 0x0FFFFFFF + }; + enum status_t { + STATUS_EXPIRED = -1, + STATUS_UNKNOWN = 0, + STATUS_QUEUED = 1, + STATUS_INPROGRESS = 2, + STATUS_COMPLETE = 3, + STATUS_ABORT = 4, + STATUS_ABORTED = 5, + STATUS_DELETE = 6 + }; + enum flags_t { + AUTO_COMPLETE = 1, + AUTO_DELETE = 2 // child-class dependent + }; + + typedef U32 handle_t; + + //------------------------------------------------------------------------ +public: + + class QueuedRequest : public LLSimpleHashEntry + { + friend class LLQueuedThread; + + protected: + ~QueuedRequest(); // use deleteRequest() + + public: + QueuedRequest(handle_t handle, U32 priority, U32 flags = 0); + + status_t getStatus() + { + return mStatus; + } + U32 getPriority() const + { + return mPriority; + } + U32 getFlags() const + { + return mFlags; + } + bool higherPriority(const QueuedRequest& second) const + { + if ( mPriority == second.mPriority) + return mHashKey < second.mHashKey; + else + return mPriority > second.mPriority; + } + + protected: + status_t setStatus(status_t newstatus) + { + status_t oldstatus = mStatus; + mStatus = newstatus; + return oldstatus; + } + status_t abortRequest(U32 flags) + { + // NOTE: flags are |'d + if (mStatus == STATUS_QUEUED) + { + setStatus(STATUS_ABORT); + } + mFlags |= flags; + status_t status = mStatus; + return status; + } + status_t setFlags(U32 flags) + { + // NOTE: flags are |'d + mFlags |= flags; + status_t status = mStatus; + return status; + } + + virtual void finishRequest(); // Always called when after has been processed + virtual void deleteRequest(); // Only method to delete a request + + void setPriority(U32 pri) + { + // Only do this on a request that is not in a queued list! + mPriority = pri; + }; + + protected: + LLAtomic32 mStatus; + U32 mPriority; + U32 mFlags; + }; + +protected: + struct queued_request_less + { + bool operator()(const QueuedRequest* lhs, const QueuedRequest* rhs) const + { + return lhs->higherPriority(*rhs); // higher priority in front of queue (set) + } + }; + + //------------------------------------------------------------------------ + +public: + static handle_t nullHandle() { return handle_t(0); } + +public: + LLQueuedThread(const std::string& name, bool threaded = TRUE, bool runalways = TRUE); + virtual ~LLQueuedThread(); + +private: + // No copy constructor or copy assignment + LLQueuedThread(const LLQueuedThread&); + LLQueuedThread& operator=(const LLQueuedThread&); + + virtual bool runCondition(void); + virtual void run(void); + +protected: + handle_t generateHandle(); + bool addRequest(QueuedRequest* req); + int processNextRequest(void); + + virtual bool processRequest(QueuedRequest* req) = 0; + +public: + bool waitForResult(handle_t handle, bool auto_complete = true); + + void update(U32 ms_elapsed); + void updateQueue(S32 inc); + void waitOnPending(); + void printQueueStats(); + + S32 getPending(bool child_thread = false); + bool getThreaded() { return mThreaded ? true : false; } + bool getRunAlways() { return mRunAlways ? true : false; } + + // Request accessors + status_t getRequestStatus(handle_t handle); + status_t abortRequest(handle_t handle, U32 flags = 0); + status_t setFlags(handle_t handle, U32 flags); + void setPriority(handle_t handle, U32 priority); + bool completeRequest(handle_t handle); + // This is public for support classes like LLWorkerThread, + // but generally the methods above should be used. + QueuedRequest* getRequest(handle_t handle); + + // debug (see source) + bool check(); + +protected: + BOOL mThreaded; // if false, run on main thread and do updates during update() + BOOL mRunAlways; // if false, only wake the threads when updateClass() is called + LLAtomic32 mIdleThread; // request queue is empty (or we are quitting) and the thread is idle + + typedef std::set request_queue_t; + request_queue_t mRequestQueue; + + enum { REQUEST_HASH_SIZE = 512 }; // must be power of 2 + typedef LLSimpleHash request_hash_t; + request_hash_t mRequestHash; + + handle_t mNextHandle; +}; + +#endif // LL_LLQUEUEDTHREAD_H diff --git a/indra/llcommon/llrun.cpp b/indra/llcommon/llrun.cpp new file mode 100644 index 0000000000..0e6a80e766 --- /dev/null +++ b/indra/llcommon/llrun.cpp @@ -0,0 +1,156 @@ +/** + * @file llrun.cpp + * @author Phoenix + * @date 2006-02-16 + * @brief Implementation of the LLRunner and related classes + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llrun.h" + +#include "llframetimer.h" + +static const LLRunner::run_handle_t INVALID_RUN_HANDLE = 0; + +/** + * LLRunner + */ +LLRunner::LLRunner() : + mNextHandle(1) +{ +} + +LLRunner::~LLRunner() +{ + mRunOnce.clear(); + mRunEvery.clear(); +} + +S32 LLRunner::run() +{ + // We collect all of the runnables which should be run. Since the + // runnables are allowed to adjust the run list, we need to copy + // them into a temporary structure which then iterates over them + // to call out of this method into the runnables. + F64 now = LLFrameTimer::getTotalSeconds(); + run_list_t run_now; + + // Collect the run once. We erase the matching ones now because + // it's easier. If we find a reason to keep them around for a + // while, we can restructure this method. + LLRunner::run_list_t::iterator iter = mRunOnce.begin(); + for( ; iter != mRunOnce.end(); ) + { + if(now > (*iter).mNextRunAt) + { + run_now.push_back(*iter); + iter = mRunOnce.erase(iter); + } + else + { + ++iter; + } + } + + // Collect the ones that repeat. + iter = mRunEvery.begin(); + LLRunner::run_list_t::iterator end = mRunEvery.end(); + for( ; iter != end; ++iter ) + { + if(now > (*iter).mNextRunAt) + { + (*iter).mNextRunAt = now + (*iter).mIncrement; + run_now.push_back(*iter); + } + } + + // Now, run them. + iter = run_now.begin(); + end = run_now.end(); + for( ; iter != end; ++iter ) + { + (*iter).mRunnable->run(this, (*iter).mHandle); + } + return run_now.size(); +} + +LLRunner::run_handle_t LLRunner::addRunnable( + run_ptr_t runnable, + ERunSchedule schedule, + F64 seconds) +{ + if(!runnable) return INVALID_RUN_HANDLE; + run_handle_t handle = mNextHandle++; + F64 next_run = LLFrameTimer::getTotalSeconds() + seconds; + LLRunInfo info(handle, runnable, schedule, next_run, seconds); + switch(schedule) + { + case RUN_IN: + // We could optimize this a bit by sorting this on entry. + mRunOnce.push_back(info); + break; + case RUN_EVERY: + mRunEvery.push_back(info); + break; + default: + handle = INVALID_RUN_HANDLE; + break; + } + return handle; +} + +LLRunner::run_ptr_t LLRunner::removeRunnable(LLRunner::run_handle_t handle) +{ + LLRunner::run_ptr_t rv; + LLRunner::run_list_t::iterator iter = mRunOnce.begin(); + LLRunner::run_list_t::iterator end = mRunOnce.end(); + for( ; iter != end; ++iter) + { + if((*iter).mHandle == handle) + { + rv = (*iter).mRunnable; + mRunOnce.erase(iter); + return rv; + } + } + + iter = mRunEvery.begin(); + end = mRunEvery.end(); + for( ; iter != end; ++iter) + { + if((*iter).mHandle == handle) + { + rv = (*iter).mRunnable; + mRunEvery.erase(iter); + return rv; + } + } + return rv; +} + +/** + * LLRunner::LLRunInfo + */ +LLRunner::LLRunInfo::LLRunInfo( + run_handle_t handle, + run_ptr_t runnable, + ERunSchedule schedule, + F64 next_run_after, + F64 increment) : + mHandle(handle), + mRunnable(runnable), + mSchedule(schedule), + mNextRunAt(next_run_after), + mIncrement(increment) +{ +} + +LLRunnable::LLRunnable() +{ } + +// virtual +LLRunnable::~LLRunnable() +{ } diff --git a/indra/llcommon/llrun.h b/indra/llcommon/llrun.h new file mode 100644 index 0000000000..8aa747ebab --- /dev/null +++ b/indra/llcommon/llrun.h @@ -0,0 +1,144 @@ +/** + * @file llrun.h + * @author Phoenix + * @date 2006-02-16 + * @brief Declaration of LLRunner and LLRunnable classes. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLRUN_H +#define LL_LLRUN_H + +#include +#include + +class LLRunnable; + +/** + * @class LLRunner + * @brief This class manages a set of LLRunnable objects. + * + * An instance of this class has a collection of LLRunnable objects + * which are scheduled to run on a repeating or one time basis. + * @see LLRunnable + */ +class LLRunner +{ +public: + /** + * @brief The pointer to a runnable. + */ + typedef boost::shared_ptr run_ptr_t; + + /** + * @brief The handle for use in the API. + */ + typedef S64 run_handle_t; + + /** + * @brief Constructor. + */ + LLRunner(); + + /** + * @brief Destructor. + */ + ~LLRunner(); + + /** + * @brief Enumeration which specifies when to run. + */ + enum ERunSchedule + { + // The runnable will run in N seconds + RUN_IN, + + // The run every N seconds + RUN_EVERY, + + // A count of the run types + RUN_SCHEDULE_COUNT + }; + + /** + * @brief Run the runnables which are scheduled to run + * + * @return Returns the number of runnables run. + */ + S32 run(); + + /** + * @brief Add a runnable to the run list. + * + * The handle of the runnable is unique to each addition. If the + * same runnable is added a second time with the same or different + * schedule, this method will return a new handle. + * @param runnable The runnable to run() on schedule. + * @param schedule Specifies the run schedule. + * @param seconds When to run the runnable as interpreted by schedule. + * @return Returns the handle to the runnable. handle == 0 means failure. + */ + run_handle_t addRunnable( + run_ptr_t runnable, + ERunSchedule schedule, + F64 seconds); + + /** + * @brief Remove the specified runnable. + * + * @param handle The handle of the runnable to remove. + * @return Returns the pointer to the runnable removed which may + * be empty. + */ + run_ptr_t removeRunnable(run_handle_t handle); + +protected: + struct LLRunInfo + { + run_handle_t mHandle; + run_ptr_t mRunnable; + ERunSchedule mSchedule; + F64 mNextRunAt; + F64 mIncrement; + LLRunInfo( + run_handle_t handle, + run_ptr_t runnable, + ERunSchedule schedule, + F64 next_run_at, + F64 increment); + }; + typedef std::vector run_list_t; + run_list_t mRunOnce; + run_list_t mRunEvery; + run_handle_t mNextHandle; +}; + + +/** + * @class LLRunnable + * @brief Abstract base class for running some scheduled process. + * + * Users of the LLRunner class are expected to derive a concrete + * implementation of this class which overrides the run() method to do + * something useful. + * @see LLRunner + */ +class LLRunnable +{ +public: + LLRunnable(); + virtual ~LLRunnable(); + + /** + * @brief Do the process. + * + * This method will be called from the LLRunner according to + * @param runner The Runner which call run(). + * @param handle The handle this run instance is run under. + */ + virtual void run(LLRunner* runner, S64 handle) = 0; +}; + +#endif // LL_LLRUN_H diff --git a/indra/llcommon/llsd.cpp b/indra/llcommon/llsd.cpp new file mode 100644 index 0000000000..25bd7ceac8 --- /dev/null +++ b/indra/llcommon/llsd.cpp @@ -0,0 +1,731 @@ +/** + * @file llsd.cpp + * @brief LLSD flexible data system + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "llsd.h" + +#include +#include "../llmath/llmath.h" + +namespace { + class ImplMap; + class ImplArray; +} + +class LLSD::Impl + /**< This class is the abstract base class of the implementation of LLSD + It provides the reference counting implementation, and the default + implementation of most methods for most data types. It also serves + as a working implementation of the Undefined type. + + */ +{ +private: + U32 mUseCount; + +protected: + Impl(); + + enum StaticAllocationMarker { STATIC }; + Impl(StaticAllocationMarker); + ///< This constructor is used for static objects and causes the + // suppresses adjusting the debugging counters when they are + // finally initialized. + + virtual ~Impl(); + + bool shared() const { return mUseCount > 1; } + +public: + static void reset(Impl*& var, Impl* impl); + ///< safely set var to refer to the new impl (possibly shared) + + static Impl& safe( Impl*); + static const Impl& safe(const Impl*); + ///< since a NULL Impl* is used for undefined, this ensures there is + // always an object you call virtual member functions on + + virtual ImplMap& makeMap(Impl*& var); + virtual ImplArray& makeArray(Impl*& var); + ///< sure var is a modifiable, non-shared map or array + + virtual LLSD::Type type() const { return LLSD::TypeUndefined; } + + static void assignUndefined(LLSD::Impl*& var); + static void assign(LLSD::Impl*& var, const LLSD::Impl* other); + + virtual void assign(Impl*& var, LLSD::Boolean); + virtual void assign(Impl*& var, LLSD::Integer); + virtual void assign(Impl*& var, LLSD::Real); + virtual void assign(Impl*& var, const LLSD::String&); + virtual void assign(Impl*& var, const LLSD::UUID&); + virtual void assign(Impl*& var, const LLSD::Date&); + virtual void assign(Impl*& var, const LLSD::URI&); + virtual void assign(Impl*& var, const LLSD::Binary&); + ///< If the receiver is the right type and unshared, these are simple + // data assignments, othewise the default implementation handless + // constructing the proper Impl subclass + + virtual Boolean asBoolean() const { return false; } + virtual Integer asInteger() const { return 0; } + virtual Real asReal() const { return 0.0; } + virtual String asString() const { return std::string(); } + virtual UUID asUUID() const { return LLUUID(); } + virtual Date asDate() const { return LLDate(); } + virtual URI asURI() const { return LLURI(); } + virtual Binary asBinary() const { return std::vector(); } + + virtual bool has(const String&) const { return false; } + virtual LLSD get(const String&) const { return LLSD(); } + virtual void erase(const String&) { } + virtual const LLSD& ref(const String&) const{ return undef(); } + + virtual int size() const { return 0; } + virtual LLSD get(Integer) const { return LLSD(); } + virtual void erase(Integer) { } + virtual const LLSD& ref(Integer) const { return undef(); } + + virtual LLSD::map_const_iterator beginMap() const { return LLSD::map_const_iterator(); } + virtual LLSD::map_const_iterator endMap() const { return LLSD::map_const_iterator(); } + virtual LLSD::array_const_iterator beginArray() const { return LLSD::array_const_iterator(); } + virtual LLSD::array_const_iterator endArray() const { return LLSD::array_const_iterator(); } + + static const LLSD& undef(); + + static U32 sAllocationCount; + static U32 sOutstandingCount; +}; + +namespace { + template + class ImplBase : public LLSD::Impl + ///< This class handles most of the work for a subclass of Impl + // for a given simple data type. Subclasses of this provide the + // conversion functions and a constructor. + { + protected: + Data mValue; + + typedef ImplBase Base; + + public: + ImplBase(DataRef value) : mValue(value) { } + + virtual LLSD::Type type() const { return T; } + + virtual void assign(LLSD::Impl*& var, DataRef value) { + if (shared()) + { + Impl::assign(var, value); + } + else + { + mValue = value; + } + } + }; + + + class ImplBoolean + : public ImplBase + { + public: + ImplBoolean(LLSD::Boolean v) : Base(v) { } + + virtual LLSD::Boolean asBoolean() const { return mValue; } + virtual LLSD::Integer asInteger() const { return mValue ? 1 : 0; } + virtual LLSD::Real asReal() const { return mValue ? 1 : 0; } + virtual LLSD::String asString() const; + }; + + LLSD::String ImplBoolean::asString() const + { return mValue ? "true" : ""; } + + + class ImplInteger + : public ImplBase + { + public: + ImplInteger(LLSD::Integer v) : Base(v) { } + + virtual LLSD::Boolean asBoolean() const { return mValue != 0; } + virtual LLSD::Integer asInteger() const { return mValue; } + virtual LLSD::Real asReal() const { return mValue; } + virtual LLSD::String asString() const; + }; + + LLSD::String ImplInteger::asString() const + { return llformat("%d", mValue); } + + + class ImplReal + : public ImplBase + { + public: + ImplReal(LLSD::Real v) : Base(v) { } + + virtual LLSD::Boolean asBoolean() const; + virtual LLSD::Integer asInteger() const; + virtual LLSD::Real asReal() const { return mValue; } + virtual LLSD::String asString() const; + }; + + LLSD::Boolean ImplReal::asBoolean() const + { return !llisnan(mValue) && mValue != 0.0; } + + LLSD::Integer ImplReal::asInteger() const + { return !llisnan(mValue) ? (LLSD::Integer)mValue : 0; } + + LLSD::String ImplReal::asString() const + { return llformat("%lg", mValue); } + + + class ImplString + : public ImplBase + { + public: + ImplString(const LLSD::String& v) : Base(v) { } + + virtual LLSD::Boolean asBoolean() const { return !mValue.empty(); } + virtual LLSD::Integer asInteger() const; + virtual LLSD::Real asReal() const; + virtual LLSD::String asString() const { return mValue; } + virtual LLSD::UUID asUUID() const { return LLUUID(mValue); } + virtual LLSD::Date asDate() const { return LLDate(mValue); } + virtual LLSD::URI asURI() const { return LLURI(mValue); } + }; + + LLSD::Integer ImplString::asInteger() const + { + // This must treat "1.23" not as an error, but as a number, which is + // then truncated down to an integer. Hence, this code doesn't call + // std::istringstream::operator>>(int&), which would not consume the + // ".23" portion. + + return (int)asReal(); + } + + LLSD::Real ImplString::asReal() const + { + F64 v = 0.0; + std::istringstream i_stream(mValue); + i_stream >> v; + + // we would probably like to ignore all trailing whitespace as + // well, but for now, simply eat the next character, and make + // sure we reached the end of the string. + // *NOTE: gcc 2.95 does not generate an eof() event on the + // stream operation above, so we manually get here to force it + // across platforms. + int c = i_stream.get(); + return ((EOF ==c) ? v : 0.0); + } + + + class ImplUUID + : public ImplBase + { + public: + ImplUUID(const LLSD::UUID& v) : Base(v) { } + + virtual LLSD::String asString() const{ return mValue.getString(); } + virtual LLSD::UUID asUUID() const { return mValue; } + }; + + + class ImplDate + : public ImplBase + { + public: + ImplDate(const LLSD::Date& v) + : ImplBase(v) + { } + + virtual LLSD::Integer asInteger() const + { + return (LLSD::Integer)(mValue.secondsSinceEpoch()); + } + virtual LLSD::Real asReal() const + { + return mValue.secondsSinceEpoch(); + } + virtual LLSD::String asString() const{ return mValue.asString(); } + virtual LLSD::Date asDate() const { return mValue; } + }; + + + class ImplURI + : public ImplBase + { + public: + ImplURI(const LLSD::URI& v) : Base(v) { } + + virtual LLSD::String asString() const{ return mValue.asString(); } + virtual LLSD::URI asURI() const { return mValue; } + }; + + + class ImplBinary + : public ImplBase + { + public: + ImplBinary(const LLSD::Binary& v) : Base(v) { } + + virtual LLSD::Binary asBinary() const{ return mValue; } + }; + + + class ImplMap : public LLSD::Impl + { + private: + typedef std::map DataMap; + + DataMap mData; + + protected: + ImplMap(const DataMap& data) : mData(data) { } + + public: + ImplMap() { } + + virtual ImplMap& makeMap(LLSD::Impl*&); + + virtual LLSD::Type type() const { return LLSD::TypeMap; } + + virtual LLSD::Boolean asBoolean() const { return !mData.empty(); } + + virtual bool has(const LLSD::String&) const; + virtual LLSD get(const LLSD::String&) const; + void insert(const LLSD::String& k, const LLSD& v); + virtual void erase(const LLSD::String&); + LLSD& ref(const LLSD::String&); + virtual const LLSD& ref(const LLSD::String&) const; + + virtual int size() const { return mData.size(); } + + LLSD::map_iterator beginMap() { return mData.begin(); } + LLSD::map_iterator endMap() { return mData.end(); } + virtual LLSD::map_const_iterator beginMap() const { return mData.begin(); } + virtual LLSD::map_const_iterator endMap() const { return mData.end(); } + }; + + ImplMap& ImplMap::makeMap(LLSD::Impl*& var) + { + if (shared()) + { + ImplMap* i = new ImplMap(mData); + Impl::assign(var, i); + return *i; + } + else + { + return *this; + } + } + + bool ImplMap::has(const LLSD::String& k) const + { + DataMap::const_iterator i = mData.find(k); + return i != mData.end(); + } + + LLSD ImplMap::get(const LLSD::String& k) const + { + DataMap::const_iterator i = mData.find(k); + return (i != mData.end()) ? i->second : LLSD(); + } + + void ImplMap::insert(const LLSD::String& k, const LLSD& v) + { + mData.insert(DataMap::value_type(k, v)); + } + + void ImplMap::erase(const LLSD::String& k) + { + mData.erase(k); + } + + LLSD& ImplMap::ref(const LLSD::String& k) + { + return mData[k]; + } + + const LLSD& ImplMap::ref(const LLSD::String& k) const + { + DataMap::const_iterator i = mData.lower_bound(k); + if (i == mData.end() || mData.key_comp()(k, i->first)) + { + return undef(); + } + + return i->second; + } + + class ImplArray : public LLSD::Impl + { + private: + typedef std::vector DataVector; + + DataVector mData; + + protected: + ImplArray(const DataVector& data) : mData(data) { } + + public: + ImplArray() { } + + virtual ImplArray& makeArray(Impl*&); + + virtual LLSD::Type type() const { return LLSD::TypeArray; } + + virtual LLSD::Boolean asBoolean() const { return !mData.empty(); } + + virtual int size() const; + virtual LLSD get(LLSD::Integer) const; + void set(LLSD::Integer, const LLSD&); + void insert(LLSD::Integer, const LLSD&); + void append(const LLSD&); + virtual void erase(LLSD::Integer); + LLSD& ref(LLSD::Integer); + virtual const LLSD& ref(LLSD::Integer) const; + + LLSD::array_iterator beginArray() { return mData.begin(); } + LLSD::array_iterator endArray() { return mData.end(); } + virtual LLSD::array_const_iterator beginArray() const { return mData.begin(); } + virtual LLSD::array_const_iterator endArray() const { return mData.end(); } + }; + + ImplArray& ImplArray::makeArray(Impl*& var) + { + if (shared()) + { + ImplArray* i = new ImplArray(mData); + Impl::assign(var, i); + return *i; + } + else + { + return *this; + } + } + + int ImplArray::size() const { return mData.size(); } + + LLSD ImplArray::get(LLSD::Integer i) const + { + if (i < 0) { return LLSD(); } + DataVector::size_type index = i; + + return (index < mData.size()) ? mData[index] : LLSD(); + } + + void ImplArray::set(LLSD::Integer i, const LLSD& v) + { + if (i < 0) { return; } + DataVector::size_type index = i; + + if (index >= mData.size()) + { + mData.resize(index + 1); + } + + mData[index] = v; + } + + void ImplArray::insert(LLSD::Integer i, const LLSD& v) + { + if (i < 0) { return; } + DataVector::size_type index = i; + + if (index >= mData.size()) + { + mData.resize(index + 1); + } + + mData.insert(mData.begin() + index, v); + } + + void ImplArray::append(const LLSD& v) + { + mData.push_back(v); + } + + void ImplArray::erase(LLSD::Integer i) + { + if (i < 0) { return; } + DataVector::size_type index = i; + + if (index < mData.size()) + { + mData.erase(mData.begin() + index); + } + } + + LLSD& ImplArray::ref(LLSD::Integer i) + { + DataVector::size_type index = i >= 0 ? i : 0; + + if (index >= mData.size()) + { + mData.resize(i + 1); + } + + return mData[index]; + } + + const LLSD& ImplArray::ref(LLSD::Integer i) const + { + if (i < 0) { return undef(); } + DataVector::size_type index = i; + + if (index >= mData.size()) + { + return undef(); + } + + return mData[index]; + } +} + +LLSD::Impl::Impl() + : mUseCount(0) +{ + ++sAllocationCount; + ++sOutstandingCount; +} + +LLSD::Impl::Impl(StaticAllocationMarker) + : mUseCount(0) +{ +} + +LLSD::Impl::~Impl() +{ + --sOutstandingCount; +} + +void LLSD::Impl::reset(Impl*& var, Impl* impl) +{ + if (impl) ++impl->mUseCount; + if (var && --var->mUseCount == 0) + { + delete var; + } + var = impl; +} + +LLSD::Impl& LLSD::Impl::safe(Impl* impl) +{ + static Impl theUndefined(STATIC); + return impl ? *impl : theUndefined; +} + +const LLSD::Impl& LLSD::Impl::safe(const Impl* impl) +{ + static Impl theUndefined(STATIC); + return impl ? *impl : theUndefined; +} + +ImplMap& LLSD::Impl::makeMap(Impl*& var) +{ + ImplMap* im = new ImplMap; + reset(var, im); + return *im; +} + +ImplArray& LLSD::Impl::makeArray(Impl*& var) +{ + ImplArray* ia = new ImplArray; + reset(var, ia); + return *ia; +} + + +void LLSD::Impl::assign(Impl*& var, const Impl* other) +{ + reset(var, const_cast(other)); +} + +void LLSD::Impl::assignUndefined(Impl*& var) +{ + reset(var, 0); +} + +void LLSD::Impl::assign(Impl*& var, LLSD::Boolean v) +{ + reset(var, new ImplBoolean(v)); +} + +void LLSD::Impl::assign(Impl*& var, LLSD::Integer v) +{ + reset(var, new ImplInteger(v)); +} + +void LLSD::Impl::assign(Impl*& var, LLSD::Real v) +{ + reset(var, new ImplReal(v)); +} + +void LLSD::Impl::assign(Impl*& var, const LLSD::String& v) +{ + reset(var, new ImplString(v)); +} + +void LLSD::Impl::assign(Impl*& var, const LLSD::UUID& v) +{ + reset(var, new ImplUUID(v)); +} + +void LLSD::Impl::assign(Impl*& var, const LLSD::Date& v) +{ + reset(var, new ImplDate(v)); +} + +void LLSD::Impl::assign(Impl*& var, const LLSD::URI& v) +{ + reset(var, new ImplURI(v)); +} + +void LLSD::Impl::assign(Impl*& var, const LLSD::Binary& v) +{ + reset(var, new ImplBinary(v)); +} + + +const LLSD& LLSD::Impl::undef() +{ + static const LLSD immutableUndefined; + return immutableUndefined; +} + +U32 LLSD::Impl::sAllocationCount = 0; +U32 LLSD::Impl::sOutstandingCount = 0; + + + +namespace { + inline LLSD::Impl& safe(LLSD::Impl* impl) + { return LLSD::Impl::safe(impl); } + + inline const LLSD::Impl& safe(const LLSD::Impl* impl) + { return LLSD::Impl::safe(impl); } + + inline ImplMap& makeMap(LLSD::Impl*& var) + { return safe(var).makeMap(var); } + + inline ImplArray& makeArray(LLSD::Impl*& var) + { return safe(var).makeArray(var); } +} + + +LLSD::LLSD() : impl(0) { } +LLSD::~LLSD() { Impl::reset(impl, 0); } + +LLSD::LLSD(const LLSD& other) : impl(0) { assign(other); } +void LLSD::assign(const LLSD& other) { Impl::assign(impl, other.impl); } + + +void LLSD::clear() { Impl::assignUndefined(impl); } + +LLSD::Type LLSD::type() const { return safe(impl).type(); } + +// Scaler Constructors +LLSD::LLSD(Boolean v) : impl(0) { assign(v); } +LLSD::LLSD(Integer v) : impl(0) { assign(v); } +LLSD::LLSD(Real v) : impl(0) { assign(v); } +LLSD::LLSD(const UUID& v) : impl(0) { assign(v); } +LLSD::LLSD(const String& v) : impl(0) { assign(v); } +LLSD::LLSD(const Date& v) : impl(0) { assign(v); } +LLSD::LLSD(const URI& v) : impl(0) { assign(v); } +LLSD::LLSD(const Binary& v) : impl(0) { assign(v); } + +// Convenience Constructors +LLSD::LLSD(F32 v) : impl(0) { assign((Real)v); } + +// Scalar Assignment +void LLSD::assign(Boolean v) { safe(impl).assign(impl, v); } +void LLSD::assign(Integer v) { safe(impl).assign(impl, v); } +void LLSD::assign(Real v) { safe(impl).assign(impl, v); } +void LLSD::assign(const String& v) { safe(impl).assign(impl, v); } +void LLSD::assign(const UUID& v) { safe(impl).assign(impl, v); } +void LLSD::assign(const Date& v) { safe(impl).assign(impl, v); } +void LLSD::assign(const URI& v) { safe(impl).assign(impl, v); } +void LLSD::assign(const Binary& v) { safe(impl).assign(impl, v); } + +// Scalar Accessors +LLSD::Boolean LLSD::asBoolean() const { return safe(impl).asBoolean(); } +LLSD::Integer LLSD::asInteger() const { return safe(impl).asInteger(); } +LLSD::Real LLSD::asReal() const { return safe(impl).asReal(); } +LLSD::String LLSD::asString() const { return safe(impl).asString(); } +LLSD::UUID LLSD::asUUID() const { return safe(impl).asUUID(); } +LLSD::Date LLSD::asDate() const { return safe(impl).asDate(); } +LLSD::URI LLSD::asURI() const { return safe(impl).asURI(); } +LLSD::Binary LLSD::asBinary() const { return safe(impl).asBinary(); } + +// const char * helpers +LLSD::LLSD(const char* v) : impl(0) { assign(v); } +void LLSD::assign(const char* v) +{ + if(v) assign(std::string(v)); + else assign(std::string()); +} + + +LLSD LLSD::emptyMap() +{ + LLSD v; + makeMap(v.impl); + return v; +} + +bool LLSD::has(const String& k) const { return safe(impl).has(k); } +LLSD LLSD::get(const String& k) const { return safe(impl).get(k); } + +void LLSD::insert(const String& k, const LLSD& v) + { makeMap(impl).insert(k, v); } +void LLSD::erase(const String& k) { makeMap(impl).erase(k); } + +LLSD& LLSD::operator[](const String& k) + { return makeMap(impl).ref(k); } +const LLSD& LLSD::operator[](const String& k) const + { return safe(impl).ref(k); } + + +LLSD LLSD::emptyArray() +{ + LLSD v; + makeArray(v.impl); + return v; +} + +int LLSD::size() const { return safe(impl).size(); } + +LLSD LLSD::get(Integer i) const { return safe(impl).get(i); } +void LLSD::set(Integer i, const LLSD& v){ makeArray(impl).set(i, v); } + +void LLSD::insert(Integer i, const LLSD& v) + { makeArray(impl).insert(i, v); } +void LLSD::append(const LLSD& v) { makeArray(impl).append(v); } +void LLSD::erase(Integer i) { makeArray(impl).erase(i); } + +LLSD& LLSD::operator[](Integer i) + { return makeArray(impl).ref(i); } +const LLSD& LLSD::operator[](Integer i) const + { return safe(impl).ref(i); } + +U32 LLSD::allocationCount() { return Impl::sAllocationCount; } +U32 LLSD::outstandingCount() { return Impl::sOutstandingCount; } + +LLSD::map_iterator LLSD::beginMap() { return makeMap(impl).beginMap(); } +LLSD::map_iterator LLSD::endMap() { return makeMap(impl).endMap(); } +LLSD::map_const_iterator LLSD::beginMap() const { return safe(impl).beginMap(); } +LLSD::map_const_iterator LLSD::endMap() const { return safe(impl).endMap(); } + +LLSD::array_iterator LLSD::beginArray() { return makeArray(impl).beginArray(); } +LLSD::array_iterator LLSD::endArray() { return makeArray(impl).endArray(); } +LLSD::array_const_iterator LLSD::beginArray() const{ return safe(impl).beginArray(); } +LLSD::array_const_iterator LLSD::endArray() const { return safe(impl).endArray(); } diff --git a/indra/llcommon/llsd.h b/indra/llcommon/llsd.h new file mode 100644 index 0000000000..97bd6fe01c --- /dev/null +++ b/indra/llcommon/llsd.h @@ -0,0 +1,366 @@ +/** + * @file llsd.h + * @brief LLSD flexible data system. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSD_NEW_H +#define LL_LLSD_NEW_H + +#include +#include +#include + +#include "stdtypes.h" + +#include "lldate.h" +#include "lluri.h" +#include "../llmath/lluuid.h" + +/** + LLSD provides a flexible data system similar to the data facilities of + dynamic languages like Perl and Python. It is created to support exchange + of structured data between loosly coupled systems. (Here, "loosly coupled" + means not compiled together into the same module.) + + Data in such exchanges must be highly tollerant of changes on either side + such as: + - recompilation + - implementation in a different langauge + - addition of extra parameters + - execution of older versions (with fewer parameters) + + To this aim, the C++ API of LLSD strives to be very easy to use, and to + default to "the right thing" whereever possible. It is extremely tollerant + of errors and unexpected situations. + + The fundimental class is LLSD. LLSD is a value holding object. It holds + one value that is either undefined, one of the scalar types, or a map or an + array. LLSD objects have value semantics (copying them copies the value, + though it can be considered efficient, due to shareing.), and mutable. + + Undefined is the singular value given to LLSD objects that are not + initialized with any data. It is also used as the return value for + operations that return an LLSD, + + The sclar data types are: + - Boolean - true or false + - Integer - a 32 bit signed integer + - Real - a 64 IEEE 754 floating point value + - UUID - a 128 unique value + - String - a sequence of zero or more Unicode chracters + - Date - an absolute point in time, UTC, + with resolution to the second + - URI - a String that is a URI + - Binary - a sequence of zero or more octets (unsigned bytes) + + A map is a dictionary mapping String keys to LLSD values. The keys are + unique within a map, and have only one value (though that value could be + an LLSD array). + + An array is a sequence of zero or more LLSD values. + + @nosubgrouping +*/ + +class LLSD +{ +public: + LLSD(); ///< initially Undefined + ~LLSD(); ///< this class may NOT be subclassed + + /** @name Copyable and Assignable */ + //@{ + LLSD(const LLSD&); + void assign(const LLSD& other); + LLSD& operator=(const LLSD& other) { assign(other); return *this; } + + //@} + + void clear(); ///< resets to Undefined + + + /** @name Scalar Types + The scalar types, and how they map onto C++ + */ + //@{ + typedef bool Boolean; + typedef S32 Integer; + typedef F64 Real; + typedef std::string String; + typedef LLUUID UUID; + typedef LLDate Date; + typedef LLURI URI; + typedef std::vector Binary; + //@} + + /** @name Scalar Constructors */ + //@{ + LLSD(Boolean); + LLSD(Integer); + LLSD(Real); + LLSD(const String&); + LLSD(const UUID&); + LLSD(const Date&); + LLSD(const URI&); + LLSD(const Binary&); + //@} + + /** @name Convenience Constructors */ + //@{ + LLSD(F32); // F32 -> Real + //@} + + /** @name Scalar Assignment */ + //@{ + void assign(Boolean); + void assign(Integer); + void assign(Real); + void assign(const String&); + void assign(const UUID&); + void assign(const Date&); + void assign(const URI&); + void assign(const Binary&); + + LLSD& operator=(Boolean v) { assign(v); return *this; } + LLSD& operator=(Integer v) { assign(v); return *this; } + LLSD& operator=(Real v) { assign(v); return *this; } + LLSD& operator=(const String& v) { assign(v); return *this; } + LLSD& operator=(const UUID& v) { assign(v); return *this; } + LLSD& operator=(const Date& v) { assign(v); return *this; } + LLSD& operator=(const URI& v) { assign(v); return *this; } + LLSD& operator=(const Binary& v) { assign(v); return *this; } + //@} + + /** + @name Scalar Accessors + @brief Fetch a scalar value, converting if needed and possible + + Conversion among the basic types, Boolean, Integer, Real and String, is + fully defined. Each type can be converted to another with a reasonable + interpretation. These conversions can be used as a convenience even + when you know the data is in one format, but you want it in another. Of + course, many of these conversions lose information. + + Note: These conversions are not the same as Perl's. In particular, when + converting a String to a Boolean, only the empty string converts to + false. Converting the String "0" to Boolean results in true. + + Conversion to and from UUID, Date, and URI is only defined to and from + String. Conversion is defined to be information preserving for valid + values of those types. These conversions can be used when one needs to + convert data to or from another system that cannot handle these types + natively, but can handle strings. + + Conversion to and from Binary isn't defined. + + Conversion of the Undefined value to any scalar type results in a + reasonable null or zero value for the type. + */ + //@{ + Boolean asBoolean() const; + Integer asInteger() const; + Real asReal() const; + String asString() const; + UUID asUUID() const; + Date asDate() const; + URI asURI() const; + Binary asBinary() const; + + operator Boolean() const { return asBoolean(); } + operator Integer() const { return asInteger(); } + operator Real() const { return asReal(); } + operator String() const { return asString(); } + operator UUID() const { return asUUID(); } + operator Date() const { return asDate(); } + operator URI() const { return asURI(); } + operator Binary() const { return asBinary(); } + + // This is needed because most platforms do not automatically + // convert the boolean negation as a bool in an if statement. + bool operator!() const {return !asBoolean();} + //@} + + /** @name Character Pointer Helpers + These are helper routines to make working with char* the same as easy as + working with strings. + */ + //@{ + LLSD(const char*); + void assign(const char*); + LLSD& operator=(const char* v) { assign(v); return *this; } + //@} + + /** @name Map Values */ + //@{ + static LLSD emptyMap(); + + bool has(const String&) const; + LLSD get(const String&) const; + void insert(const String&, const LLSD&); + void erase(const String&); + + LLSD& operator[](const String&); + LLSD& operator[](const char* c) { return (*this)[String(c)]; } + const LLSD& operator[](const String&) const; + const LLSD& operator[](const char* c) const { return (*this)[String(c)]; } + //@} + + /** @name Array Values */ + //@{ + static LLSD emptyArray(); + + LLSD get(Integer) const; + void set(Integer, const LLSD&); + void insert(Integer, const LLSD&); + void append(const LLSD&); + void erase(Integer); + + const LLSD& operator[](Integer) const; + LLSD& operator[](Integer); + //@} + + /** @name Iterators */ + //@{ + int size() const; + + typedef std::map::iterator map_iterator; + typedef std::map::const_iterator map_const_iterator; + + map_iterator beginMap(); + map_iterator endMap(); + map_const_iterator beginMap() const; + map_const_iterator endMap() const; + + typedef std::vector::iterator array_iterator; + typedef std::vector::const_iterator array_const_iterator; + + array_iterator beginArray(); + array_iterator endArray(); + array_const_iterator beginArray() const; + array_const_iterator endArray() const; + //@} + + /** @name Type Testing */ + //@{ + enum Type { + TypeUndefined, + TypeBoolean, + TypeInteger, + TypeReal, + TypeString, + TypeUUID, + TypeDate, + TypeURI, + TypeBinary, + TypeMap, + TypeArray + }; + + Type type() const; + + bool isUndefined() const { return type() == TypeUndefined; } + bool isDefined() const { return type() != TypeUndefined; } + bool isBoolean() const { return type() == TypeBoolean; } + bool isInteger() const { return type() == TypeInteger; } + bool isReal() const { return type() == TypeReal; } + bool isString() const { return type() == TypeString; } + bool isUUID() const { return type() == TypeUUID; } + bool isDate() const { return type() == TypeDate; } + bool isURI() const { return type() == TypeURI; } + bool isBinary() const { return type() == TypeBinary; } + bool isMap() const { return type() == TypeMap; } + bool isArray() const { return type() == TypeArray; } + //@} + + /** @name Automatic Cast Protection + These are not implemented on purpose. Without them, C++ can perform + some conversions that are clearly not what the programmer intended. + + If you get a linker error about these being missing, you have made + mistake in your code. DO NOT IMPLEMENT THESE FUNCTIONS as a fix. + + All of thse problems stem from trying to support char* in LLSD or in + std::string. There are too many automatic casts that will lead to + using an arbitrary pointer or scalar type to std::string. + */ + //@{ + LLSD(const void*); ///< construct from aribrary pointers + void assign(const void*); ///< assign from arbitrary pointers + LLSD& operator=(const void*); ///< assign from arbitrary pointers + + bool has(Integer) const; ///< has only works for Maps + //@} + + /** @name Implementation */ + //@{ +public: + class Impl; +private: + Impl* impl; + //@} + + /** @name Unit Testing Interface */ + //@{ +public: + static U32 allocationCount(); ///< how many Impls have been made + static U32 outstandingCount(); ///< how many Impls are still alive + //@} +}; + +struct llsd_select_bool : public std::unary_function +{ + LLSD::Boolean operator()(const LLSD& sd) const + { + return sd.asBoolean(); + } +}; +struct llsd_select_integer : public std::unary_function +{ + LLSD::Integer operator()(const LLSD& sd) const + { + return sd.asInteger(); + } +}; +struct llsd_select_real : public std::unary_function +{ + LLSD::Real operator()(const LLSD& sd) const + { + return sd.asReal(); + } +}; +struct llsd_select_float : public std::unary_function +{ + F32 operator()(const LLSD& sd) const + { + return (F32)sd.asReal(); + } +}; +struct llsd_select_uuid : public std::unary_function +{ + LLSD::UUID operator()(const LLSD& sd) const + { + return sd.asUUID(); + } +}; +struct llsd_select_string : public std::unary_function +{ + LLSD::String operator()(const LLSD& sd) const + { + return sd.asString(); + } +}; + + +/** QUESTIONS & TO DOS + - Would Binary be more convenient as usigned char* buffer semantics? + - Should Binary be convertable to/from String, and if so how? + - as UTF8 encoded strings (making not like UUID<->String) + - as Base64 or Base96 encoded (making like UUID<->String) + - Conversions to std::string and LLUUID do not result in easy assignment + to std::string, LLString or LLUUID due to non-unique conversion paths +*/ + +#endif // LL_LLSD_NEW_H diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp new file mode 100644 index 0000000000..fb8efc91ca --- /dev/null +++ b/indra/llcommon/llsdserialize.cpp @@ -0,0 +1,1621 @@ +/** + * @file llsdserialize.cpp + * @author Phoenix + * @date 2006-03-05 + * @brief Implementation of LLSD parsers and formatters + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llsdserialize.h" +#include "llmemory.h" +#include "llstreamtools.h" // for fullread + +#include +#include "apr-1/apr_base64.h" + +#if !LL_WINDOWS +#include // htonl & ntohl +#endif + +#include "lldate.h" +#include "llsd.h" +#include "lluri.h" + +// File constants +static const int MAX_HDR_LEN = 20; +static const char LEGACY_NON_HEADER[] = ""; + +//static +const char* LLSDSerialize::LLSDBinaryHeader = "LLSD/Binary"; + +//static +const char* LLSDSerialize::LLSDXMLHeader = "LLSD/XML"; + +// virtual +LLSDParser::~LLSDParser() +{ } + +// virtual +LLSDNotationParser::~LLSDNotationParser() +{ } + + +// static +void LLSDSerialize::serialize(const LLSD& sd, std::ostream& str, ELLSD_Serialize type, U32 options) +{ + LLPointer f = NULL; + + switch (type) + { + case LLSD_BINARY: + str << "\n"; + f = new LLSDBinaryFormatter; + break; + + case LLSD_XML: + str << "\n"; + f = new LLSDXMLFormatter; + break; + + default: + llwarns << "serialize request for unkown ELLSD_Serialize" << llendl; + } + + if (f.notNull()) + { + f->format(sd, str, options); + } +} + +// static +bool LLSDSerialize::deserialize(LLSD& sd, std::istream& str) +{ + LLPointer p = NULL; + char hdr_buf[MAX_HDR_LEN + 1] = ""; /* Flawfinder: ignore */ + int i; + int inbuf = 0; + bool legacy_no_header = false; + bool fail_if_not_legacy = false; + std::string header = ""; + + /* + * Get the first line before anything. + */ + str.get(hdr_buf, MAX_HDR_LEN, '\n'); + if (str.fail()) + { + str.clear(); + fail_if_not_legacy = true; + } + + if (!strncasecmp(LEGACY_NON_HEADER, hdr_buf, strlen(LEGACY_NON_HEADER))) /* Flawfinder: ignore */ + { + legacy_no_header = true; + inbuf = str.gcount(); + } + else + { + if (fail_if_not_legacy) + goto fail; + /* + * Remove the newline chars + */ + for (i = 0; i < MAX_HDR_LEN; i++) + { + if (hdr_buf[i] == 0 || hdr_buf[i] == '\r' || + hdr_buf[i] == '\n') + { + hdr_buf[i] = 0; + break; + } + } + header = hdr_buf; + + std::string::size_type start = std::string::npos; + std::string::size_type end = std::string::npos; + start = header.find_first_not_of("parsePart(hdr_buf, inbuf); + p = x; + } + else if (header == LLSDBinaryHeader) + { + p = new LLSDBinaryParser; + } + else if (header == LLSDXMLHeader) + { + p = new LLSDXMLParser; + } + else + { + llwarns << "deserialize request for unknown ELLSD_Serialize" << llendl; + } + + if (p.notNull()) + { + p->parse(str, sd); + return true; + } + +fail: + llwarns << "deserialize LLSD parse failure" << llendl; + return false; +} + +/** + * Endian handlers + */ +#if LL_BIG_ENDIAN +U64 ll_htonll(U64 hostlonglong) { return hostlonglong; } +U64 ll_ntohll(U64 netlonglong) { return netlonglong; } +F64 ll_htond(F64 hostlonglong) { return hostlonglong; } +F64 ll_ntohd(F64 netlonglong) { return netlonglong; } +#else +// I read some comments one a indicating that doing an integer add +// here would be faster than a bitwise or. For now, the or has +// programmer clarity, since the intended outcome matches the +// operation. +U64 ll_htonll(U64 hostlonglong) +{ + return ((U64)(htonl((U32)((hostlonglong >> 32) & 0xFFFFFFFF))) | + ((U64)(htonl((U32)(hostlonglong & 0xFFFFFFFF))) << 32)); +} +U64 ll_ntohll(U64 netlonglong) +{ + return ((U64)(ntohl((U32)((netlonglong >> 32) & 0xFFFFFFFF))) | + ((U64)(ntohl((U32)(netlonglong & 0xFFFFFFFF))) << 32)); +} +union LLEndianSwapper +{ + F64 d; + U64 i; +}; +F64 ll_htond(F64 hostdouble) +{ + LLEndianSwapper tmp; + tmp.d = hostdouble; + tmp.i = ll_htonll(tmp.i); + return tmp.d; +} +F64 ll_ntohd(F64 netdouble) +{ + LLEndianSwapper tmp; + tmp.d = netdouble; + tmp.i = ll_ntohll(tmp.i); + return tmp.d; +} +#endif + +/** + * Local functions. + */ +bool deserialize_string(std::istream& str, std::string& value); +bool deserialize_string_delim(std::istream& str, std::string& value, char d); +bool deserialize_string_raw(std::istream& str, std::string& value); +void serialize_string(const std::string& value, std::ostream& str); + +/** + * Local constants. + */ +static const std::string NOTATION_TRUE_SERIAL("true"); +static const std::string NOTATION_FALSE_SERIAL("false"); + +static const char BINARY_TRUE_SERIAL = '1'; +static const char BINARY_FALSE_SERIAL = '0'; + +static const S32 NOTATION_PARSE_FAILURE = -1; + +/** + * LLSDParser + */ +LLSDParser::LLSDParser() +{ +} + +/** + * LLSDNotationParser + */ +// virtual +S32 LLSDNotationParser::parse(std::istream& istr, LLSD& data) const +{ + // map: { string:object, string:object } + // array: [ object, object, object ] + // undef: ! + // boolean: true | false | 1 | 0 | T | F | t | f | TRUE | FALSE + // integer: i#### + // real: r#### + // uuid: u#### + // string: "g'day" | 'have a "nice" day' | s(size)"raw data" + // uri: l"escaped" + // date: d"YYYY-MM-DDTHH:MM:SS.FFZ" + // binary: b##"ff3120ab1" | b(size)"raw data" + char c; + c = istr.peek(); + while(isspace(c)) + { + // pop the whitespace. + c = istr.get(); + c = istr.peek(); + continue; + } + if(!istr.good()) + { + return 0; + } + S32 parse_count = 1; + switch(c) + { + case '{': + parse_count += parseMap(istr, data); + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading map." << llendl; + } + if(data.isUndefined()) + { + parse_count = NOTATION_PARSE_FAILURE; + } + break; + + case '[': + parse_count += parseArray(istr, data); + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading array." << llendl; + } + if(data.isUndefined()) + { + parse_count = NOTATION_PARSE_FAILURE; + } + break; + + case '!': + c = istr.get(); + data.clear(); + break; + + case '0': + c = istr.get(); + data = false; + break; + + case 'F': + case 'f': + do + { + istr.ignore(); + c = istr.peek(); + } while (isalpha(c)); + data = false; + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading boolean." << llendl; + } + break; + + case '1': + c = istr.get(); + data = true; + break; + + case 'T': + case 't': + do + { + istr.ignore(); + c = istr.peek(); + } while (isalpha(c)); + data = true; + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading boolean." << llendl; + } + break; + + case 'i': + { + c = istr.get(); + S32 integer = 0; + istr >> integer; + data = integer; + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading integer." << llendl; + } + break; + } + + case 'r': + { + c = istr.get(); + F64 real = 0.0; + istr >> real; + data = real; + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading real." << llendl; + } + break; + } + + case 'u': + { + c = istr.get(); + LLUUID id; + istr >> id; + data = id; + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading uuid." << llendl; + } + break; + } + + case '\"': + case '\'': + case 's': + parseString(istr, data); + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading string." << llendl; + } + if(data.isUndefined()) + { + parse_count = NOTATION_PARSE_FAILURE; + } + break; + + case 'l': + { + c = istr.get(); // pop the 'l' + c = istr.get(); // pop the delimiter + std::string str; + deserialize_string_delim(istr, str, c); + data = LLURI(str); + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading link." << llendl; + } + break; + } + + case 'd': + { + c = istr.get(); // pop the 'd' + c = istr.get(); // pop the delimiter + std::string str; + deserialize_string_delim(istr, str, c); + data = LLDate(str); + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading date." << llendl; + } + break; + } + + case 'b': + parseBinary(istr, data); + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading data." << llendl; + } + if(data.isUndefined()) + { + parse_count = NOTATION_PARSE_FAILURE; + } + break; + + default: + data.clear(); + parse_count = NOTATION_PARSE_FAILURE; + llinfos << "Unrecognized character while parsing: int(" << (int)c + << ")" << llendl; + break; + } + return parse_count; +} + +// static +LLSD LLSDNotationParser::parse(std::istream& istr) +{ + LLSDNotationParser parser; + LLSD rv; + S32 count = parser.parse(istr, rv); + lldebugs << "LLSDNotationParser::parse parsed " << count << " objects." + << llendl; + return rv; +} + +S32 LLSDNotationParser::parseMap(std::istream& istr, LLSD& map) const +{ + // map: { string:object, string:object } + map = LLSD::emptyMap(); + S32 parse_count = 0; + char c = istr.get(); + if(c == '{') + { + // eat commas, white + bool found_name = false; + std::string name; + c = istr.get(); + while(c != '}' && istr.good()) + { + if(!found_name) + { + if((c == '\"') || (c == '\'') || (c == 's')) + { + istr.putback(c); + found_name = true; + deserialize_string(istr, name); + } + c = istr.get(); + } + else + { + if(isspace(c) || (c == ':')) + { + c = istr.get(); + continue; + } + istr.putback(c); + LLSD child; + S32 count = parse(istr, child); + if(count > 0) + { + parse_count += count; + map.insert(name, child); + } + else + { + map.clear(); + return NOTATION_PARSE_FAILURE; + } + found_name = false; + c = istr.get(); + } + } + } + return parse_count; +} + +S32 LLSDNotationParser::parseArray(std::istream& istr, LLSD& array) const +{ + // array: [ object, object, object ] + array = LLSD::emptyArray(); + S32 parse_count = 0; + char c = istr.get(); + if(c == '[') + { + // eat commas, white + c = istr.get(); + while((c != ']') && istr.good()) + { + LLSD child; + if(isspace(c) || (c == ',')) + { + c = istr.get(); + continue; + } + istr.putback(c); + S32 count = parse(istr, child); + if(count > 0) + { + parse_count += count; + array.append(child); + } + else + { + array.clear(); + return NOTATION_PARSE_FAILURE; + } + c = istr.get(); + } + } + return parse_count; +} + +void LLSDNotationParser::parseString(std::istream& istr, LLSD& data) const +{ + std::string value; + if(deserialize_string(istr, value)) + { + data = value; + } + else + { + // failed to parse. + data.clear(); + } +} + +void LLSDNotationParser::parseBinary(std::istream& istr, LLSD& data) const +{ + // binary: b##"ff3120ab1" + // or: b(len)"..." + + // I want to manually control those values here to make sure the + // parser doesn't break when someone changes a constant somewhere + // else. + const U32 BINARY_BUFFER_SIZE = 256; + const U32 STREAM_GET_COUNT = 255; + + // need to read the base out. + char buf[BINARY_BUFFER_SIZE]; /* Flawfinder: ignore */ + istr.get(buf, STREAM_GET_COUNT, '"'); + char c = istr.get(); + if((c == '"') && (0 == strncmp("b(", buf, 2))) + { + // We probably have a valid raw binary stream. determine + // the size, and read it. + // *FIX: Should we set a maximum size? + S32 len = strtol(buf + 2, NULL, 0); + std::vector value; + if(len) + { + value.resize(len); + fullread(istr, (char *)&value[0], len); + } + c = istr.get(); // strip off the trailing double-quote + data = value; + } + else if((c == '"') && (0 == strncmp("b64", buf, 3))) + { + // *FIX: A bit inefficient, but works for now. To make the + // format better, I would need to add a hint into the + // serialization format that indicated how long it was. + std::stringstream coded_stream; + istr.get(*(coded_stream.rdbuf()), '\"'); + c = istr.get(); + std::string encoded(coded_stream.str()); + S32 len = apr_base64_decode_len(encoded.c_str()); + std::vector value; + value.resize(len); + len = apr_base64_decode_binary(&value[0], encoded.c_str()); + value.resize(len); + data = value; + } + else if((c == '"') && (0 == strncmp("b16", buf, 3))) + { + // yay, base 16. We pop the next character which is either a + // double quote or base 16 data. If it's a double quote, we're + // done parsing. If it's not, put the data back, and read the + // stream until the next double quote. + char* read; /*Flawfinder: ignore*/ + U8 byte; + U8 byte_buffer[BINARY_BUFFER_SIZE]; + U8* write; + std::vector value; + c = istr.get(); + while(c != '"') + { + istr.putback(c); + read = buf; + write = byte_buffer; + istr.get(buf, STREAM_GET_COUNT, '"'); + c = istr.get(); + while(*read != '\0') /*Flawfinder: ignore*/ + { + byte = hex_as_nybble(*read++); + byte = byte << 4; + byte |= hex_as_nybble(*read++); + *write++ = byte; + } + // copy the data out of the byte buffer + value.insert(value.end(), byte_buffer, write); + } + data = value; + } + else + { + data.clear(); + } +} + + +/** + * LLSDBinaryParser + */ +LLSDBinaryParser::LLSDBinaryParser() +{ +} + +// virtual +LLSDBinaryParser::~LLSDBinaryParser() +{ +} + +// virtual +S32 LLSDBinaryParser::parse(std::istream& istr, LLSD& data) const +{ +/** + * Undefined: '!'
+ * Boolean: 't' for true 'f' for false
+ * Integer: 'i' + 4 bytes network byte order
+ * Real: 'r' + 8 bytes IEEE double
+ * UUID: 'u' + 16 byte unsigned integer
+ * String: 's' + 4 byte integer size + string
+ * strings also secretly support the notation format + * Date: 'd' + 8 byte IEEE double for seconds since epoch
+ * URI: 'l' + 4 byte integer size + string uri
+ * Binary: 'b' + 4 byte integer size + binary data
+ * Array: '[' + 4 byte integer size + all values + ']'
+ * Map: '{' + 4 byte integer size every(key + value) + '}'
+ * map keys are serialized as s + 4 byte integer size + string or in the + * notation format. + */ + char c; + c = istr.get(); + if(!istr.good()) + { + return 0; + } + S32 parse_count = 1; + switch(c) + { + case '{': + parse_count += parseMap(istr, data); + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading binary map." << llendl; + } + break; + + case '[': + parse_count += parseArray(istr, data); + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading binary array." << llendl; + } + break; + + case '!': + data.clear(); + break; + + case '0': + data = false; + break; + + case '1': + data = true; + break; + + case 'i': + { + U32 value_nbo = 0; + istr.read((char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ + data = (S32)ntohl(value_nbo); + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading binary integer." << llendl; + } + break; + } + + case 'r': + { + F64 real_nbo = 0.0; + istr.read((char*)&real_nbo, sizeof(F64)); /*Flawfinder: ignore*/ + data = ll_ntohd(real_nbo); + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading binary real." << llendl; + } + break; + } + + case 'u': + { + LLUUID id; + istr.read((char*)(&id.mData), UUID_BYTES); /*Flawfinder: ignore*/ + data = id; + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading binary uuid." << llendl; + } + break; + } + + case '\'': + case '"': + { + std::string value; + deserialize_string_delim(istr, value, c); + data = value; + break; + } + + case 's': + { + std::string value; + parseString(istr, value); + data = value; + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading binary string." << llendl; + } + break; + } + + case 'l': + { + std::string value; + parseString(istr, value); + data = LLURI(value); + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading binary link." << llendl; + } + break; + } + + case 'd': + { + F64 real = 0.0; + istr.read((char*)&real, sizeof(F64)); /*Flawfinder: ignore*/ + data = LLDate(real); + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading binary date." << llendl; + } + break; + } + + case 'b': + { + // We probably have a valid raw binary stream. determine + // the size, and read it. + // *FIX: Should we set a maximum size? + U32 size_nbo = 0; + istr.read((char*)&size_nbo, sizeof(U32)); + S32 size = (S32)ntohl(size_nbo); + std::vector value; + if(size) + { + value.resize(size); + istr.read((char*)&value[0], size); /*Flawfinder: ignore*/ + } + data = value; + if(istr.fail()) + { + llinfos << "STREAM FAILURE reading binary." << llendl; + } + break; + } + + default: + --parse_count; + llinfos << "Unrecognized character while parsing: int(" << (int)c + << ")" << llendl; + break; + } + return parse_count; +} + +// static +LLSD LLSDBinaryParser::parse(std::istream& istr) +{ + LLSDBinaryParser parser; + LLSD rv; + S32 count = parser.parse(istr, rv); + lldebugs << "LLSDBinaryParser::parse parsed " << count << " objects." + << llendl; + return rv; +} + +S32 LLSDBinaryParser::parseMap(std::istream& istr, LLSD& map) const +{ + map = LLSD::emptyMap(); + U32 value_nbo = 0; + istr.read((char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ + S32 size = (S32)ntohl(value_nbo); + S32 parse_count = 0; + S32 count = 0; + char c = istr.get(); + while(c != '}' && (count < size) && istr.good()) + { + std::string name; + switch(c) + { + case 'k': + parseString(istr, name); + break; + case '\'': + case '"': + deserialize_string_delim(istr, name, c); + break; + } + LLSD child; + S32 child_count = parse(istr, child); + if(child_count) + { + parse_count += child_count; + map.insert(name, child); + } + ++count; + c = istr.get(); + } + return parse_count; +} + +S32 LLSDBinaryParser::parseArray(std::istream& istr, LLSD& array) const +{ + array = LLSD::emptyArray(); + U32 value_nbo = 0; + istr.read((char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ + S32 size = (S32)ntohl(value_nbo); + + // *FIX: This would be a good place to reserve some space in the + // array... + + S32 parse_count = 0; + S32 count = 0; + char c = istr.peek(); + while((c != ']') && (count < size) && istr.good()) + { + LLSD child; + S32 child_count = parse(istr, child); + if(child_count) + { + parse_count += child_count; + array.append(child); + } + ++count; + c = istr.peek(); + } + c = istr.get(); + return parse_count; +} + +void LLSDBinaryParser::parseString( + std::istream& istr, + std::string& value) const +{ + // *FIX: This is memory inefficient. + U32 value_nbo = 0; + istr.read((char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ + S32 size = (S32)ntohl(value_nbo); + std::vector buf; + buf.resize(size); + istr.read(&buf[0], size); /*Flawfinder: ignore*/ + value.assign(buf.begin(), buf.end()); +} + + +/** + * LLSDFormatter + */ +LLSDFormatter::LLSDFormatter() : + mBoolAlpha(false) +{ +} + +// virtual +LLSDFormatter::~LLSDFormatter() +{ } + +void LLSDFormatter::boolalpha(bool alpha) +{ + mBoolAlpha = alpha; +} + +void LLSDFormatter::realFormat(const std::string& format) +{ + mRealFormat = format; +} + +void LLSDFormatter::formatReal(LLSD::Real real, std::ostream& ostr) const +{ + char buffer[MAX_STRING]; /* Flawfinder: ignore */ + snprintf(buffer, MAX_STRING, mRealFormat.c_str(), real); + ostr << buffer; +} + +/** + * LLSDNotationFormatter + */ +LLSDNotationFormatter::LLSDNotationFormatter() +{ +} + +// virtual +LLSDNotationFormatter::~LLSDNotationFormatter() +{ } + +// static +std::string LLSDNotationFormatter::escapeString(const std::string& in) +{ + std::ostringstream ostr; + serialize_string(in, ostr); + return ostr.str(); +} + +// virtual +S32 LLSDNotationFormatter::format(const LLSD& data, std::ostream& ostr, U32 options) const +{ + S32 format_count = 1; + switch(data.type()) + { + case LLSD::TypeMap: + { + ostr << "{"; + bool need_comma = false; + LLSD::map_const_iterator iter = data.beginMap(); + LLSD::map_const_iterator end = data.endMap(); + for(; iter != end; ++iter) + { + if(need_comma) ostr << ","; + need_comma = true; + ostr << '\''; + serialize_string((*iter).first, ostr); + ostr << "':"; + format_count += format((*iter).second, ostr); + } + ostr << "}"; + break; + } + + case LLSD::TypeArray: + { + ostr << "["; + bool need_comma = false; + LLSD::array_const_iterator iter = data.beginArray(); + LLSD::array_const_iterator end = data.endArray(); + for(; iter != end; ++iter) + { + if(need_comma) ostr << ","; + need_comma = true; + format_count += format(*iter, ostr); + } + ostr << "]"; + break; + } + + case LLSD::TypeUndefined: + ostr << "!"; + break; + + case LLSD::TypeBoolean: + if(mBoolAlpha || +#if( LL_WINDOWS || __GNUC__ > 2) + (ostr.flags() & std::ios::boolalpha) +#else + (ostr.flags() & 0x0100) +#endif + ) + { + ostr << (data.asBoolean() + ? NOTATION_TRUE_SERIAL : NOTATION_FALSE_SERIAL); + } + else + { + ostr << (data.asBoolean() ? 1 : 0); + } + break; + + case LLSD::TypeInteger: + ostr << "i" << data.asInteger(); + break; + + case LLSD::TypeReal: + ostr << "r"; + if(mRealFormat.empty()) + { + ostr << data.asReal(); + } + else + { + formatReal(data.asReal(), ostr); + } + break; + + case LLSD::TypeUUID: + ostr << "u" << data.asUUID(); + break; + + case LLSD::TypeString: + ostr << '\''; + serialize_string(data.asString(), ostr); + ostr << '\''; + break; + + case LLSD::TypeDate: + ostr << "d\"" << data.asDate() << "\""; + break; + + case LLSD::TypeURI: + ostr << "l\""; + serialize_string(data.asString(), ostr); + ostr << "\""; + break; + + case LLSD::TypeBinary: + { + // *FIX: memory inefficient. + std::vector buffer = data.asBinary(); + ostr << "b(" << buffer.size() << ")\""; + if(buffer.size()) ostr.write((const char*)&buffer[0], buffer.size()); + ostr << "\""; + break; + } + + default: + // *NOTE: This should never happen. + ostr << "!"; + break; + } + return format_count; +} + + +/** + * LLSDBinaryFormatter + */ +LLSDBinaryFormatter::LLSDBinaryFormatter() +{ +} + +// virtual +LLSDBinaryFormatter::~LLSDBinaryFormatter() +{ } + +// virtual +S32 LLSDBinaryFormatter::format(const LLSD& data, std::ostream& ostr, U32 options) const +{ + S32 format_count = 1; + switch(data.type()) + { + case LLSD::TypeMap: + { + ostr.put('{'); + U32 size_nbo = htonl(data.size()); + ostr.write((const char*)(&size_nbo), sizeof(U32)); + LLSD::map_const_iterator iter = data.beginMap(); + LLSD::map_const_iterator end = data.endMap(); + for(; iter != end; ++iter) + { + ostr.put('k'); + formatString((*iter).first, ostr); + format_count += format((*iter).second, ostr); + } + ostr.put('}'); + break; + } + + case LLSD::TypeArray: + { + ostr.put('['); + U32 size_nbo = htonl(data.size()); + ostr.write((const char*)(&size_nbo), sizeof(U32)); + LLSD::array_const_iterator iter = data.beginArray(); + LLSD::array_const_iterator end = data.endArray(); + for(; iter != end; ++iter) + { + format_count += format(*iter, ostr); + } + ostr.put(']'); + break; + } + + case LLSD::TypeUndefined: + ostr.put('!'); + break; + + case LLSD::TypeBoolean: + if(data.asBoolean()) ostr.put(BINARY_TRUE_SERIAL); + else ostr.put(BINARY_FALSE_SERIAL); + break; + + case LLSD::TypeInteger: + { + ostr.put('i'); + U32 value_nbo = htonl(data.asInteger()); + ostr.write((const char*)(&value_nbo), sizeof(U32)); + break; + } + + case LLSD::TypeReal: + { + ostr.put('r'); + F64 value_nbo = ll_htond(data.asReal()); + ostr.write((const char*)(&value_nbo), sizeof(F64)); + break; + } + + case LLSD::TypeUUID: + ostr.put('u'); + ostr.write((const char*)(&(data.asUUID().mData)), UUID_BYTES); + break; + + case LLSD::TypeString: + ostr.put('s'); + formatString(data.asString(), ostr); + break; + + case LLSD::TypeDate: + { + ostr.put('d'); + F64 value = data.asReal(); + ostr.write((const char*)(&value), sizeof(F64)); + break; + } + + case LLSD::TypeURI: + ostr.put('l'); + formatString(data.asString(), ostr); + break; + + case LLSD::TypeBinary: + { + // *FIX: memory inefficient. + ostr.put('b'); + std::vector buffer = data.asBinary(); + U32 size_nbo = htonl(buffer.size()); + ostr.write((const char*)(&size_nbo), sizeof(U32)); + if(buffer.size()) ostr.write((const char*)&buffer[0], buffer.size()); + break; + } + + default: + // *NOTE: This should never happen. + ostr.put('!'); + break; + } + return format_count; +} + +void LLSDBinaryFormatter::formatString( + const std::string& string, + std::ostream& ostr) const +{ + U32 size_nbo = htonl(string.size()); + ostr.write((const char*)(&size_nbo), sizeof(U32)); + ostr.write(string.c_str(), string.size()); +} + +/** + * local functions + */ +bool deserialize_string(std::istream& str, std::string& value) +{ + char c = str.get(); + if (str.fail()) + { + // No data in stream, bail out + return false; + } + + bool rv = false; + switch(c) + { + case '\'': + case '"': + rv = deserialize_string_delim(str, value, c); + break; + case 's': + rv = deserialize_string_raw(str, value); + break; + default: + break; + } + return rv; +} + +bool deserialize_string_delim( + std::istream& str, + std::string& value, + char delim) +{ + std::ostringstream write_buffer; + bool found_escape = false; + bool found_hex = false; + bool found_digit = false; + U8 byte = 0; + + while (true) + { + char next_char = str.get(); + + if(str.fail()) + { + // If our stream is empty, break out + value = write_buffer.str(); + return false; + } + + if(found_escape) + { + // next character(s) is a special sequence. + if(found_hex) + { + if(found_digit) + { + found_digit = false; + found_hex = false; + found_escape = false; + byte = byte << 4; + byte |= hex_as_nybble(next_char); + write_buffer << byte; + byte = 0; + } + else + { + // next character is the first nybble of + // + found_digit = true; + byte = hex_as_nybble(next_char); + } + } + else if(next_char == 'x') + { + found_hex = true; + } + else + { + switch(next_char) + { + case 'a': + write_buffer << '\a'; + break; + case 'b': + write_buffer << '\b'; + break; + case 'f': + write_buffer << '\f'; + break; + case 'n': + write_buffer << '\n'; + break; + case 'r': + write_buffer << '\r'; + break; + case 't': + write_buffer << '\t'; + break; + case 'v': + write_buffer << '\v'; + break; + default: + write_buffer << next_char; + break; + } + found_escape = false; + } + } + else if(next_char == '\\') + { + found_escape = true; + } + else if(next_char == delim) + { + break; + } + else + { + write_buffer << next_char; + } + } + + value = write_buffer.str(); + return true; +} + +bool deserialize_string_raw(std::istream& str, std::string& value) +{ + bool ok = false; + const S32 BUF_LEN = 20; + char buf[BUF_LEN]; /* Flawfinder: ignore */ + str.get(buf, BUF_LEN - 1, ')'); + char c = str.get(); + c = str.get(); + if(((c == '"') || (c == '\'')) && (buf[0] == '(')) + { + // We probably have a valid raw string. determine + // the size, and read it. + // *FIX: Should we set a maximum size? + // *FIX: This is memory inefficient. + S32 len = strtol(buf + 1, NULL, 0); + std::vector buf; + buf.resize(len); + str.read(&buf[0], len); /*Flawfinder: ignore*/ + value.assign(buf.begin(), buf.end()); + c = str.get(); + if((c == '"') || (c == '\'')) + { + ok = true; + } + } + return ok; +} + +static const char* NOTATION_STRING_CHARACTERS[256] = +{ + "\\x00", // 0 + "\\x01", // 1 + "\\x02", // 2 + "\\x03", // 3 + "\\x04", // 4 + "\\x05", // 5 + "\\x06", // 6 + "\\a", // 7 + "\\b", // 8 + "\\t", // 9 + "\\n", // 10 + "\\v", // 11 + "\\f", // 12 + "\\r", // 13 + "\\x0e", // 14 + "\\x0f", // 15 + "\\x10", // 16 + "\\x11", // 17 + "\\x12", // 18 + "\\x13", // 19 + "\\x14", // 20 + "\\x15", // 21 + "\\x16", // 22 + "\\x17", // 23 + "\\x18", // 24 + "\\x19", // 25 + "\\x1a", // 26 + "\\x1b", // 27 + "\\x1c", // 28 + "\\x1d", // 29 + "\\x1e", // 30 + "\\x1f", // 31 + " ", // 32 + "!", // 33 + "\"", // 34 + "#", // 35 + "$", // 36 + "%", // 37 + "&", // 38 + "\\'", // 39 + "(", // 40 + ")", // 41 + "*", // 42 + "+", // 43 + ",", // 44 + "-", // 45 + ".", // 46 + "/", // 47 + "0", // 48 + "1", // 49 + "2", // 50 + "3", // 51 + "4", // 52 + "5", // 53 + "6", // 54 + "7", // 55 + "8", // 56 + "9", // 57 + ":", // 58 + ";", // 59 + "<", // 60 + "=", // 61 + ">", // 62 + "?", // 63 + "@", // 64 + "A", // 65 + "B", // 66 + "C", // 67 + "D", // 68 + "E", // 69 + "F", // 70 + "G", // 71 + "H", // 72 + "I", // 73 + "J", // 74 + "K", // 75 + "L", // 76 + "M", // 77 + "N", // 78 + "O", // 79 + "P", // 80 + "Q", // 81 + "R", // 82 + "S", // 83 + "T", // 84 + "U", // 85 + "V", // 86 + "W", // 87 + "X", // 88 + "Y", // 89 + "Z", // 90 + "[", // 91 + "\\\\", // 92 + "]", // 93 + "^", // 94 + "_", // 95 + "`", // 96 + "a", // 97 + "b", // 98 + "c", // 99 + "d", // 100 + "e", // 101 + "f", // 102 + "g", // 103 + "h", // 104 + "i", // 105 + "j", // 106 + "k", // 107 + "l", // 108 + "m", // 109 + "n", // 110 + "o", // 111 + "p", // 112 + "q", // 113 + "r", // 114 + "s", // 115 + "t", // 116 + "u", // 117 + "v", // 118 + "w", // 119 + "x", // 120 + "y", // 121 + "z", // 122 + "{", // 123 + "|", // 124 + "}", // 125 + "~", // 126 + "\\x7f", // 127 + "\\x80", // 128 + "\\x81", // 129 + "\\x82", // 130 + "\\x83", // 131 + "\\x84", // 132 + "\\x85", // 133 + "\\x86", // 134 + "\\x87", // 135 + "\\x88", // 136 + "\\x89", // 137 + "\\x8a", // 138 + "\\x8b", // 139 + "\\x8c", // 140 + "\\x8d", // 141 + "\\x8e", // 142 + "\\x8f", // 143 + "\\x90", // 144 + "\\x91", // 145 + "\\x92", // 146 + "\\x93", // 147 + "\\x94", // 148 + "\\x95", // 149 + "\\x96", // 150 + "\\x97", // 151 + "\\x98", // 152 + "\\x99", // 153 + "\\x9a", // 154 + "\\x9b", // 155 + "\\x9c", // 156 + "\\x9d", // 157 + "\\x9e", // 158 + "\\x9f", // 159 + "\\xa0", // 160 + "\\xa1", // 161 + "\\xa2", // 162 + "\\xa3", // 163 + "\\xa4", // 164 + "\\xa5", // 165 + "\\xa6", // 166 + "\\xa7", // 167 + "\\xa8", // 168 + "\\xa9", // 169 + "\\xaa", // 170 + "\\xab", // 171 + "\\xac", // 172 + "\\xad", // 173 + "\\xae", // 174 + "\\xaf", // 175 + "\\xb0", // 176 + "\\xb1", // 177 + "\\xb2", // 178 + "\\xb3", // 179 + "\\xb4", // 180 + "\\xb5", // 181 + "\\xb6", // 182 + "\\xb7", // 183 + "\\xb8", // 184 + "\\xb9", // 185 + "\\xba", // 186 + "\\xbb", // 187 + "\\xbc", // 188 + "\\xbd", // 189 + "\\xbe", // 190 + "\\xbf", // 191 + "\\xc0", // 192 + "\\xc1", // 193 + "\\xc2", // 194 + "\\xc3", // 195 + "\\xc4", // 196 + "\\xc5", // 197 + "\\xc6", // 198 + "\\xc7", // 199 + "\\xc8", // 200 + "\\xc9", // 201 + "\\xca", // 202 + "\\xcb", // 203 + "\\xcc", // 204 + "\\xcd", // 205 + "\\xce", // 206 + "\\xcf", // 207 + "\\xd0", // 208 + "\\xd1", // 209 + "\\xd2", // 210 + "\\xd3", // 211 + "\\xd4", // 212 + "\\xd5", // 213 + "\\xd6", // 214 + "\\xd7", // 215 + "\\xd8", // 216 + "\\xd9", // 217 + "\\xda", // 218 + "\\xdb", // 219 + "\\xdc", // 220 + "\\xdd", // 221 + "\\xde", // 222 + "\\xdf", // 223 + "\\xe0", // 224 + "\\xe1", // 225 + "\\xe2", // 226 + "\\xe3", // 227 + "\\xe4", // 228 + "\\xe5", // 229 + "\\xe6", // 230 + "\\xe7", // 231 + "\\xe8", // 232 + "\\xe9", // 233 + "\\xea", // 234 + "\\xeb", // 235 + "\\xec", // 236 + "\\xed", // 237 + "\\xee", // 238 + "\\xef", // 239 + "\\xf0", // 240 + "\\xf1", // 241 + "\\xf2", // 242 + "\\xf3", // 243 + "\\xf4", // 244 + "\\xf5", // 245 + "\\xf6", // 246 + "\\xf7", // 247 + "\\xf8", // 248 + "\\xf9", // 249 + "\\xfa", // 250 + "\\xfb", // 251 + "\\xfc", // 252 + "\\xfd", // 253 + "\\xfe", // 254 + "\\xff" // 255 +}; + +void serialize_string(const std::string& value, std::ostream& str) +{ + std::string::const_iterator it = value.begin(); + std::string::const_iterator end = value.end(); + U8 c; + for(; it != end; ++it) + { + c = (U8)(*it); + str << NOTATION_STRING_CHARACTERS[c]; + } +} + + diff --git a/indra/llcommon/llsdserialize.h b/indra/llcommon/llsdserialize.h new file mode 100644 index 0000000000..bc6817c732 --- /dev/null +++ b/indra/llcommon/llsdserialize.h @@ -0,0 +1,592 @@ +/** + * @file llsdserialize.h + * @author Phoenix + * @date 2006-02-26 + * @brief Declaration of parsers and formatters for LLSD + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSDSERIALIZE_H +#define LL_LLSDSERIALIZE_H + +#include +#include "llsd.h" +#include "llmemory.h" + +/** + * @class LLSDParser + * @brief Abstract base class for simple LLSD parsers. + */ +class LLSDParser : public LLRefCount +{ +protected: + /** + * @brief Destructor + */ + virtual ~LLSDParser(); + +public: + /** + * @brief Constructor + */ + LLSDParser(); + + /** + * @brief Call this method to parse a stream for LLSD. + * + * This method parses the istream for a structured data. This + * method assumes that the istream is a complete llsd object -- + * for example an opened and closed map with an arbitrary nesting + * of elements. This method will return after reading one data + * object, allowing continued reading from the stream by the + * caller. + * @param istr The input stream. + * @param data[out] The newly parse structured data. + * @return Returns The number of LLSD objects parsed into data. + */ + virtual S32 parse(std::istream& istr, LLSD& data) const = 0; + +protected: + +}; + +/** + * @class LLSDNotationParser + * @brief Parser which handles the original notation format for LLSD. + */ +class LLSDNotationParser : public LLSDParser +{ +protected: + /** + * @brief Destructor + */ + virtual ~LLSDNotationParser(); + +public: + /** + * @brief Constructor + */ + LLSDNotationParser() {} + + /** + * @brief Call this method to parse a stream for LLSD. + * + * This method parses the istream for a structured data. This + * method assumes that the istream is a complete llsd object -- + * for example an opened and closed map with an arbitrary nesting + * of elements. This method will return after reading one data + * object, allowing continued reading from the stream by the + * caller. + * @param istr The input stream. + * @param data[out] The newly parse structured data. Undefined on failure. + * @return Returns the number of LLSD objects parsed into + * data. Returns -1 on parse failure. + */ + virtual S32 parse(std::istream& istr, LLSD& data) const; + + /** + * @brief Simple notation parse. + * + * This simplified parser cannot not distinguish between a failed + * parse and a parse which yields a single undefined LLSD. You can + * use this if error checking will be implicit in the use of the + * results of the parse. + * @param istr The input stream. + * @return Returns the parsed LLSD object. + */ + static LLSD parse(std::istream& istr); + +private: + /** + * @brief Parse a map from the istream + * + * @param istr The input stream. + * @param map The map to add the parsed data. + * @return Returns The number of LLSD objects parsed into data. + */ + S32 parseMap(std::istream& istr, LLSD& map) const; + + /** + * @brief Parse an array from the istream. + * + * @param istr The input stream. + * @param array The array to append the parsed data. + * @return Returns The number of LLSD objects parsed into data. + */ + S32 parseArray(std::istream& istr, LLSD& array) const; + + /** + * @brief Parse a string from the istream and assign it to data. + * + * @param istr The input stream. + * @param data[out] The data to assign. + */ + void parseString(std::istream& istr, LLSD& data) const; + + /** + * @brief Parse binary data from the stream. + * + * @param istr The input stream. + * @param data[out] The data to assign. + */ + void parseBinary(std::istream& istr, LLSD& data) const; +}; + +/** + * @class LLSDXMLParser + * @brief Parser which handles XML format LLSD. + */ +class LLSDXMLParser : public LLSDParser +{ +protected: + /** + * @brief Destructor + */ + virtual ~LLSDXMLParser(); + +public: + /** + * @brief Constructor + */ + LLSDXMLParser(); + + /** + * @brief Call this method to parse a stream for LLSD. + * + * This method parses the istream for a structured data. This + * method assumes that the istream is a complete llsd object -- + * for example an opened and closed map with an arbitrary nesting + * of elements. This method will return after reading one data + * object, allowing continued reading from the stream by the + * caller. + * @param istr The input stream. + * @param data[out] The newly parse structured data. + * @return Returns the number of LLSD objects parsed into data. + */ + virtual S32 parse(std::istream& istr, LLSD& data) const; + +private: + class Impl; + Impl& impl; + + void parsePart(const char *buf, int len); + friend class LLSDSerialize; +}; + +/** + * @class LLSDBinaryParser + * @brief Parser which handles binary formatted LLSD. + */ +class LLSDBinaryParser : public LLSDParser +{ +protected: + /** + * @brief Destructor + */ + virtual ~LLSDBinaryParser(); + +public: + /** + * @brief Constructor + */ + LLSDBinaryParser(); + + /** + * @brief Call this method to parse a stream for LLSD. + * + * This method parses the istream for a structured data. This + * method assumes that the istream is a complete llsd object -- + * for example an opened and closed map with an arbitrary nesting + * of elements. This method will return after reading one data + * object, allowing continued reading from the stream by the + * caller. + * @param istr The input stream. + * @param data[out] The newly parse structured data. + * @return Returns the number of LLSD objects parsed into data. + */ + virtual S32 parse(std::istream& istr, LLSD& data) const; + + /** + * @brief Simple notation parse. + * + * This simplified parser cannot not distinguish between a failed + * parse and a parse which yields a single undefined LLSD. You can + * use this if error checking will be implicit in the use of the + * results of the parse. + * @param istr The input stream. + * @return Returns the parsed LLSD object. + */ + static LLSD parse(std::istream& istr); + +private: + /** + * @brief Parse a map from the istream + * + * @param istr The input stream. + * @param map The map to add the parsed data. + * @return Returns The number of LLSD objects parsed into data. + */ + S32 parseMap(std::istream& istr, LLSD& map) const; + + /** + * @brief Parse an array from the istream. + * + * @param istr The input stream. + * @param array The array to append the parsed data. + * @return Returns The number of LLSD objects parsed into data. + */ + S32 parseArray(std::istream& istr, LLSD& array) const; + + /** + * @brief Parse a string from the istream and assign it to data. + * + * @param istr The input stream. + * @param value[out] The string to assign. + */ + void parseString(std::istream& istr, std::string& value) const; +}; + + +/** + * @class LLSDFormatter + * @brief Abstract base class for formatting LLSD. + */ +class LLSDFormatter : public LLRefCount +{ +protected: + /** + * @brief Destructor + */ + virtual ~LLSDFormatter(); + +public: + /** + * Options for output + */ + enum e_formatter_options_type + { + OPTIONS_NONE = 0, + OPTIONS_PRETTY = 1 + } EFormatterOptions; + + /** + * @brief Constructor + */ + LLSDFormatter(); + + /** + * @brief Set the boolean serialization format. + * + * @param alpha Serializes boolean as alpha if true. + */ + void boolalpha(bool alpha); + + /** + * @brief Set the real format + * + * By default, the formatter will use default double serialization + * which is frequently frustrating for many applications. You can + * set the precision on the stream independently, but that still + * might not work depending on the value. + * EXAMPLES:
+ * %.2f
+ * @param format A format string which follows the printf format + * rules. Specify an empty string to return to default formatting. + */ + void realFormat(const std::string& format); + + /** + * @brief Call this method to format an LLSD to a stream. + * + * @param data The data to write. + * @param ostr The destination stream for the data. + * @return Returns The number of LLSD objects fomatted out + */ + virtual S32 format(const LLSD& data, std::ostream& ostr, U32 options = LLSDFormatter::OPTIONS_NONE) const = 0; + +protected: + /** + * @brief Helper method which appropriately obeys the real format. + * + * @param real The real value to format. + * @param ostr The destination stream for the data. + */ + void formatReal(LLSD::Real real, std::ostream& ostr) const; + +protected: + bool mBoolAlpha; + std::string mRealFormat; +}; + + +/** + * @class LLSDNotationFormatter + * @brief Formatter which outputs the original notation format for LLSD. + */ +class LLSDNotationFormatter : public LLSDFormatter +{ +protected: + /** + * @brief Destructor + */ + virtual ~LLSDNotationFormatter(); + +public: + /** + * @brief Constructor + */ + LLSDNotationFormatter(); + + /** + * @brief Helper static method to return a notation escaped string + * + * This method will return the notation escaped string, but not + * the surrounding serialization identifiers such as a double or + * single quote. It will be up to the caller to embed those as + * appropriate. + * @param in The raw, unescaped string. + * @return Returns an escaped string appropriate for serialization. + */ + static std::string escapeString(const std::string& in); + + /** + * @brief Call this method to format an LLSD to a stream. + * + * @param data The data to write. + * @param ostr The destination stream for the data. + * @return Returns The number of LLSD objects fomatted out + */ + virtual S32 format(const LLSD& data, std::ostream& ostr, U32 options = LLSDFormatter::OPTIONS_NONE) const; +}; + + +/** + * @class LLSDXMLFormatter + * @brief Formatter which outputs the LLSD as XML. + */ +class LLSDXMLFormatter : public LLSDFormatter +{ +protected: + /** + * @brief Destructor + */ + virtual ~LLSDXMLFormatter(); + +public: + /** + * @brief Constructor + */ + LLSDXMLFormatter(); + + /** + * @brief Helper static method to return an xml escaped string + * + * @param in A valid UTF-8 string. + * @return Returns an escaped string appropriate for serialization. + */ + static std::string escapeString(const std::string& in); + + /** + * @brief Call this method to format an LLSD to a stream. + * + * @param data The data to write. + * @param ostr The destination stream for the data. + * @return Returns The number of LLSD objects fomatted out + */ + virtual S32 format(const LLSD& data, std::ostream& ostr, U32 options = LLSDFormatter::OPTIONS_NONE) const; + +protected: + + /** + * @brief Implementation to format the data. This is called recursively. + * + * @param data The data to write. + * @param ostr The destination stream for the data. + * @return Returns The number of LLSD objects fomatted out + */ + S32 format_impl(const LLSD& data, std::ostream& ostr, U32 options, U32 level) const; +}; + + +/** + * @class LLSDBinaryFormatter + * @brief Formatter which outputs the LLSD as a binary notation format. + * + * The binary format is a compact and efficient representation of + * structured data useful for when transmitting over a small data pipe + * or when transmission frequency is very high.
+ * + * The normal boolalpha and real format commands are ignored.
+ * + * All integers are transmitted in network byte order. The format is:
+ * Undefined: '!'
+ * Boolean: character '1' for true character '0' for false
+ * Integer: 'i' + 4 bytes network byte order
+ * Real: 'r' + 8 bytes IEEE double
+ * UUID: 'u' + 16 byte unsigned integer
+ * String: 's' + 4 byte integer size + string
+ * Date: 'd' + 8 byte IEEE double for seconds since epoch
+ * URI: 'l' + 4 byte integer size + string uri
+ * Binary: 'b' + 4 byte integer size + binary data
+ * Array: '[' + 4 byte integer size + all values + ']'
+ * Map: '{' + 4 byte integer size every(key + value) + '}'
+ * map keys are serialized as 'k' + 4 byte integer size + string + */ +class LLSDBinaryFormatter : public LLSDFormatter +{ +protected: + /** + * @brief Destructor + */ + virtual ~LLSDBinaryFormatter(); + +public: + /** + * @brief Constructor + */ + LLSDBinaryFormatter(); + + /** + * @brief Call this method to format an LLSD to a stream. + * + * @param data The data to write. + * @param ostr The destination stream for the data. + * @return Returns The number of LLSD objects fomatted out + */ + virtual S32 format(const LLSD& data, std::ostream& ostr, U32 options = LLSDFormatter::OPTIONS_NONE) const; + +protected: + /** + * @brief Helper method to serialize strings + * + * This method serializes a network byte order size and the raw + * string contents. + * @param string The string to write. + * @param ostr The destination stream for the data. + */ + void formatString(const std::string& string, std::ostream& ostr) const; +}; + + +/** + * @class LLSDNotationStreamFormatter + * @brief Formatter which is specialized for use on streams which + * outputs the original notation format for LLSD. + * + * This class is useful for doing inline stream operations. For example: + * + * + * LLSD sd;
+ * sd["foo"] = "bar";
+ * std::stringstream params;
+ * params << "[{'version':i1}," << LLSDOStreamer(sd) + * << "]"; + *
+ */ +template +class LLSDOStreamer : public Formatter +{ +public: + /** + * @brief Constructor + */ + LLSDOStreamer(const LLSD& data, U32 options = LLSDFormatter::OPTIONS_NONE) : + mSD(data), mOptions(options) {} + + /** + * @brief Stream operator. + * + * Use this inline during construction during a stream operation. + * @param str The destination stream for serialized output. + * @param The formatter which will output it's LLSD. + * @return Returns the stream passed in after streaming mSD. + */ + friend std::ostream& operator<<( + std::ostream& str, + const LLSDOStreamer& formatter) + { + formatter.format(formatter.mSD, str, formatter.mOptions); + return str; + } + +protected: + LLSD mSD; + U32 mOptions; +}; + +typedef LLSDOStreamer LLSDNotationStreamer; +typedef LLSDOStreamer LLSDXMLStreamer; + +/** + * @class LLSDSerialize + * @Serializer / deserializer for the various LLSD formats + */ +class LLSDSerialize +{ +public: + enum ELLSD_Serialize + { + LLSD_BINARY, LLSD_XML + }; + + /* + * Generic in/outs + */ + static void serialize(const LLSD& sd, std::ostream& str, ELLSD_Serialize, + U32 options = LLSDFormatter::OPTIONS_NONE); + static bool deserialize(LLSD& sd, std::istream& str); + + /* + * Notation Methods + */ + static S32 toNotation(const LLSD& sd, std::ostream& str) + { + LLPointer f = new LLSDNotationFormatter; + return f->format(sd, str, LLSDFormatter::OPTIONS_NONE); + } + static S32 fromNotation(LLSD& sd, std::istream& str) + { + LLPointer p = new LLSDNotationParser; + return p->parse(str, sd); + } + + /* + * XML Methods + */ + static S32 toXML(const LLSD& sd, std::ostream& str) + { + LLPointer f = new LLSDXMLFormatter; + return f->format(sd, str, LLSDFormatter::OPTIONS_NONE); + } + static S32 toPrettyXML(const LLSD& sd, std::ostream& str) + { + LLPointer f = new LLSDXMLFormatter; + return f->format(sd, str, LLSDFormatter::OPTIONS_PRETTY); + } + static S32 fromXML(LLSD& sd, std::istream& str) + { + LLPointer p = new LLSDXMLParser; + return p->parse(str, sd); + } + + /* + * Binary Methods + */ + static S32 toBinary(const LLSD& sd, std::ostream& str) + { + LLPointer f = new LLSDBinaryFormatter; + return f->format(sd, str, LLSDFormatter::OPTIONS_NONE); + } + static S32 fromBinary(LLSD& sd, std::istream& str) + { + LLPointer p = new LLSDBinaryParser; + return p->parse(str, sd); + } +private: + static const char *LLSDBinaryHeader; + static const char *LLSDXMLHeader; +}; + +#endif // LL_LLSDSERIALIZE_H diff --git a/indra/llcommon/llsdserialize_xml.cpp b/indra/llcommon/llsdserialize_xml.cpp new file mode 100644 index 0000000000..2824d0f73c --- /dev/null +++ b/indra/llcommon/llsdserialize_xml.cpp @@ -0,0 +1,706 @@ +/** + * @file llsdserialize_xml.cpp + * @brief XML parsers and formatters for LLSD + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llsdserialize_xml.h" + +#include +#include + +#include "apr-1/apr_base64.h" + +extern "C" +{ +#include "expat/expat.h" +} + +/** + * LLSDXMLFormatter + */ +LLSDXMLFormatter::LLSDXMLFormatter() +{ +} + +// virtual +LLSDXMLFormatter::~LLSDXMLFormatter() +{ +} + +// virtual +S32 LLSDXMLFormatter::format(const LLSD& data, std::ostream& ostr, U32 options) const +{ + std::streamsize old_precision = ostr.precision(25); + + LLString post = ""; + if (options & LLSDFormatter::OPTIONS_PRETTY) + { + post = "\n"; + } + ostr << "" << post; + S32 rv = format_impl(data, ostr, options, 1); + ostr << "\n"; + + ostr.precision(old_precision); + return rv; +} + +S32 LLSDXMLFormatter::format_impl(const LLSD& data, std::ostream& ostr, U32 options, U32 level) const +{ + S32 format_count = 1; + LLString pre = ""; + LLString post = ""; + + if (options & LLSDFormatter::OPTIONS_PRETTY) + { + for (U32 i = 0; i < level; i++) + { + pre += " "; + } + post = "\n"; + } + + switch(data.type()) + { + case LLSD::TypeMap: + if(0 == data.size()) + { + ostr << pre << "" << post; + } + else + { + ostr << pre << "" << post; + LLSD::map_const_iterator iter = data.beginMap(); + LLSD::map_const_iterator end = data.endMap(); + for(; iter != end; ++iter) + { + ostr << pre << "" << escapeString((*iter).first) << "" << post; + format_count += format_impl((*iter).second, ostr, options, level + 1); + } + ostr << pre << "" << post; + } + break; + + case LLSD::TypeArray: + if(0 == data.size()) + { + ostr << pre << "" << post; + } + else + { + ostr << pre << "" << post; + LLSD::array_const_iterator iter = data.beginArray(); + LLSD::array_const_iterator end = data.endArray(); + for(; iter != end; ++iter) + { + format_count += format_impl(*iter, ostr, options, level + 1); + } + ostr << pre << "" << post; + } + break; + + case LLSD::TypeUndefined: + ostr << pre << "" << post; + break; + + case LLSD::TypeBoolean: + ostr << pre << ""; + if(mBoolAlpha || +#if( LL_WINDOWS || __GNUC__ > 2) + (ostr.flags() & std::ios::boolalpha) +#else + (ostr.flags() & 0x0100) +#endif + ) + { + ostr << (data.asBoolean() ? "true" : "false"); + } + else + { + ostr << (data.asBoolean() ? 1 : 0); + } + ostr << "" << post; + break; + + case LLSD::TypeInteger: + ostr << pre << "" << data.asInteger() << "" << post; + break; + + case LLSD::TypeReal: + ostr << pre << ""; + if(mRealFormat.empty()) + { + ostr << data.asReal(); + } + else + { + formatReal(data.asReal(), ostr); + } + ostr << "" << post; + break; + + case LLSD::TypeUUID: + if(data.asUUID().isNull()) ostr << pre << "" << post; + else ostr << pre << "" << data.asUUID() << "" << post; + break; + + case LLSD::TypeString: + if(data.asString().empty()) ostr << pre << "" << post; + else ostr << pre << "" << escapeString(data.asString()) <<"" << post; + break; + + case LLSD::TypeDate: + ostr << pre << "" << data.asDate() << "" << post; + break; + + case LLSD::TypeURI: + ostr << pre << "" << escapeString(data.asString()) << "" << post; + break; + + case LLSD::TypeBinary: + { + LLSD::Binary buffer = data.asBinary(); + if(buffer.empty()) + { + ostr << pre << "" << post; + } + else + { + // *FIX: memory inefficient. + ostr << pre << ""; + int b64_buffer_length = apr_base64_encode_len(buffer.size()); + char* b64_buffer = new char[b64_buffer_length]; + b64_buffer_length = apr_base64_encode_binary( + b64_buffer, + &buffer[0], + buffer.size()); + ostr.write(b64_buffer, b64_buffer_length - 1); + delete[] b64_buffer; + ostr << "" << post; + } + break; + } + default: + // *NOTE: This should never happen. + ostr << pre << "" << post; + break; + } + return format_count; +} + +// static +std::string LLSDXMLFormatter::escapeString(const std::string& in) +{ + std::ostringstream out; + std::string::const_iterator it = in.begin(); + std::string::const_iterator end = in.end(); + for(; it != end; ++it) + { + switch((*it)) + { + case '<': + out << "<"; + break; + case '>': + out << ">"; + break; + case '&': + out << "&"; + break; + case '\'': + out << "'"; + break; + case '"': + out << """; + break; + default: + out << (*it); + break; + } + } + return out.str(); +} + + + +class LLSDXMLParser::Impl +{ +public: + Impl(); + ~Impl(); + + LLSD parse(std::istream& input); + + void parsePart(const char *buf, int len); + +private: + void reset(); + + void startElementHandler(const XML_Char* name, const XML_Char** attributes); + void endElementHandler(const XML_Char* name); + void characterDataHandler(const XML_Char* data, int length); + + static void sStartElementHandler( + void* userData, const XML_Char* name, const XML_Char** attributes); + static void sEndElementHandler( + void* userData, const XML_Char* name); + static void sCharacterDataHandler( + void* userData, const XML_Char* data, int length); + + void startSkipping(); + + enum Element { + ELEMENT_LLSD, + ELEMENT_UNDEF, + ELEMENT_BOOL, + ELEMENT_INTEGER, + ELEMENT_REAL, + ELEMENT_STRING, + ELEMENT_UUID, + ELEMENT_DATE, + ELEMENT_URI, + ELEMENT_BINARY, + ELEMENT_MAP, + ELEMENT_ARRAY, + ELEMENT_KEY, + ELEMENT_UNKNOWN + }; + static Element readElement(const XML_Char* name); + + static const XML_Char* findAttribute(const XML_Char* name, const XML_Char** pairs); + + + XML_Parser mParser; + + LLSD mResult; + + bool mInLLSDElement; + bool mGracefullStop; + + typedef std::deque LLSDRefStack; + LLSDRefStack mStack; + + int mDepth; + bool mSkipping; + int mSkipThrough; + + std::string mCurrentKey; + std::ostringstream mCurrentContent; + + bool mPreStaged; +}; + + +LLSDXMLParser::Impl::Impl() +{ + mParser = XML_ParserCreate(NULL); + mPreStaged = false; + reset(); +} + +LLSDXMLParser::Impl::~Impl() +{ + XML_ParserFree(mParser); +} + +bool is_eol(char c) +{ + return (c == '\n' || c == '\r'); +} + +void clear_eol(std::istream& input) +{ + char c = input.peek(); + while (input.good() && is_eol(c)) + { + input.get(c); + c = input.peek(); + } +} + +static unsigned get_till_eol(std::istream& input, char *buf, unsigned bufsize) +{ + unsigned count = 0; + while (count < bufsize && input.good()) + { + input.get(buf[count]); + count++; + if (is_eol(buf[count - 1])) + break; + } + return count; +} + +LLSD LLSDXMLParser::Impl::parse(std::istream& input) +{ + reset(); + XML_Status status; + + static const int BUFFER_SIZE = 1024; + void* buffer = NULL; + int count = 0; + while (input.good() && !input.eof()) + { + buffer = XML_GetBuffer(mParser, BUFFER_SIZE); + + /* + * If we happened to end our last buffer right at the end of the llsd, but the + * stream is still going we will get a null buffer here. Check for mGracefullStop. + */ + if (!buffer) + { + break; + } + count = get_till_eol(input, (char *)buffer, BUFFER_SIZE); + if (!count) + { + break; + } + status = XML_ParseBuffer(mParser, count, false); + + if (status == XML_STATUS_ERROR) + { + break; + } + } + + // FIXME: This code is buggy - if the stream was empty or not good, there + // is not buffer to parse, both the call to XML_ParseBuffer and the buffer + // manipulations are illegal + // futhermore, it isn't clear that the expat buffer semantics are preserved + + status = XML_ParseBuffer(mParser, 0, true); + if (status == XML_STATUS_ERROR && !mGracefullStop) + { + ((char*) buffer)[count? count - 1 : 0] = '\0'; + llinfos << "LLSDXMLParser::Impl::parse: XML_STATUS_ERROR parsing:" << (char*) buffer << llendl; + return LLSD(); + } + + clear_eol(input); + return mResult; +} + +void LLSDXMLParser::Impl::reset() +{ + if (mPreStaged) + { + mPreStaged = false; + return; + } + + mResult.clear(); + + mInLLSDElement = false; + mDepth = 0; + + mGracefullStop = false; + + mStack.clear(); + + mSkipping = false; + +#if( LL_WINDOWS || __GNUC__ > 2) + mCurrentKey.clear(); +#else + mCurrentKey = std::string(); +#endif + + + XML_ParserReset(mParser, "utf-8"); + XML_SetUserData(mParser, this); + XML_SetElementHandler(mParser, sStartElementHandler, sEndElementHandler); + XML_SetCharacterDataHandler(mParser, sCharacterDataHandler); +} + + +void LLSDXMLParser::Impl::startSkipping() +{ + mSkipping = true; + mSkipThrough = mDepth; +} + +const XML_Char* +LLSDXMLParser::Impl::findAttribute(const XML_Char* name, const XML_Char** pairs) +{ + while (NULL != pairs && NULL != *pairs) + { + if(0 == strcmp(name, *pairs)) + { + return *(pairs + 1); + } + pairs += 2; + } + return NULL; +} + +void LLSDXMLParser::Impl::parsePart(const char *buf, int len) +{ + void * buffer = XML_GetBuffer(mParser, len); + if (buffer != NULL && buf != NULL) + { + memcpy(buffer, buf, len); + } + XML_ParseBuffer(mParser, len, false); + + mPreStaged = true; +} + +void LLSDXMLParser::Impl::startElementHandler(const XML_Char* name, const XML_Char** attributes) +{ + mDepth += 1; + if (mSkipping) + { + return; + } + + Element element = readElement(name); + mCurrentContent.str(""); + + switch (element) + { + case ELEMENT_LLSD: + if (mInLLSDElement) { return startSkipping(); } + mInLLSDElement = true; + return; + + case ELEMENT_KEY: + if (mStack.empty() || !(mStack.back()->isMap())) + { + return startSkipping(); + } + return; + + case ELEMENT_BINARY: + { + const XML_Char* encoding = findAttribute("encoding", attributes); + if(encoding && strcmp("base64", encoding) != 0) { return startSkipping(); } + break; + } + + default: + // all rest are values, fall through + ; + } + + + if (!mInLLSDElement) { return startSkipping(); } + + if (mStack.empty()) + { + mStack.push_back(&mResult); + } + else if (mStack.back()->isMap()) + { + if (mCurrentKey.empty()) { return startSkipping(); } + + LLSD& map = *mStack.back(); + LLSD& newElement = map[mCurrentKey]; + mStack.push_back(&newElement); + +#if( LL_WINDOWS || __GNUC__ > 2) + mCurrentKey.clear(); +#else + mCurrentKey = std::string(); +#endif + } + else if (mStack.back()->isArray()) + { + LLSD& array = *mStack.back(); + array.append(LLSD()); + LLSD& newElement = array[array.size()-1]; + mStack.push_back(&newElement); + } + else { + // improperly nested value in a non-structure + return startSkipping(); + } + + switch (element) + { + case ELEMENT_MAP: + *mStack.back() = LLSD::emptyMap(); + break; + + case ELEMENT_ARRAY: + *mStack.back() = LLSD::emptyArray(); + break; + + default: + // all the other values will be set in the end element handler + ; + } +} + +void LLSDXMLParser::Impl::endElementHandler(const XML_Char* name) +{ + mDepth -= 1; + if (mSkipping) + { + if (mDepth < mSkipThrough) + { + mSkipping = false; + } + return; + } + + Element element = readElement(name); + + switch (element) + { + case ELEMENT_LLSD: + if (mInLLSDElement) + { + mInLLSDElement = false; + mGracefullStop = true; + XML_StopParser(mParser, false); + } + return; + + case ELEMENT_KEY: + mCurrentKey = mCurrentContent.str(); + return; + + default: + // all rest are values, fall through + ; + } + + if (!mInLLSDElement) { return; } + + LLSD& value = *mStack.back(); + mStack.pop_back(); + + std::string content = mCurrentContent.str(); + mCurrentContent.str(""); + + switch (element) + { + case ELEMENT_UNDEF: + value.clear(); + break; + + case ELEMENT_BOOL: + value = content == "true" || content == "1"; + break; + + case ELEMENT_INTEGER: + value = LLSD(content).asInteger(); + break; + + case ELEMENT_REAL: + value = LLSD(content).asReal(); + break; + + case ELEMENT_STRING: + value = content; + break; + + case ELEMENT_UUID: + value = LLSD(content).asUUID(); + break; + + case ELEMENT_DATE: + value = LLSD(content).asDate(); + break; + + case ELEMENT_URI: + value = LLSD(content).asURI(); + break; + + case ELEMENT_BINARY: + { + S32 len = apr_base64_decode_len(content.c_str()); + std::vector data; + data.resize(len); + len = apr_base64_decode_binary(&data[0], content.c_str()); + data.resize(len); + value = data; + break; + } + + case ELEMENT_UNKNOWN: + value.clear(); + break; + + default: + // other values, map and array, have already been set + break; + } +} + +void LLSDXMLParser::Impl::characterDataHandler(const XML_Char* data, int length) +{ + mCurrentContent.write(data, length); +} + + +void LLSDXMLParser::Impl::sStartElementHandler( + void* userData, const XML_Char* name, const XML_Char** attributes) +{ + ((LLSDXMLParser::Impl*)userData)->startElementHandler(name, attributes); +} + +void LLSDXMLParser::Impl::sEndElementHandler( + void* userData, const XML_Char* name) +{ + ((LLSDXMLParser::Impl*)userData)->endElementHandler(name); +} + +void LLSDXMLParser::Impl::sCharacterDataHandler( + void* userData, const XML_Char* data, int length) +{ + ((LLSDXMLParser::Impl*)userData)->characterDataHandler(data, length); +} + + +LLSDXMLParser::Impl::Element LLSDXMLParser::Impl::readElement(const XML_Char* name) +{ + if (strcmp(name, "llsd") == 0) { return ELEMENT_LLSD; } + if (strcmp(name, "undef") == 0) { return ELEMENT_UNDEF; } + if (strcmp(name, "boolean") == 0) { return ELEMENT_BOOL; } + if (strcmp(name, "integer") == 0) { return ELEMENT_INTEGER; } + if (strcmp(name, "real") == 0) { return ELEMENT_REAL; } + if (strcmp(name, "string") == 0) { return ELEMENT_STRING; } + if (strcmp(name, "uuid") == 0) { return ELEMENT_UUID; } + if (strcmp(name, "date") == 0) { return ELEMENT_DATE; } + if (strcmp(name, "uri") == 0) { return ELEMENT_URI; } + if (strcmp(name, "binary") == 0) { return ELEMENT_BINARY; } + if (strcmp(name, "map") == 0) { return ELEMENT_MAP; } + if (strcmp(name, "array") == 0) { return ELEMENT_ARRAY; } + if (strcmp(name, "key") == 0) { return ELEMENT_KEY; } + + return ELEMENT_UNKNOWN; +} + + + + + + + +LLSDXMLParser::LLSDXMLParser() + : impl(* new Impl) +{ +} + +LLSDXMLParser::~LLSDXMLParser() +{ + delete &impl; +} + +void LLSDXMLParser::parsePart(const char *buf, int len) +{ + impl.parsePart(buf, len); +} + +// virtual +S32 LLSDXMLParser::parse(std::istream& input, LLSD& data) const +{ + data = impl.parse(input); + return 0; +} diff --git a/indra/llcommon/llsdserialize_xml.h b/indra/llcommon/llsdserialize_xml.h new file mode 100644 index 0000000000..f4aa7b92a4 --- /dev/null +++ b/indra/llcommon/llsdserialize_xml.h @@ -0,0 +1,17 @@ +/** + * @file llsdserialize_xml.h + * @brief XML parsers and formatters for LLSD + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSDSERIALIZE_XML_H +#define LL_LLSDSERIALIZE_XML_H + +#include "llsdserialize.h" + +// all the XML class definitions are in llsdserialze.h for now + +#endif // LL_LLSDSERIALIZE_XML_H + diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp new file mode 100644 index 0000000000..9305f82e05 --- /dev/null +++ b/indra/llcommon/llsdutil.cpp @@ -0,0 +1,177 @@ +/** + * @file llsdutil.cpp + * @author Phoenix + * @date 2006-05-24 + * @brief Implementation of classes, functions, etc, for using structured data. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#if LL_LINUX +#include +#endif +#if LL_DARWIN +#include +#endif + +#include "linden_common.h" +#include "llsdutil.h" + +// vector3 +LLSD ll_sd_from_vector3(const LLVector3& vec) +{ + LLSD rv; + rv.append((F64)vec.mV[VX]); + rv.append((F64)vec.mV[VY]); + rv.append((F64)vec.mV[VZ]); + return rv; +} + +LLVector3 ll_vector3_from_sd(const LLSD& sd, S32 start_index) +{ + LLVector3 rv; + rv.mV[VX] = (F32)sd[start_index].asReal(); + rv.mV[VY] = (F32)sd[++start_index].asReal(); + rv.mV[VZ] = (F32)sd[++start_index].asReal(); + return rv; +} + +// vector3d +LLSD ll_sd_from_vector3d(const LLVector3d& vec) +{ + LLSD rv; + rv.append(vec.mdV[VX]); + rv.append(vec.mdV[VY]); + rv.append(vec.mdV[VZ]); + return rv; +} + +LLVector3d ll_vector3d_from_sd(const LLSD& sd, S32 start_index) +{ + LLVector3d rv; + rv.mdV[VX] = sd[start_index].asReal(); + rv.mdV[VY] = sd[++start_index].asReal(); + rv.mdV[VZ] = sd[++start_index].asReal(); + return rv; +} + +//vector2 +LLSD ll_sd_from_vector2(const LLVector2& vec) +{ + LLSD rv; + rv.append((F64)vec.mV[VX]); + rv.append((F64)vec.mV[VY]); + return rv; +} + +LLVector2 ll_vector2_from_sd(const LLSD& sd) +{ + LLVector2 rv; + rv.mV[VX] = (F32)sd[0].asReal(); + rv.mV[VY] = (F32)sd[1].asReal(); + return rv; +} + +// Quaternion +LLSD ll_sd_from_quaternion(const LLQuaternion& quat) +{ + LLSD rv; + rv.append((F64)quat.mQ[VX]); + rv.append((F64)quat.mQ[VY]); + rv.append((F64)quat.mQ[VZ]); + rv.append((F64)quat.mQ[VW]); + return rv; +} + +LLQuaternion ll_quaternion_from_sd(const LLSD& sd) +{ + LLQuaternion quat; + quat.mQ[VX] = (F32)sd[0].asReal(); + quat.mQ[VY] = (F32)sd[1].asReal(); + quat.mQ[VZ] = (F32)sd[2].asReal(); + quat.mQ[VW] = (F32)sd[3].asReal(); + return quat; +} + +// color4 +LLSD ll_sd_from_color4(const LLColor4& c) +{ + LLSD rv; + rv.append(c.mV[0]); + rv.append(c.mV[1]); + rv.append(c.mV[2]); + rv.append(c.mV[3]); + return rv; +} + +LLColor4 ll_color4_from_sd(const LLSD& sd) +{ + LLColor4 c; + c.mV[0] = (F32)sd[0].asReal(); + c.mV[1] = (F32)sd[1].asReal(); + c.mV[2] = (F32)sd[2].asReal(); + c.mV[3] = (F32)sd[3].asReal(); + return c; +} + +// U32 +LLSD ll_sd_from_U32(const U32 val) +{ + std::vector v; + U32 net_order = htonl(val); + + v.resize(4); + memcpy(&(v[0]), &net_order, 4); /* Flawfinder: ignore */ + + return LLSD(v); +} + +U32 ll_U32_from_sd(const LLSD& sd) +{ + U32 ret; + std::vector v = sd.asBinary(); + if (v.size() < 4) + { + return 0; + } + memcpy(&ret, &(v[0]), 4); /* Flawfinder: ignore */ + ret = ntohl(ret); + return ret; +} + +//U64 +LLSD ll_sd_from_U64(const U64 val) +{ + std::vector v; + U32 high, low; + + high = (U32)(val >> 32); + low = (U32)val; + high = htonl(high); + low = htonl(low); + + v.resize(8); + memcpy(&(v[0]), &high, 4); /* Flawfinder: ignore */ + memcpy(&(v[4]), &low, 4); /* Flawfinder: ignore */ + + return LLSD(v); +} + +U64 ll_U64_from_sd(const LLSD& sd) +{ + U32 high, low; + std::vector v = sd.asBinary(); + + if (v.size() < 8) + { + return 0; + } + + memcpy(&high, &(v[0]), 4); /* Flawfinder: ignore */ + memcpy(&low, &(v[4]), 4); /* Flawfinder: ignore */ + high = ntohl(high); + low = ntohl(low); + + return ((U64)high) << 32 | low; +} diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h new file mode 100644 index 0000000000..9369f1a39f --- /dev/null +++ b/indra/llcommon/llsdutil.h @@ -0,0 +1,50 @@ +/** + * @file llsdutil.h + * @author Phoenix + * @date 2006-05-24 + * @brief Utility classes, functions, etc, for using structured data. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSDUTIL_H +#define LL_LLSDUTIL_H + +#include "llsd.h" +#include "../llmath/v3math.h" +#include "../llmath/v3dmath.h" +#include "../llmath/v2math.h" +#include "../llmath/llquaternion.h" +#include "../llmath/v4color.h" +#include "../llprimitive/lltextureanim.h" + +// vector3 +LLSD ll_sd_from_vector3(const LLVector3& vec); +LLVector3 ll_vector3_from_sd(const LLSD& sd, S32 start_index = 0); + +// vector3d (double) +LLSD ll_sd_from_vector3d(const LLVector3d& vec); +LLVector3d ll_vector3d_from_sd(const LLSD& sd, S32 start_index = 0); + +// vector2 +LLSD ll_sd_from_vector2(const LLVector2& vec); +LLVector2 ll_vector2_from_sd(const LLSD& sd); + +// Quaternion +LLSD ll_sd_from_quaternion(const LLQuaternion& quat); +LLQuaternion ll_quaternion_from_sd(const LLSD& sd); + +// color4 +LLSD ll_sd_from_color4(const LLColor4& c); +LLColor4 ll_color4_from_sd(const LLSD& sd); + +// U32 +LLSD ll_sd_from_U32(const U32); +U32 ll_U32_from_sd(const LLSD& sd); + +// U64 +LLSD ll_sd_from_U64(const U64); +U64 ll_U64_from_sd(const LLSD& sd); + +#endif // LL_LLSDUTIL_H diff --git a/indra/llcommon/llsecondlifeurls.cpp b/indra/llcommon/llsecondlifeurls.cpp new file mode 100644 index 0000000000..9d5395ad07 --- /dev/null +++ b/indra/llcommon/llsecondlifeurls.cpp @@ -0,0 +1,63 @@ +/** + * @file llsecondlifeurls.cpp + * @brief Urls used in the product + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llsecondlifeurls.h" + +const char CREATE_ACCOUNT_URL[] = + "http://secondlife.com/registration/"; + +const char MANAGE_ACCOUNT[] = + "http://secondlife.com/account/"; + +const char AUCTION_URL[] = + "http://secondlife.com/auctions/auction-detail.php?id="; + +const char EVENTS_URL[] = + "http://secondlife.com/events/"; + +const char TIER_UP_URL[] = + "http://secondlife.com/app/landtier"; + +const char LAND_URL[] = + "http://secondlife.com/app/landtier"; + +const char UPGRADE_TO_PREMIUM_URL[] = + "http://secondlife.com/app/upgrade/"; + +const char DIRECTX_9_URL[] = + "http://secondlife.com/support/"; + +const char AMD_AGP_URL[] = + "http://secondlife.com/support/"; + +const char VIA_URL[] = + "http://secondlife.com/support/"; + +const char INTEL_CHIPSET_URL[] = + "http://secondlife.com/support/"; + +const char SIS_CHIPSET_URL[] = + "http://secondlife.com/support/"; + +const char BLOGS_URL[] = + "http://blog.secondlife.com/"; + +const char BUY_CURRENCY_URL[] = + "http://secondlife.com/app/currency/"; + +const char LSL_DOC_URL[] = + "http://secondlife.com/app/lsldoc/"; + +const char SL_KB_URL[] = + "http://secondlife.com/knowledgebase/"; + +const char ACCOUNT_TRANSACTIONS_URL[] = + "https://secondlife.com/account/transactions.php"; + +const char RELEASE_NOTES[] = "releasenotes.txt"; diff --git a/indra/llcommon/llsecondlifeurls.h b/indra/llcommon/llsecondlifeurls.h new file mode 100644 index 0000000000..86d18d0eab --- /dev/null +++ b/indra/llcommon/llsecondlifeurls.h @@ -0,0 +1,64 @@ +/** + * @file llsecondlifeurls.h + * @brief Global URLs to pages on our web site + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSECONDLIFEURLS_H +#define LL_LLSECONDLIFEURLS_H + +// Account registration web page +extern const char CREATE_ACCOUNT_URL[]; + +// Manage Account +extern const char MANAGE_ACCOUNT[]; + +extern const char AUCTION_URL[]; + +extern const char EVENTS_URL[]; + +// Tier up to a new land level. +extern const char TIER_UP_URL[]; + +// Tier up to a new land level. +extern const char LAND_URL[]; + +// Upgrade from basic membership to premium membership +extern const char UPGRADE_TO_PREMIUM_URL[]; + +// How to get DirectX 9 +extern const char DIRECTX_9_URL[]; + +// On AMD with bad AGP controller +extern const char AMD_AGP_URL[]; + +// Out of date VIA chipset +extern const char VIA_URL[]; + +// Out of date intel chipset driver +extern const char INTEL_CHIPSET_URL[]; + +// Out of date SiS chipset driver +extern const char SIS_CHIPSET_URL[]; + +// Linden Blogs page +extern const char BLOGS_URL[]; + +// Currency page +extern const char BUY_CURRENCY_URL[]; + +// LSL script wiki +extern const char LSL_DOC_URL[]; + +// SL KnowledgeBase page +extern const char SL_KB_URL[]; + +// Account transactions +extern const char ACCOUNT_TRANSACTIONS_URL[]; + +// Local Url Release Notes +extern const char RELEASE_NOTES[]; + +#endif diff --git a/indra/llcommon/llsimplehash.h b/indra/llcommon/llsimplehash.h new file mode 100644 index 0000000000..0acac99c45 --- /dev/null +++ b/indra/llcommon/llsimplehash.h @@ -0,0 +1,137 @@ +/** + * @file llsimplehash.h + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSIMPLEHASH_H +#define LL_LLSIMPLEHASH_H + +#include "llstl.h" + +template +class LLSimpleHashEntry +{ +protected: + HASH_KEY_TYPE mHashKey; + LLSimpleHashEntry* mNextEntry; +public: + LLSimpleHashEntry(HASH_KEY_TYPE key) : + mHashKey(key), + mNextEntry(0) + { + } + virtual ~LLSimpleHashEntry() + { + } + HASH_KEY_TYPE getHashKey() const + { + return mHashKey; + } + LLSimpleHashEntry* getNextEntry() const + { + return mNextEntry; + } + void setNextEntry(LLSimpleHashEntry* next) + { + mNextEntry = next; + } +}; + +template +class LLSimpleHash +{ +public: + LLSimpleHash() + { + llassert(TABLE_SIZE); + llassert((TABLE_SIZE ^ (TABLE_SIZE-1)) == (TABLE_SIZE | (TABLE_SIZE-1))); // power of 2 + memset(mEntryTable, 0, sizeof(mEntryTable)); + } + virtual ~LLSimpleHash() + { + } + + virtual int getIndex(HASH_KEY_TYPE key) + { + return key & (TABLE_SIZE-1); + } + + bool insert(LLSimpleHashEntry* entry) + { + llassert(entry->getNextEntry() == 0); + int index = getIndex(entry->getHashKey()); + entry->setNextEntry(mEntryTable[index]); + mEntryTable[index] = entry; + return true; + } + LLSimpleHashEntry* find(HASH_KEY_TYPE key) + { + int index = getIndex(key); + LLSimpleHashEntry* res = mEntryTable[index]; + while(res && (res->getHashKey() != key)) + { + res = res->getNextEntry(); + } + return res; + } + bool erase(LLSimpleHashEntry* entry) + { + return erase(entry->getHashKey()); + } + bool erase(HASH_KEY_TYPE key) + { + int index = getIndex(key); + LLSimpleHashEntry* prev = 0; + LLSimpleHashEntry* res = mEntryTable[index]; + while(res && (res->getHashKey() != key)) + { + prev = res; + res = res->getNextEntry(); + } + if (res) + { + LLSimpleHashEntry* next = res->getNextEntry(); + if (prev) + { + prev->setNextEntry(next); + } + else + { + mEntryTable[index] = next; + } + return true; + } + else + { + return false; + } + } + // Removes and returns an arbitrary ("first") element from the table + // Used for deleting the entire table. + LLSimpleHashEntry* pop_element() + { + for (int i=0; i* entry = mEntryTable[i]; + if (entry) + { + mEntryTable[i] = entry->getNextEntry(); + return entry; + } + } + return 0; + } + // debugging + LLSimpleHashEntry* get_element_at_index(S32 index) const + { + return mEntryTable[index]; + } + + +private: + LLSimpleHashEntry* mEntryTable[TABLE_SIZE]; +}; + +#endif // LL_LLSIMPLEHASH_H diff --git a/indra/llcommon/llskiplist.h b/indra/llcommon/llskiplist.h new file mode 100644 index 0000000000..a86eb05d46 --- /dev/null +++ b/indra/llcommon/llskiplist.h @@ -0,0 +1,502 @@ +/** + * @file llskiplist.h + * @brief skip list implementation + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ +#ifndef LL_LLSKIPLIST_H +#define LL_LLSKIPLIST_H + +#include "llerror.h" +//#include "vmath.h" + +// NOTA BENE: Insert first needs to be < NOT <= + +template +class LLSkipList +{ +public: + typedef BOOL (*compare)(const DATA_TYPE& first, const DATA_TYPE& second); + typedef compare insert_func; + typedef compare equals_func; + + void init(); + + // basic constructor + LLSkipList(); + + // basic constructor including sorter + LLSkipList(insert_func insert_first, equals_func equals); + ~LLSkipList(); + + inline void setInsertFirst(insert_func insert_first); + inline void setEquals(equals_func equals); + + inline BOOL addData(const DATA_TYPE& data); + inline BOOL checkData(const DATA_TYPE& data); + + // returns number of items in the list + inline S32 getLength() const; // NOT a constant time operation, traverses entire list! + + inline BOOL moveData(const DATA_TYPE& data, LLSkipList *newlist); + + inline BOOL removeData(const DATA_TYPE& data); + + // remove all nodes from the list but do not delete data + inline void removeAllNodes(); + + // place mCurrentp on first node + inline void resetList(); + + // return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp + inline DATA_TYPE getCurrentData(); + + // same as getCurrentData() but a more intuitive name for the operation + inline DATA_TYPE getNextData(); + + // remove the Node at mCurentOperatingp + // leave mCurrentp and mCurentOperatingp on the next entry + inline void removeCurrentData(); + + // reset the list and return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp + inline DATA_TYPE getFirstData(); + + class LLSkipNode + { + public: + LLSkipNode() + : mData(0) + { + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mForward[i] = NULL; + } + } + + LLSkipNode(DATA_TYPE data) + : mData(data) + { + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mForward[i] = NULL; + } + } + + ~LLSkipNode() + { + } + + DATA_TYPE mData; + LLSkipNode *mForward[BINARY_DEPTH]; + + private: + // Disallow copying of LLSkipNodes by not implementing these methods. + LLSkipNode(const LLSkipNode &); + LLSkipNode &operator=(const LLSkipNode &); + }; + + static BOOL defaultEquals(const DATA_TYPE& first, const DATA_TYPE& second) + { + return first == second; + } + +private: + LLSkipNode mHead; + LLSkipNode *mUpdate[BINARY_DEPTH]; + LLSkipNode *mCurrentp; + LLSkipNode *mCurrentOperatingp; + S32 mLevel; + insert_func mInsertFirst; + equals_func mEquals; + +private: + // Disallow copying of LLSkipNodes by not implementing these methods. + LLSkipList(const LLSkipList &); + LLSkipList &operator=(const LLSkipList &); +}; + + +/////////////////////// +// +// Implementation +// + +template +inline void LLSkipList::init() +{ + if (BINARY_DEPTH < 2) + { + llerrs << "Trying to create skip list with too little depth, " + "must be 2 or greater" << llendl; + } + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mHead.mForward[i] = NULL; + mUpdate[i] = NULL; + } + mLevel = 1; + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + + +// basic constructor +template +inline LLSkipList::LLSkipList() +: mInsertFirst(NULL), + mEquals(defaultEquals) +{ + init(); +} + +// basic constructor including sorter +template +inline LLSkipList::LLSkipList(insert_func insert, + equals_func equals) +: mInsertFirst(insert), + mEquals(equals) +{ + init(); +} + +template +inline LLSkipList::~LLSkipList() +{ + removeAllNodes(); +} + +template +inline void LLSkipList::setInsertFirst(insert_func insert_first) +{ + mInsertFirst = insert_first; +} + +template +inline void LLSkipList::setEquals(equals_func equals) +{ + mEquals = equals; +} + +template +inline BOOL LLSkipList::addData(const DATA_TYPE& data) +{ + S32 level; + LLSkipNode *current = &mHead; + LLSkipNode *temp; + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mData, data))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mData < data)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + // now add the new node + S32 newlevel; + for (newlevel = 1; newlevel <= mLevel && newlevel < BINARY_DEPTH; newlevel++) + { + if (frand(1.f) < 0.5f) + break; + } + + LLSkipNode *snode = new LLSkipNode(data); + + if (newlevel > mLevel) + { + mHead.mForward[mLevel] = NULL; + mUpdate[mLevel] = &mHead; + mLevel = newlevel; + } + + for (level = 0; level < newlevel; level++) + { + snode->mForward[level] = mUpdate[level]->mForward[level]; + mUpdate[level]->mForward[level] = snode; + } + return TRUE; +} + +template +inline BOOL LLSkipList::checkData(const DATA_TYPE& data) +{ + S32 level; + LLSkipNode *current = &mHead; + LLSkipNode *temp; + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mData, data))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mData < data)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + + if (current) + { + return mEquals(current->mData, data); + } + + return FALSE; +} + +// returns number of items in the list +template +inline S32 LLSkipList::getLength() const +{ + U32 length = 0; + for (LLSkipNode* temp = *(mHead.mForward); temp != NULL; temp = temp->mForward[0]) + { + length++; + } + return length; +} + + +template +inline BOOL LLSkipList::moveData(const DATA_TYPE& data, LLSkipList *newlist) +{ + BOOL removed = removeData(data); + BOOL added = newlist->addData(data); + return removed && added; +} + + +template +inline BOOL LLSkipList::removeData(const DATA_TYPE& data) +{ + S32 level; + LLSkipNode *current = &mHead; + LLSkipNode *temp; + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mData, data))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mData < data)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + + if (!current) + { + // empty list or beyond the end! + return FALSE; + } + + // is this the one we want? + if (!mEquals(current->mData, data)) + { + // nope! + return FALSE; + } + else + { + // do we need to fix current or currentop? + if (current == mCurrentp) + { + mCurrentp = current->mForward[0]; + } + + if (current == mCurrentOperatingp) + { + mCurrentOperatingp = current->mForward[0]; + } + // yes it is! change pointers as required + for (level = 0; level < mLevel; level++) + { + if (mUpdate[level]->mForward[level] != current) + { + // cool, we've fixed all the pointers! + break; + } + mUpdate[level]->mForward[level] = current->mForward[level]; + } + + // clean up cuurent + delete current; + + // clean up mHead + while ( (mLevel > 1) + &&(!mHead.mForward[mLevel - 1])) + { + mLevel--; + } + } + return TRUE; +} + +// remove all nodes from the list but do not delete data +template +inline void LLSkipList::removeAllNodes() +{ + LLSkipNode *temp; + // reset mCurrentp + mCurrentp = *(mHead.mForward); + + while (mCurrentp) + { + temp = mCurrentp->mForward[0]; + delete mCurrentp; + mCurrentp = temp; + } + + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mHead.mForward[i] = NULL; + mUpdate[i] = NULL; + } + + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + +// place mCurrentp on first node +template +inline void LLSkipList::resetList() +{ + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + +// return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp +template +inline DATA_TYPE LLSkipList::getCurrentData() +{ + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mForward[0]; + return mCurrentOperatingp->mData; + } + else + { + //return NULL; // causes compile warning + return (DATA_TYPE)0; // equivalent, but no warning + } +} + +// same as getCurrentData() but a more intuitive name for the operation +template +inline DATA_TYPE LLSkipList::getNextData() +{ + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mForward[0]; + return mCurrentOperatingp->mData; + } + else + { + //return NULL; // causes compile warning + return (DATA_TYPE)0; // equivalent, but no warning + } +} + +// remove the Node at mCurentOperatingp +// leave mCurrentp and mCurentOperatingp on the next entry +template +inline void LLSkipList::removeCurrentData() +{ + if (mCurrentOperatingp) + { + removeData(mCurrentOperatingp->mData); + } +} + +// reset the list and return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp +template +inline DATA_TYPE LLSkipList::getFirstData() +{ + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mForward[0]; + return mCurrentOperatingp->mData; + } + else + { + //return NULL; // causes compile warning + return (DATA_TYPE)0; // equivalent, but no warning + } +} + + +#endif diff --git a/indra/llcommon/llskipmap.h b/indra/llcommon/llskipmap.h new file mode 100644 index 0000000000..52270d89ba --- /dev/null +++ b/indra/llcommon/llskipmap.h @@ -0,0 +1,1004 @@ +/** + * @file llskipmap.h + * @brief Associative container based on the skiplist algorithm. + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSKIPMAP_H +#define LL_LLSKIPMAP_H + +#include "llerror.h" + +template +class LLSkipMap +{ +public: + // basic constructor + LLSkipMap(); + + // basic constructor including sorter + LLSkipMap(BOOL (*insert_first)(const INDEX_TYPE &first, const INDEX_TYPE &second), + BOOL (*equals)(const INDEX_TYPE &first, const INDEX_TYPE &second)); + + ~LLSkipMap(); + + void setInsertFirst(BOOL (*insert_first)(const INDEX_TYPE &first, const INDEX_TYPE &second)); + void setEquals(BOOL (*equals)(const INDEX_TYPE &first, const INDEX_TYPE &second)); + + DATA_TYPE &addData(const INDEX_TYPE &index, DATA_TYPE datap); + DATA_TYPE &addData(const INDEX_TYPE &index); + DATA_TYPE &getData(const INDEX_TYPE &index); + DATA_TYPE &operator[](const INDEX_TYPE &index); + + // If index present, returns data. + // If index not present, adds and returns NULL. + DATA_TYPE &getData(const INDEX_TYPE &index, BOOL &b_new_entry); + + // Returns TRUE if data present in map. + BOOL checkData(const INDEX_TYPE &index); + + // Returns TRUE if key is present in map. This is useful if you + // are potentially storing NULL pointers in the map + BOOL checkKey(const INDEX_TYPE &index); + + // If there, returns the data. + // If not, returns NULL. + // Never adds entries to the map. + DATA_TYPE getIfThere(const INDEX_TYPE &index); + + INDEX_TYPE reverseLookup(const DATA_TYPE datap); + + // returns number of items in the list + S32 getLength(); // WARNING! getLength is O(n), not O(1)! + + BOOL removeData(const INDEX_TYPE &index); + + // remove all nodes from the list but do not delete data + void removeAllData(); + + // place mCurrentp on first node + void resetList(); + + // return the data currently pointed to + DATA_TYPE getCurrentDataWithoutIncrement(); + + // return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp + DATA_TYPE getCurrentData(); + + // same as getCurrentData() but a more intuitive name for the operation + DATA_TYPE getNextData(); + + INDEX_TYPE getNextKey(); + + // return the key currently pointed to + INDEX_TYPE getCurrentKeyWithoutIncrement(); + + // The internal iterator is at the end of the list. + BOOL notDone() const; + + // remove the Node at mCurentOperatingp + // leave mCurrentp and mCurentOperatingp on the next entry + void removeCurrentData(); + + void deleteCurrentData(); + + // reset the list and return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp + DATA_TYPE getFirstData(); + + INDEX_TYPE getFirstKey(); + + class LLSkipMapNode + { + public: + LLSkipMapNode() + { + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mForward[i] = NULL; + } + + U8 *zero = (U8 *)&mIndex; + + for (i = 0; i < (S32)sizeof(INDEX_TYPE); i++) + { + *(zero + i) = 0; + } + + zero = (U8 *)&mData; + + for (i = 0; i < (S32)sizeof(DATA_TYPE); i++) + { + *(zero + i) = 0; + } + } + + LLSkipMapNode(const INDEX_TYPE &index) + : mIndex(index) + { + + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mForward[i] = NULL; + } + + U8 *zero = (U8 *)&mData; + + for (i = 0; i < (S32)sizeof(DATA_TYPE); i++) + { + *(zero + i) = 0; + } + } + + LLSkipMapNode(const INDEX_TYPE &index, DATA_TYPE datap) + : mIndex(index) + { + + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mForward[i] = NULL; + } + + mData = datap; + } + + ~LLSkipMapNode() + { + } + + + INDEX_TYPE mIndex; + DATA_TYPE mData; + LLSkipMapNode *mForward[BINARY_DEPTH]; + + private: + // Disallow copying of LLSkipMapNodes by not implementing these methods. + LLSkipMapNode(const LLSkipMapNode &); + LLSkipMapNode &operator=(const LLSkipMapNode &rhs); + }; + + static BOOL defaultEquals(const INDEX_TYPE &first, const INDEX_TYPE &second) + { + return first == second; + } + +private: + // don't generate implicit copy constructor or copy assignment + LLSkipMap(const LLSkipMap &); + LLSkipMap &operator=(const LLSkipMap &); + +private: + LLSkipMapNode mHead; + LLSkipMapNode *mUpdate[BINARY_DEPTH]; + LLSkipMapNode *mCurrentp; + LLSkipMapNode *mCurrentOperatingp; + S32 mLevel; + BOOL (*mInsertFirst)(const INDEX_TYPE &first, const INDEX_TYPE &second); + BOOL (*mEquals)(const INDEX_TYPE &first, const INDEX_TYPE &second); + S32 mNumberOfSteps; +}; + +////////////////////////////////////////////////// +// +// LLSkipMap implementation +// + +template +inline LLSkipMap::LLSkipMap() + : mInsertFirst(NULL), + mEquals(defaultEquals) +{ + // Skipmaps must have binary depth of at least 2 + cassert(BINARY_DEPTH >= 2); + + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mUpdate[i] = NULL; + } + mLevel = 1; + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + +template +inline LLSkipMap::LLSkipMap(BOOL (*insert_first)(const INDEX_TYPE &first, const INDEX_TYPE &second), + BOOL (*equals)(const INDEX_TYPE &first, const INDEX_TYPE &second)) + : mInsertFirst(insert_first), + mEquals(equals) +{ + // Skipmaps must have binary depth of at least 2 + cassert(BINARY_DEPTH >= 2); + + mLevel = 1; + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mHead.mForward[i] = NULL; + mUpdate[i] = NULL; + } + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + +template +inline LLSkipMap::~LLSkipMap() +{ + removeAllData(); +} + +template +inline void LLSkipMap::setInsertFirst(BOOL (*insert_first)(const INDEX_TYPE &first, const INDEX_TYPE &second)) +{ + mInsertFirst = insert_first; +} + +template +inline void LLSkipMap::setEquals(BOOL (*equals)(const INDEX_TYPE &first, const INDEX_TYPE &second)) +{ + mEquals = equals; +} + +template +inline DATA_TYPE &LLSkipMap::addData(const INDEX_TYPE &index, DATA_TYPE datap) +{ + S32 level; + LLSkipMapNode *current = &mHead; + LLSkipMapNode *temp; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + // replace the existing data if a node is already there + if ( (current) + &&(mEquals(current->mIndex, index))) + { + current->mData = datap; + return current->mData; + } + + // now add the new node + S32 newlevel; + for (newlevel = 1; newlevel <= mLevel && newlevel < BINARY_DEPTH; newlevel++) + { + if (rand() & 1) + { + break; + } + } + + LLSkipMapNode *snode = new LLSkipMapNode(index, datap); + + if (newlevel > mLevel) + { + mHead.mForward[mLevel] = NULL; + mUpdate[mLevel] = &mHead; + mLevel = newlevel; + } + + for (level = 0; level < newlevel; level++) + { + snode->mForward[level] = mUpdate[level]->mForward[level]; + mUpdate[level]->mForward[level] = snode; + } + return snode->mData; +} + +template +inline DATA_TYPE &LLSkipMap::addData(const INDEX_TYPE &index) +{ + S32 level; + LLSkipMapNode *current = &mHead; + LLSkipMapNode *temp; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + // now add the new node + S32 newlevel; + for (newlevel = 1; newlevel <= mLevel && newlevel < BINARY_DEPTH; newlevel++) + { + if (rand() & 1) + break; + } + + LLSkipMapNode *snode = new LLSkipMapNode(index); + + if (newlevel > mLevel) + { + mHead.mForward[mLevel] = NULL; + mUpdate[mLevel] = &mHead; + mLevel = newlevel; + } + + for (level = 0; level < newlevel; level++) + { + snode->mForward[level] = mUpdate[level]->mForward[level]; + mUpdate[level]->mForward[level] = snode; + } + return snode->mData; +} + +template +inline DATA_TYPE &LLSkipMap::getData(const INDEX_TYPE &index) +{ + S32 level; + LLSkipMapNode *current = &mHead; + LLSkipMapNode *temp; + + mNumberOfSteps = 0; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + mNumberOfSteps++; + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + mNumberOfSteps++; + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + mNumberOfSteps++; + + if ( (current) + &&(mEquals(current->mIndex, index))) + { + + return current->mData; + } + + // now add the new node + S32 newlevel; + for (newlevel = 1; newlevel <= mLevel && newlevel < BINARY_DEPTH; newlevel++) + { + if (rand() & 1) + break; + } + + LLSkipMapNode *snode = new LLSkipMapNode(index); + + if (newlevel > mLevel) + { + mHead.mForward[mLevel] = NULL; + mUpdate[mLevel] = &mHead; + mLevel = newlevel; + } + + for (level = 0; level < newlevel; level++) + { + snode->mForward[level] = mUpdate[level]->mForward[level]; + mUpdate[level]->mForward[level] = snode; + } + + return snode->mData; +} + +template +inline DATA_TYPE &LLSkipMap::operator[](const INDEX_TYPE &index) +{ + + return getData(index); +} + +// If index present, returns data. +// If index not present, adds and returns NULL. +template +inline DATA_TYPE &LLSkipMap::getData(const INDEX_TYPE &index, BOOL &b_new_entry) +{ + S32 level; + LLSkipMapNode *current = &mHead; + LLSkipMapNode *temp; + + mNumberOfSteps = 0; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + mNumberOfSteps++; + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + mNumberOfSteps++; + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + mNumberOfSteps++; + current = *current->mForward; + + if ( (current) + &&(mEquals(current->mIndex, index))) + { + + return current->mData; + } + b_new_entry = TRUE; + addData(index); + + return current->mData; +} + +// Returns TRUE if data present in map. +template +inline BOOL LLSkipMap::checkData(const INDEX_TYPE &index) +{ + S32 level; + LLSkipMapNode *current = &mHead; + LLSkipMapNode *temp; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + if (current) + { + // Gets rid of some compiler ambiguity for the LLPointer<> templated class. + if (current->mData) + { + return mEquals(current->mIndex, index); + } + } + + return FALSE; +} + +// Returns TRUE if key is present in map. This is useful if you +// are potentially storing NULL pointers in the map +template +inline BOOL LLSkipMap::checkKey(const INDEX_TYPE &index) +{ + S32 level; + LLSkipMapNode *current = &mHead; + LLSkipMapNode *temp; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + if (current) + { + return mEquals(current->mIndex, index); + } + + return FALSE; +} + +// If there, returns the data. +// If not, returns NULL. +// Never adds entries to the map. +template +inline DATA_TYPE LLSkipMap::getIfThere(const INDEX_TYPE &index) +{ + S32 level; + LLSkipMapNode *current = &mHead; + LLSkipMapNode *temp; + + mNumberOfSteps = 0; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + mNumberOfSteps++; + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + mNumberOfSteps++; + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + mNumberOfSteps++; + current = *current->mForward; + + if (current) + { + if (mEquals(current->mIndex, index)) + { + return current->mData; + } + } + + // Avoid Linux compiler warning on returning NULL. + return DATA_TYPE(); +} + +template +inline INDEX_TYPE LLSkipMap::reverseLookup(const DATA_TYPE datap) +{ + LLSkipMapNode *current = &mHead; + + while (current) + { + if (datap == current->mData) + { + + return current->mIndex; + } + current = *current->mForward; + } + + // not found! return NULL + return INDEX_TYPE(); +} + +// returns number of items in the list +template +inline S32 LLSkipMap::getLength() +{ + U32 length = 0; + for (LLSkipMapNode* temp = *(mHead.mForward); temp != NULL; temp = temp->mForward[0]) + { + length++; + } + + return length; +} + +template +inline BOOL LLSkipMap::removeData(const INDEX_TYPE &index) +{ + S32 level; + LLSkipMapNode *current = &mHead; + LLSkipMapNode *temp; + + // find the pointer one in front of the one we want + if (mInsertFirst) + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(mInsertFirst(temp->mIndex, index))) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + else + { + for (level = mLevel - 1; level >= 0; level--) + { + temp = *(current->mForward + level); + while ( (temp) + &&(temp->mIndex < index)) + { + current = temp; + temp = *(current->mForward + level); + } + *(mUpdate + level) = current; + } + } + + // we're now just in front of where we want to be . . . take one step forward + current = *current->mForward; + + if (!current) + { + // empty list or beyond the end! + + return FALSE; + } + + // is this the one we want? + if (!mEquals(current->mIndex, index)) + { + // nope! + + return FALSE; + } + else + { + // do we need to fix current or currentop? + if (current == mCurrentp) + { + mCurrentp = *current->mForward; + } + + if (current == mCurrentOperatingp) + { + mCurrentOperatingp = *current->mForward; + } + // yes it is! change pointers as required + for (level = 0; level < mLevel; level++) + { + if (*((*(mUpdate + level))->mForward + level) != current) + { + // cool, we've fixed all the pointers! + break; + } + *((*(mUpdate + level))->mForward + level) = *(current->mForward + level); + } + + delete current; + + // clean up mHead + while ( (mLevel > 1) + &&(!*(mHead.mForward + mLevel - 1))) + { + mLevel--; + } + } + + return TRUE; +} + + +// remove all nodes from the list but do not delete data +template +void LLSkipMap::removeAllData() +{ + LLSkipMapNode *temp; + // reset mCurrentp + mCurrentp = *(mHead.mForward); + + while (mCurrentp) + { + temp = mCurrentp->mForward[0]; + delete mCurrentp; + mCurrentp = temp; + } + + S32 i; + for (i = 0; i < BINARY_DEPTH; i++) + { + mHead.mForward[i] = NULL; + mUpdate[i] = NULL; + } + + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + + +// place mCurrentp on first node +template +inline void LLSkipMap::resetList() +{ + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); +} + + +// return the data currently pointed to +template +inline DATA_TYPE LLSkipMap::getCurrentDataWithoutIncrement() +{ + if (mCurrentOperatingp) + { + return mCurrentOperatingp->mData; + } + else + { + return DATA_TYPE(); + } +} + +// return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp +template +inline DATA_TYPE LLSkipMap::getCurrentData() +{ + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mForward[0]; + return mCurrentOperatingp->mData; + } + else + { + // Basic types, like int, have default constructors that initialize + // them to zero. g++ 2.95 supports this. "int()" is zero. + // This also is nice for LLUUID() + return DATA_TYPE(); + } +} + +// same as getCurrentData() but a more intuitive name for the operation +template +inline DATA_TYPE LLSkipMap::getNextData() +{ + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mForward[0]; + return mCurrentOperatingp->mData; + } + else + { + // Basic types, like int, have default constructors that initialize + // them to zero. g++ 2.95 supports this. "int()" is zero. + // This also is nice for LLUUID() + return DATA_TYPE(); + } +} + +template +inline INDEX_TYPE LLSkipMap::getNextKey() +{ + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mForward[0]; + return mCurrentOperatingp->mIndex; + } + else + { + return mHead.mIndex; + } +} + +// return the key currently pointed to +template +inline INDEX_TYPE LLSkipMap::getCurrentKeyWithoutIncrement() +{ + if (mCurrentOperatingp) + { + return mCurrentOperatingp->mIndex; + } + else + { + // See comment for getNextData() + return INDEX_TYPE(); + } +} + +template +inline BOOL LLSkipMap::notDone() const +{ + if (mCurrentOperatingp) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +// remove the Node at mCurentOperatingp +// leave mCurrentp and mCurentOperatingp on the next entry +template +inline void LLSkipMap::removeCurrentData() +{ + if (mCurrentOperatingp) + { + removeData(mCurrentOperatingp->mIndex); + } +} + +template +inline void LLSkipMap::deleteCurrentData() +{ + if (mCurrentOperatingp) + { + deleteData(mCurrentOperatingp->mIndex); + } +} + +// reset the list and return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp +template +inline DATA_TYPE LLSkipMap::getFirstData() +{ + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mForward[0]; + return mCurrentOperatingp->mData; + } + else + { + // See comment for getNextData() + return DATA_TYPE(); + } +} + +template +inline INDEX_TYPE LLSkipMap::getFirstKey() +{ + mCurrentp = *(mHead.mForward); + mCurrentOperatingp = *(mHead.mForward); + if (mCurrentp) + { + mCurrentOperatingp = mCurrentp; + mCurrentp = mCurrentp->mForward[0]; + return mCurrentOperatingp->mIndex; + } + else + { + return mHead.mIndex; + } +} + +#endif diff --git a/indra/llcommon/llstack.h b/indra/llcommon/llstack.h new file mode 100644 index 0000000000..024acf149e --- /dev/null +++ b/indra/llcommon/llstack.h @@ -0,0 +1,30 @@ +/** + * @file llstack.h + * @brief LLStack template class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSTACK_H +#define LL_LLSTACK_H + +#include "linked_lists.h" + +template class LLStack +{ +private: + LLLinkedList mStack; + +public: + LLStack() {} + ~LLStack() {} + + void push(DATA_TYPE *data) { mStack.addData(data); } + DATA_TYPE *pop() { DATA_TYPE *tempp = mStack.getFirstData(); mStack.removeCurrentData(); return tempp; } + void deleteAllData() { mStack.deleteAllData(); } + void removeAllNodes() { mStack.removeAllNodes(); } +}; + +#endif + diff --git a/indra/llcommon/llstat.cpp b/indra/llcommon/llstat.cpp new file mode 100644 index 0000000000..ef707d3497 --- /dev/null +++ b/indra/llcommon/llstat.cpp @@ -0,0 +1,803 @@ +/** + * @file llstat.cpp + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llstat.h" +#include "llframetimer.h" +#include "timing.h" + +class LLStatAccum::impl +{ +public: + static const TimeScale IMPL_NUM_SCALES = (TimeScale)(SCALE_TWO_MINUTE + 1); + static U64 sScaleTimes[IMPL_NUM_SCALES]; + + BOOL mUseFrameTimer; + + BOOL mRunning; + U64 mLastTime; + + struct Bucket + { + F64 accum; + U64 endTime; + + BOOL lastValid; + F64 lastAccum; + }; + + Bucket mBuckets[IMPL_NUM_SCALES]; + + BOOL mLastSampleValid; + F64 mLastSampleValue; + + + impl(bool useFrameTimer); + + void reset(U64 when); + + void sum(F64 value); + void sum(F64 value, U64 when); + + F32 meanValue(TimeScale scale) const; + + U64 getCurrentUsecs() const; + // Get current microseconds based on timer type +}; + + +U64 LLStatAccum::impl::sScaleTimes[IMPL_NUM_SCALES] = +{ + USEC_PER_SEC * 1, // seconds + USEC_PER_SEC * 60, // minutes + USEC_PER_SEC * 60 * 2 // minutes +#if 0 + // enable these when more time scales are desired + USEC_PER_SEC * 60*60, // hours + USEC_PER_SEC * 24*60*60, // days + USEC_PER_SEC * 7*24*60*60, // weeks +#endif +}; + + +LLStatAccum::impl::impl(bool useFrameTimer) +{ + mUseFrameTimer = useFrameTimer; + mRunning = FALSE; + mLastSampleValid = FALSE; +} + +void LLStatAccum::impl::reset(U64 when) +{ + mRunning = TRUE; + mLastTime = when; + + for (int i = 0; i < IMPL_NUM_SCALES; ++i) + { + mBuckets[i].accum = 0.0; + mBuckets[i].endTime = when + sScaleTimes[i]; + mBuckets[i].lastValid = FALSE; + } +} + +void LLStatAccum::impl::sum(F64 value) +{ + sum(value, getCurrentUsecs()); +} + +void LLStatAccum::impl::sum(F64 value, U64 when) +{ + if (!mRunning) + { + reset(when); + return; + } + if (when < mLastTime) + { + llwarns << "LLStatAccum::sum clock has gone backwards from " + << mLastTime << " to " << when << ", resetting" << llendl; + + reset(when); + return; + } + + for (int i = 0; i < IMPL_NUM_SCALES; ++i) + { + Bucket& bucket = mBuckets[i]; + + if (when < bucket.endTime) + { + bucket.accum += value; + } + else + { + U64 timeScale = sScaleTimes[i]; + + U64 timeSpan = when - mLastTime; + // how long is this value for + U64 timeLeft = when - bucket.endTime; + // how much time is left after filling this bucket + + if (timeLeft < timeScale) + { + F64 valueLeft = value * timeLeft / timeSpan; + + bucket.lastValid = TRUE; + bucket.lastAccum = bucket.accum + (value - valueLeft); + bucket.accum = valueLeft; + bucket.endTime += timeScale; + } + else + { + U64 timeTail = timeLeft % timeScale; + + bucket.lastValid = TRUE; + bucket.lastAccum = value * timeScale / timeSpan; + bucket.accum = value * timeTail / timeSpan; + bucket.endTime += (timeLeft - timeTail) + timeScale; + } + } + } + + mLastTime = when; +} + + +F32 LLStatAccum::impl::meanValue(TimeScale scale) const +{ + if (!mRunning) + { + return 0.0; + } + if (scale < 0 || scale >= IMPL_NUM_SCALES) + { + llwarns << "llStatAccum::meanValue called for unsupported scale: " + << scale << llendl; + return 0.0; + } + + const Bucket& bucket = mBuckets[scale]; + + F64 value = bucket.accum; + U64 timeLeft = bucket.endTime - mLastTime; + U64 scaleTime = sScaleTimes[scale]; + + if (bucket.lastValid) + { + value += bucket.lastAccum * timeLeft / scaleTime; + } + else if (timeLeft < scaleTime) + { + value *= scaleTime / (scaleTime - timeLeft); + } + else + { + value = 0.0; + } + + return (F32)(value / scaleTime); +} + + +U64 LLStatAccum::impl::getCurrentUsecs() const +{ + if (mUseFrameTimer) + { + return LLFrameTimer::getTotalTime(); + } + else + { + return totalTime(); + } +} + + + + + +LLStatAccum::LLStatAccum(bool useFrameTimer) + : m(* new impl(useFrameTimer)) +{ +} + +LLStatAccum::~LLStatAccum() +{ + delete &m; +} + +F32 LLStatAccum::meanValue(TimeScale scale) const +{ + return m.meanValue(scale); +} + + + +LLStatMeasure::LLStatMeasure(bool use_frame_timer) + : LLStatAccum(use_frame_timer) +{ +} + +void LLStatMeasure::sample(F64 value) +{ + U64 when = m.getCurrentUsecs(); + + if (m.mLastSampleValid) + { + F64 avgValue = (value + m.mLastSampleValue) / 2.0; + F64 interval = (F64)(when - m.mLastTime); + + m.sum(avgValue * interval, when); + } + else + { + m.reset(when); + } + + m.mLastSampleValid = TRUE; + m.mLastSampleValue = value; +} + + +LLStatRate::LLStatRate(bool use_frame_timer) + : LLStatAccum(use_frame_timer) +{ +} + +void LLStatRate::count(U32 value) +{ + m.sum((F64)value * impl::sScaleTimes[SCALE_SECOND]); +} + + +LLStatTime::LLStatTime(bool use_frame_timer) + : LLStatAccum(use_frame_timer) +{ +} + +void LLStatTime::start() +{ + m.sum(0.0); +} + +void LLStatTime::stop() +{ + U64 endTime = m.getCurrentUsecs(); + m.sum((F64)(endTime - m.mLastTime), endTime); +} + + + +LLTimer LLStat::sTimer; +LLFrameTimer LLStat::sFrameTimer; + +LLStat::LLStat(const U32 num_bins, const BOOL use_frame_timer) +{ + llassert(num_bins > 0); + U32 i; + mUseFrameTimer = use_frame_timer; + mNumValues = 0; + mLastValue = 0.f; + mLastTime = 0.f; + mNumBins = num_bins; + mCurBin = (mNumBins-1); + mNextBin = 0; + mBins = new F32[mNumBins]; + mBeginTime = new F64[mNumBins]; + mTime = new F64[mNumBins]; + mDT = new F32[mNumBins]; + for (i = 0; i < mNumBins; i++) + { + mBins[i] = 0.f; + mBeginTime[i] = 0.0; + mTime[i] = 0.0; + mDT[i] = 0.f; + } +} + +LLStat::~LLStat() +{ + delete[] mBins; + delete[] mBeginTime; + delete[] mTime; + delete[] mDT; +} + +void LLStat::reset() +{ + U32 i; + + mNumValues = 0; + mLastValue = 0.f; + mCurBin = (mNumBins-1); + delete[] mBins; + delete[] mBeginTime; + delete[] mTime; + delete[] mDT; + mBins = new F32[mNumBins]; + mBeginTime = new F64[mNumBins]; + mTime = new F64[mNumBins]; + mDT = new F32[mNumBins]; + for (i = 0; i < mNumBins; i++) + { + mBins[i] = 0.f; + mBeginTime[i] = 0.0; + mTime[i] = 0.0; + mDT[i] = 0.f; + } +} + +void LLStat::setBeginTime(const F64 time) +{ + mBeginTime[mNextBin] = time; +} + +void LLStat::addValueTime(const F64 time, const F32 value) +{ + if (mNumValues < mNumBins) + { + mNumValues++; + } + + // Increment the bin counters. + mCurBin++; + if ((U32)mCurBin == mNumBins) + { + mCurBin = 0; + } + mNextBin++; + if ((U32)mNextBin == mNumBins) + { + mNextBin = 0; + } + + mBins[mCurBin] = value; + mTime[mCurBin] = time; + mDT[mCurBin] = (F32)(mTime[mCurBin] - mBeginTime[mCurBin]); + //this value is used to prime the min/max calls + mLastTime = mTime[mCurBin]; + mLastValue = value; + + // Set the begin time for the next stat segment. + mBeginTime[mNextBin] = mTime[mCurBin]; + mTime[mNextBin] = mTime[mCurBin]; + mDT[mNextBin] = 0.f; +} + +void LLStat::start() +{ + if (mUseFrameTimer) + { + mBeginTime[mNextBin] = sFrameTimer.getElapsedSeconds(); + } + else + { + mBeginTime[mNextBin] = sTimer.getElapsedTimeF64(); + } +} + +void LLStat::addValue(const F32 value) +{ + if (mNumValues < mNumBins) + { + mNumValues++; + } + + // Increment the bin counters. + mCurBin++; + if ((U32)mCurBin == mNumBins) + { + mCurBin = 0; + } + mNextBin++; + if ((U32)mNextBin == mNumBins) + { + mNextBin = 0; + } + + mBins[mCurBin] = value; + if (mUseFrameTimer) + { + mTime[mCurBin] = sFrameTimer.getElapsedSeconds(); + } + else + { + mTime[mCurBin] = sTimer.getElapsedTimeF64(); + } + mDT[mCurBin] = (F32)(mTime[mCurBin] - mBeginTime[mCurBin]); + + //this value is used to prime the min/max calls + mLastTime = mTime[mCurBin]; + mLastValue = value; + + // Set the begin time for the next stat segment. + mBeginTime[mNextBin] = mTime[mCurBin]; + mTime[mNextBin] = mTime[mCurBin]; + mDT[mNextBin] = 0.f; +} + + +F32 LLStat::getMax() const +{ + U32 i; + F32 current_max = mLastValue; + if (mNumBins == 0) + { + current_max = 0.f; + } + else + { + for (i = 0; (i < mNumBins) && (i < mNumValues); i++) + { + // Skip the bin we're currently filling. + if (i == (U32)mNextBin) + { + continue; + } + if (mBins[i] > current_max) + { + current_max = mBins[i]; + } + } + } + return current_max; +} + +F32 LLStat::getMean() const +{ + U32 i; + F32 current_mean = 0.f; + U32 samples = 0; + for (i = 0; (i < mNumBins) && (i < mNumValues); i++) + { + // Skip the bin we're currently filling. + if (i == (U32)mNextBin) + { + continue; + } + current_mean += mBins[i]; + samples++; + } + + // There will be a wrap error at 2^32. :) + if (samples != 0) + { + current_mean /= samples; + } + else + { + current_mean = 0.f; + } + return current_mean; +} + +F32 LLStat::getMin() const +{ + U32 i; + F32 current_min = mLastValue; + + if (mNumBins == 0) + { + current_min = 0.f; + } + else + { + for (i = 0; (i < mNumBins) && (i < mNumValues); i++) + { + // Skip the bin we're currently filling. + if (i == (U32)mNextBin) + { + continue; + } + if (mBins[i] < current_min) + { + current_min = mBins[i]; + } + } + } + return current_min; +} + +F32 LLStat::getSum() const +{ + U32 i; + F32 sum = 0.f; + for (i = 0; (i < mNumBins) && (i < mNumValues); i++) + { + // Skip the bin we're currently filling. + if (i == (U32)mNextBin) + { + continue; + } + sum += mBins[i]; + } + + return sum; +} + +F32 LLStat::getSumDuration() const +{ + U32 i; + F32 sum = 0.f; + for (i = 0; (i < mNumBins) && (i < mNumValues); i++) + { + // Skip the bin we're currently filling. + if (i == (U32)mNextBin) + { + continue; + } + sum += mDT[i]; + } + + return sum; +} + +F32 LLStat::getPrev(S32 age) const +{ + S32 bin; + bin = mCurBin - age; + + while (bin < 0) + { + bin += mNumBins; + } + + if (bin == mNextBin) + { + // Bogus for bin we're currently working on. + return 0.f; + } + return mBins[bin]; +} + +F32 LLStat::getPrevPerSec(S32 age) const +{ + S32 bin; + bin = mCurBin - age; + + while (bin < 0) + { + bin += mNumBins; + } + + if (bin == mNextBin) + { + // Bogus for bin we're currently working on. + return 0.f; + } + return mBins[bin] / mDT[bin]; +} + +F64 LLStat::getPrevBeginTime(S32 age) const +{ + S32 bin; + bin = mCurBin - age; + + while (bin < 0) + { + bin += mNumBins; + } + + if (bin == mNextBin) + { + // Bogus for bin we're currently working on. + return 0.f; + } + + return mBeginTime[bin]; +} + +F64 LLStat::getPrevTime(S32 age) const +{ + S32 bin; + bin = mCurBin - age; + + while (bin < 0) + { + bin += mNumBins; + } + + if (bin == mNextBin) + { + // Bogus for bin we're currently working on. + return 0.f; + } + + return mTime[bin]; +} + +F32 LLStat::getBin(S32 bin) const +{ + return mBins[bin]; +} + +F32 LLStat::getBinPerSec(S32 bin) const +{ + return mBins[bin] / mDT[bin]; +} + +F64 LLStat::getBinBeginTime(S32 bin) const +{ + return mBeginTime[bin]; +} + +F64 LLStat::getBinTime(S32 bin) const +{ + return mTime[bin]; +} + +F32 LLStat::getCurrent() const +{ + return mBins[mCurBin]; +} + +F32 LLStat::getCurrentPerSec() const +{ + return mBins[mCurBin] / mDT[mCurBin]; +} + +F64 LLStat::getCurrentBeginTime() const +{ + return mBeginTime[mCurBin]; +} + +F64 LLStat::getCurrentTime() const +{ + return mTime[mCurBin]; +} + +F32 LLStat::getCurrentDuration() const +{ + return mDT[mCurBin]; +} + +F32 LLStat::getMeanPerSec() const +{ + U32 i; + F32 value = 0.f; + F32 dt = 0.f; + + for (i = 0; (i < mNumBins) && (i < mNumValues); i++) + { + // Skip the bin we're currently filling. + if (i == (U32)mNextBin) + { + continue; + } + value += mBins[i]; + dt += mDT[i]; + } + + if (dt > 0.f) + { + return value/dt; + } + else + { + return 0.f; + } +} + +F32 LLStat::getMeanDuration() const +{ + F32 dur = 0.0f; + U32 count = 0; + for (U32 i=0; (i < mNumBins) && (i < mNumValues); i++) + { + if (i == (U32)mNextBin) + { + continue; + } + dur += mDT[i]; + count++; + } + + if (count > 0) + { + dur /= F32(count); + return dur; + } + else + { + return 0.f; + } +} + +F32 LLStat::getMaxPerSec() const +{ + U32 i; + F32 value; + + if (mNextBin != 0) + { + value = mBins[0]/mDT[0]; + } + else if (mNumValues > 0) + { + value = mBins[1]/mDT[1]; + } + else + { + value = 0.f; + } + + for (i = 0; (i < mNumBins) && (i < mNumValues); i++) + { + // Skip the bin we're currently filling. + if (i == (U32)mNextBin) + { + continue; + } + value = llmax(value, mBins[i]/mDT[i]); + } + return value; +} + +F32 LLStat::getMinPerSec() const +{ + U32 i; + F32 value; + + if (mNextBin != 0) + { + value = mBins[0]/mDT[0]; + } + else if (mNumValues > 0) + { + value = mBins[1]/mDT[1]; + } + else + { + value = 0.f; + } + + for (i = 0; (i < mNumBins) && (i < mNumValues); i++) + { + // Skip the bin we're currently filling. + if (i == (U32)mNextBin) + { + continue; + } + value = llmin(value, mBins[i]/mDT[i]); + } + return value; +} + +F32 LLStat::getMinDuration() const +{ + F32 dur = 0.0f; + for (U32 i=0; (i < mNumBins) && (i < mNumValues); i++) + { + dur = llmin(dur, mDT[i]); + } + return dur; +} + +U32 LLStat::getNumValues() const +{ + return mNumValues; +} + +S32 LLStat::getNumBins() const +{ + return mNumBins; +} + +S32 LLStat::getCurBin() const +{ + return mCurBin; +} + +S32 LLStat::getNextBin() const +{ + return mNextBin; +} + +F64 LLStat::getLastTime() const +{ + return mLastTime; +} diff --git a/indra/llcommon/llstat.h b/indra/llcommon/llstat.h new file mode 100644 index 0000000000..4c86557365 --- /dev/null +++ b/indra/llcommon/llstat.h @@ -0,0 +1,182 @@ +/** + * @file llstat.h + * @brief Runtime statistics accumulation. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSTAT_H +#define LL_LLSTAT_H + +#include + +#include "lltimer.h" +#include "llframetimer.h" + +// +// Accumulates statistics for an arbitrary length of time. +// Does this by maintaining a chain of accumulators, each one +// accumulation the results of the parent. Can scale to arbitrary +// amounts of time with very low memory cost. +// + +class LLStatAccum +{ +protected: + LLStatAccum(bool use_frame_timer); + virtual ~LLStatAccum(); + +public: + enum TimeScale { + SCALE_SECOND, + SCALE_MINUTE, + SCALE_TWO_MINUTE, + SCALE_HOUR, + SCALE_DAY, + SCALE_WEEK, + + NUM_SCALES + }; + + F32 meanValue(TimeScale scale) const; + // see the subclasses for the specific meaning of value + + F32 meanValueOverLastSecond() const { return meanValue(SCALE_SECOND); } + F32 meanValueOverLastMinute() const { return meanValue(SCALE_MINUTE); } + +protected: + class impl; + impl& m; +}; + +class LLStatMeasure : public LLStatAccum + // gathers statistics about things that are measured + // ex.: tempature, time dilation +{ +public: + LLStatMeasure(bool use_frame_timer = true); + + void sample(F64); + void sample(S32 v) { sample((F64)v); } + void sample(U32 v) { sample((F64)v); } + void sample(S64 v) { sample((F64)v); } + void sample(U64 v) { sample((F64)v); } +}; + + +class LLStatRate : public LLStatAccum + // gathers statistics about things that can be counted over time + // ex.: LSL instructions executed, messages sent, simulator frames completed + // renders it in terms of rate of thing per second +{ +public: + LLStatRate(bool use_frame_timer = true); + + void count(U32); + // used to note that n items have occured + + void mark() { count(1); } + // used for counting the rate thorugh a point in the code +}; + + +class LLTimeBlock; + +class LLStatTime : public LLStatAccum + // gathers statistics about time spent in a block of code + // measure average duration per second in the block +{ +public: + LLStatTime(bool use_frame_timer = false); + +private: + void start(); + void stop(); + friend class LLTimeBlock; +}; + +class LLTimeBlock +{ +public: + LLTimeBlock(LLStatTime& stat) : mStat(stat) { mStat.start(); } + ~LLTimeBlock() { mStat.stop(); } +private: + LLStatTime& mStat; +}; + + + + + +class LLStat +{ +public: + LLStat(const U32 num_bins = 32, BOOL use_frame_timer = FALSE); + ~LLStat(); + + void reset(); + + void start(); // Start the timer for the current "frame", otherwise uses the time tracked from + // the last addValue + void addValue(const F32 value = 1.f); // Adds the current value being tracked, and tracks the DT. + void addValue(const S32 value) { addValue((F32)value); } + void addValue(const U32 value) { addValue((F32)value); } + + void setBeginTime(const F64 time); + void addValueTime(const F64 time, const F32 value = 1.f); + + S32 getCurBin() const; + S32 getNextBin() const; + + F32 getCurrent() const; + F32 getCurrentPerSec() const; + F64 getCurrentBeginTime() const; + F64 getCurrentTime() const; + F32 getCurrentDuration() const; + + F32 getPrev(S32 age) const; // Age is how many "addValues" previously - zero is current + F32 getPrevPerSec(S32 age) const; // Age is how many "addValues" previously - zero is current + F64 getPrevBeginTime(S32 age) const; + F64 getPrevTime(S32 age) const; + + F32 getBin(S32 bin) const; + F32 getBinPerSec(S32 bin) const; + F64 getBinBeginTime(S32 bin) const; + F64 getBinTime(S32 bin) const; + + F32 getMax() const; + F32 getMaxPerSec() const; + + F32 getMean() const; + F32 getMeanPerSec() const; + F32 getMeanDuration() const; + + F32 getMin() const; + F32 getMinPerSec() const; + F32 getMinDuration() const; + + F32 getSum() const; + F32 getSumDuration() const; + + U32 getNumValues() const; + S32 getNumBins() const; + + F64 getLastTime() const; +private: + BOOL mUseFrameTimer; + U32 mNumValues; + U32 mNumBins; + F32 mLastValue; + F64 mLastTime; + F32 *mBins; + F64 *mBeginTime; + F64 *mTime; + F32 *mDT; + S32 mCurBin; + S32 mNextBin; + static LLTimer sTimer; + static LLFrameTimer sFrameTimer; +}; + +#endif // LL_STAT_ diff --git a/indra/llcommon/llstatenums.h b/indra/llcommon/llstatenums.h new file mode 100644 index 0000000000..07c6e0bf52 --- /dev/null +++ b/indra/llcommon/llstatenums.h @@ -0,0 +1,40 @@ +/** + * @file llstatenums.h + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSTATENUMS_H +#define LL_LLSTATENUMS_H + +enum +{ + LL_SIM_STAT_TIME_DILATION, + LL_SIM_STAT_FPS, + LL_SIM_STAT_PHYSFPS, + LL_SIM_STAT_AGENTUPS, + LL_SIM_STAT_FRAMEMS, + LL_SIM_STAT_NETMS, + LL_SIM_STAT_SIMOTHERMS, + LL_SIM_STAT_SIMPHYSICSMS, + LL_SIM_STAT_AGENTMS, + LL_SIM_STAT_IMAGESMS, + LL_SIM_STAT_SCRIPTMS, + LL_SIM_STAT_NUMTASKS, + LL_SIM_STAT_NUMTASKSACTIVE, + LL_SIM_STAT_NUMAGENTMAIN, + LL_SIM_STAT_NUMAGENTCHILD, + LL_SIM_STAT_NUMSCRIPTSACTIVE, + LL_SIM_STAT_LSLIPS, + LL_SIM_STAT_INPPS, + LL_SIM_STAT_OUTPPS, + LL_SIM_STAT_PENDING_DOWNLOADS, + LL_SIM_STAT_PENDING_UPLOADS, + LL_SIM_STAT_VIRTUAL_SIZE_KB, + LL_SIM_STAT_RESIDENT_SIZE_KB, + LL_SIM_STAT_PENDING_LOCAL_UPLOADS, + LL_SIM_STAT_TOTAL_UNACKED_BYTES +}; + +#endif diff --git a/indra/llcommon/llstl.h b/indra/llcommon/llstl.h new file mode 100644 index 0000000000..61d83f7259 --- /dev/null +++ b/indra/llcommon/llstl.h @@ -0,0 +1,452 @@ +/** + * @file llstl.h + * @brief helper object & functions for use with the stl. + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSTL_H +#define LL_LLSTL_H + +#include +#include +#include +#include +#include +#include + +#include +#include + +// Use to compare the first element only of a pair +// e.g. typedef std::set, compare_pair > some_pair_set_t; +template +struct compare_pair_first +{ + bool operator()(const std::pair& a, const std::pair& b) const + { + return a.first < b.first; + } +}; + +template +struct compare_pair_greater +{ + bool operator()(const std::pair& a, const std::pair& b) const + { + if (!(a.first < b.first)) + return true; + else if (!(b.first < a.first)) + return false; + else + return !(a.second < b.second); + } +}; + +// Use to compare the contents of two pointers (e.g. std::string*) +template +struct compare_pointer_contents +{ + typedef const T* Tptr; + bool operator()(const Tptr& a, const Tptr& b) const + { + return *a < *b; + } +}; + +// DeletePointer is a simple helper for deleting all pointers in a container. +// The general form is: +// +// std::for_each(cont.begin(), cont.end(), DeletePointer()); + +struct DeletePointer +{ + template void operator()(T* ptr) const + { + delete ptr; + } +}; +struct DeletePointerArray +{ + template void operator()(T* ptr) const + { + delete[] ptr; + } +}; + +// DeletePointer is a simple helper for deleting all pointers in a map. +// The general form is: +// +// std::for_each(somemap.begin(), somemap.end(), DeletePairedPointer()); + +struct DeletePairedPointer +{ + template void operator()(T &ptr) const + { + delete ptr.second; + } +}; +struct DeletePairedPointerArray +{ + template void operator()(T &ptr) const + { + delete[] ptr.second; + } +}; + + +// Alternate version of the above so that has a more cumbersome +// syntax, but it can be used with compositional functors. *FIX: The +// functor retuns a bool because msdev bombs during the composition if +// you return void. Once we upgrade to a newer compiler, the second +// unary_function template parameter can be set to void. +// +// Here's a snippit showing how you use this object: +// +// typedef std::map map_type; +// map_type widget_map; +// ... // add elements +// // delete them all +// for_each(widget_map.begin(), +// widget_map.end(), +// llcompose1(DeletePointerFunctor(), +// llselect2nd())); + +template +struct DeletePointerFunctor : public std::unary_function +{ + bool operator()(T* ptr) const + { + delete ptr; + return true; + } +}; + +// See notes about DeleteArray for why you should consider avoiding this. +template +struct DeleteArrayFunctor : public std::unary_function +{ + bool operator()(T* ptr) const + { + delete[] ptr; + return true; + } +}; + +// CopyNewPointer is a simple helper which accepts a pointer, and +// returns a new pointer built with the copy constructor. Example: +// +// transform(in.begin(), in.end(), out.end(), CopyNewPointer()); + +struct CopyNewPointer +{ + template T* operator()(const T* ptr) const + { + return new T(*ptr); + } +}; + +// Simple function to help with finding pointers in maps. +// For example: +// typedef map_t; +// std::map foo; +// foo[18] = "there"; +// foo[2] = "hello"; +// const char* bar = get_ptr_in_map(foo, 2); // bar -> "hello" +// const char* baz = get_ptr_in_map(foo, 3); // baz == NULL +template +inline T* get_ptr_in_map(const std::map& inmap, const K& key) +{ + // Typedef here avoids warnings because of new c++ naming rules. + typedef typename std::map::const_iterator map_iter; + map_iter iter = inmap.find(key); + if(iter == inmap.end()) + { + return NULL; + } + else + { + return iter->second; + } +}; + +// helper function which returns true if key is in inmap. +template +inline bool is_in_map(const std::map& inmap, const K& key) +{ + typedef typename std::map::const_iterator map_iter; + if(inmap.find(key) == inmap.end()) + { + return false; + } + else + { + return true; + } +} + +// Similar to get_ptr_in_map, but for any type with a valid T(0) constructor. +// To replace LLSkipMap getIfThere, use: +// get_if_there(map, key, 0) +// WARNING: Make sure default_value (generally 0) is not a valid map entry! +template +inline T get_if_there(const std::map& inmap, const K& key, T default_value) +{ + // Typedef here avoids warnings because of new c++ naming rules. + typedef typename std::map::const_iterator map_iter; + map_iter iter = inmap.find(key); + if(iter == inmap.end()) + { + return default_value; + } + else + { + return iter->second; + } +}; + +// Useful for replacing the removeObj() functionality of LLDynamicArray +// Example: +// for (std::vector::iterator iter = mList.begin(); iter != mList.end(); ) +// { +// if ((*iter)->isMarkedForRemoval()) +// iter = vector_replace_with_last(mList, iter); +// else +// ++iter; +// } +template +inline Iter vector_replace_with_last(std::vector& invec, Iter iter) +{ + typename std::vector::iterator last = invec.end(); --last; + if (iter == invec.end()) + { + return iter; + } + else if (iter == last) + { + invec.pop_back(); + return invec.end(); + } + else + { + *iter = *last; + invec.pop_back(); + return iter; + } +}; + +// Useful for replacing the removeObj() functionality of LLDynamicArray +// Example: +// vector_replace_with_last(mList, x); +template +inline bool vector_replace_with_last(std::vector& invec, const T& val) +{ + typename std::vector::iterator iter = std::find(invec.begin(), invec.end(), val); + if (iter != invec.end()) + { + typename std::vector::iterator last = invec.end(); --last; + *iter = *last; + invec.pop_back(); + return true; + } + return false; +} + +// Append N elements to the vector and return a pointer to the first new element. +template +inline T* vector_append(std::vector& invec, S32 N) +{ + U32 sz = invec.size(); + invec.resize(sz+N); + return &(invec[sz]); +} + +// call function f to n members starting at first. similar to std::for_each +template +Function ll_for_n(InputIter first, Size n, Function f) +{ + for ( ; n > 0; --n, ++first) + f(*first); + return f; +} + +// copy first to result n times, incrementing each as we go +template +OutputIter ll_copy_n(InputIter first, Size n, OutputIter result) +{ + for ( ; n > 0; --n, ++result, ++first) + *result = *first; + return result; +} + +// set *result = op(*f) for n elements of f +template +OutputIter ll_transform_n( + InputIter first, + Size n, + OutputIter result, + UnaryOp op) +{ + for ( ; n > 0; --n, ++result, ++first) + *result = op(*first); + return result; +} + + + +/* + * + * Copyright (c) 1994 + * Hewlett-Packard Company + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Hewlett-Packard Company makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * + * Copyright (c) 1996-1998 + * Silicon Graphics Computer Systems, Inc. + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Silicon Graphics makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + */ + + +// helper to deal with the fact that MSDev does not package +// select... with the stl. Look up usage on the sgi website. + +template +struct _LLSelect1st : public std::unary_function<_Pair, typename _Pair::first_type> { + const typename _Pair::first_type& operator()(const _Pair& __x) const { + return __x.first; + } +}; + +template +struct _LLSelect2nd : public std::unary_function<_Pair, typename _Pair::second_type> +{ + const typename _Pair::second_type& operator()(const _Pair& __x) const { + return __x.second; + } +}; + +template struct llselect1st : public _LLSelect1st<_Pair> {}; +template struct llselect2nd : public _LLSelect2nd<_Pair> {}; + +// helper to deal with the fact that MSDev does not package +// compose... with the stl. Look up usage on the sgi website. + +template +class ll_unary_compose : + public std::unary_function +{ +protected: + _Operation1 __op1; + _Operation2 __op2; +public: + ll_unary_compose(const _Operation1& __x, const _Operation2& __y) + : __op1(__x), __op2(__y) {} + typename _Operation1::result_type + operator()(const typename _Operation2::argument_type& __x) const { + return __op1(__op2(__x)); + } +}; + +template +inline ll_unary_compose<_Operation1,_Operation2> +llcompose1(const _Operation1& __op1, const _Operation2& __op2) +{ + return ll_unary_compose<_Operation1,_Operation2>(__op1, __op2); +} + +template +class ll_binary_compose + : public std::unary_function { +protected: + _Operation1 _M_op1; + _Operation2 _M_op2; + _Operation3 _M_op3; +public: + ll_binary_compose(const _Operation1& __x, const _Operation2& __y, + const _Operation3& __z) + : _M_op1(__x), _M_op2(__y), _M_op3(__z) { } + typename _Operation1::result_type + operator()(const typename _Operation2::argument_type& __x) const { + return _M_op1(_M_op2(__x), _M_op3(__x)); + } +}; + +template +inline ll_binary_compose<_Operation1, _Operation2, _Operation3> +llcompose2(const _Operation1& __op1, const _Operation2& __op2, + const _Operation3& __op3) +{ + return ll_binary_compose<_Operation1,_Operation2,_Operation3> + (__op1, __op2, __op3); +} + +// helpers to deal with the fact that MSDev does not package +// bind... with the stl. Again, this is from sgi. +template +class llbinder1st : + public std::unary_function { +protected: + _Operation op; + typename _Operation::first_argument_type value; +public: + llbinder1st(const _Operation& __x, + const typename _Operation::first_argument_type& __y) + : op(__x), value(__y) {} + typename _Operation::result_type + operator()(const typename _Operation::second_argument_type& __x) const { + return op(value, __x); + } +}; + +template +inline llbinder1st<_Operation> +llbind1st(const _Operation& __oper, const _Tp& __x) +{ + typedef typename _Operation::first_argument_type _Arg1_type; + return llbinder1st<_Operation>(__oper, _Arg1_type(__x)); +} + +template +class llbinder2nd + : public std::unary_function { +protected: + _Operation op; + typename _Operation::second_argument_type value; +public: + llbinder2nd(const _Operation& __x, + const typename _Operation::second_argument_type& __y) + : op(__x), value(__y) {} + typename _Operation::result_type + operator()(const typename _Operation::first_argument_type& __x) const { + return op(__x, value); + } +}; + +template +inline llbinder2nd<_Operation> +llbind2nd(const _Operation& __oper, const _Tp& __x) +{ + typedef typename _Operation::second_argument_type _Arg2_type; + return llbinder2nd<_Operation>(__oper, _Arg2_type(__x)); +} + +#endif // LL_LLSTL_H diff --git a/indra/llcommon/llstreamtools.cpp b/indra/llcommon/llstreamtools.cpp new file mode 100644 index 0000000000..f9038afedc --- /dev/null +++ b/indra/llcommon/llstreamtools.cpp @@ -0,0 +1,551 @@ +/** + * @file llstreamtools.cpp + * @brief some helper functions for parsing legacy simstate and asset files. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include +#include + +#include "llstreamtools.h" + + +// ---------------------------------------------------------------------------- +// some std::istream helper functions +// ---------------------------------------------------------------------------- + +// skips spaces and tabs +bool skip_whitespace(std::istream& input_stream) +{ + char c = input_stream.peek(); + while (('\t' == c || ' ' == c) && input_stream.good()) + { + input_stream.get(); + c = input_stream.peek(); + } + return input_stream.good(); +} + +// skips whitespace, newlines, and carriage returns +bool skip_emptyspace(std::istream& input_stream) +{ + char c = input_stream.peek(); + while ( input_stream.good() + && ('\t' == c || ' ' == c || '\n' == c || '\r' == c) ) + { + input_stream.get(); + c = input_stream.peek(); + } + return input_stream.good(); +} + +// skips emptyspace and lines that start with a # +bool skip_comments_and_emptyspace(std::istream& input_stream) +{ + while (skip_emptyspace(input_stream)) + { + char c = input_stream.peek(); + if ('#' == c ) + { + while ('\n' != c && input_stream.good()) + { + c = input_stream.get(); + } + } + else + { + break; + } + } + return input_stream.good(); +} + +bool skip_line(std::istream& input_stream) +{ + char c; + do + { + c = input_stream.get(); + } while ('\n' != c && input_stream.good()); + return input_stream.good(); +} + +bool skip_to_next_word(std::istream& input_stream) +{ + char c = input_stream.peek(); + while ( input_stream.good() + && ( (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || '_' == c ) ) + { + input_stream.get(); + c = input_stream.peek(); + } + while ( input_stream.good() + && !( (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || '_' == c ) ) + { + input_stream.get(); + c = input_stream.peek(); + } + return input_stream.good(); +} + +bool skip_to_end_of_next_keyword(const char* keyword, std::istream& input_stream) +{ + int key_length = strlen(keyword); /*Flawfinder: ignore*/ + if (0 == key_length) + { + return false; + } + while (input_stream.good()) + { + skip_emptyspace(input_stream); + char c = input_stream.get(); + if (keyword[0] != c) + { + skip_line(input_stream); + } + else + { + int key_index = 1; + while ( key_index < key_length + && keyword[key_index - 1] == c + && input_stream.good()) + { + key_index++; + c = input_stream.get(); + } + + if (key_index == key_length + && keyword[key_index-1] == c) + { + c = input_stream.peek(); + if (' ' == c || '\t' == c || '\r' == c || '\n' == c) + { + return true; + } + else + { + skip_line(input_stream); + } + } + else + { + skip_line(input_stream); + } + } + } + return false; +} + +/* skip_to_start_of_next_keyword() is disabled -- might tickle corruption bug in windows iostream +bool skip_to_start_of_next_keyword(const char* keyword, std::istream& input_stream) +{ + int key_length = strlen(keyword); + if (0 == key_length) + { + return false; + } + while (input_stream.good()) + { + skip_emptyspace(input_stream); + char c = input_stream.get(); + if (keyword[0] != c) + { + skip_line(input_stream); + } + else + { + int key_index = 1; + while ( key_index < key_length + && keyword[key_index - 1] == c + && input_stream.good()) + { + key_index++; + c = input_stream.get(); + } + + if (key_index == key_length + && keyword[key_index-1] == c) + { + c = input_stream.peek(); + if (' ' == c || '\t' == c || '\r' == c || '\n' == c) + { + // put the keyword back onto the stream + for (int index = key_length - 1; index >= 0; index--) + { + input_stream.putback(keyword[index]); + } + return true; + } + else + { + skip_line(input_stream); + break; + } + } + else + { + skip_line(input_stream); + } + } + } + return false; +} +*/ + +bool get_word(std::string& output_string, std::istream& input_stream) +{ + skip_emptyspace(input_stream); + char c = input_stream.peek(); + while ( !isspace(c) + && '\n' != c + && '\r' != c + && input_stream.good() ) + { + output_string += c; + input_stream.get(); + c = input_stream.peek(); + } + return input_stream.good(); +} + +bool get_word(std::string& output_string, std::istream& input_stream, int n) +{ + skip_emptyspace(input_stream); + int char_count = 0; + char c = input_stream.peek(); + while (!isspace(c) + && '\n' != c + && '\r' != c + && input_stream.good() + && char_count < n) + { + char_count++; + output_string += c; + input_stream.get(); + c = input_stream.peek(); + } + return input_stream.good(); +} + +// get everything up to and including the next newline +bool get_line(std::string& output_string, std::istream& input_stream) +{ + char c = input_stream.get(); + while (input_stream.good()) + { + if ('\r' == c) + { + // skip carriage returns + } + else + { + output_string += c; + if ('\n' == c) + { + break; + } + } + c = input_stream.get(); + } + return input_stream.good(); +} + +// get everything up to and including the next newline +// up to the next n characters. +// add a newline on the end if bail before actual line ending +bool get_line(std::string& output_string, std::istream& input_stream, int n) +{ + int char_count = 0; + char c = input_stream.get(); + while (input_stream.good() && char_count < n) + { + char_count++; + output_string += c; + if ('\r' == c) + { + // skip carriage returns + } + else + { + if ('\n' == c) + { + break; + } + if (char_count >= n) + { + output_string.append("\n"); + break; + } + } + c = input_stream.get(); + } + return input_stream.good(); +} + +/* disabled -- might tickle bug in windows iostream +// backs up the input_stream by line_size + 1 characters +bool unget_line(const std::string& line, std::istream& input_stream) +{ + input_stream.putback('\n'); // unget the newline + for (int line_index = line.size()-1; line_index >= 0; line_index--) + { + input_stream.putback(line[line_index]); + } + return input_stream.good(); +} +*/ + +// removes the last char in 'line' if it matches 'c' +// returns true if removed last char +bool remove_last_char(char c, std::string& line) +{ + int line_size = line.size(); + if (line_size > 1 + && c == line[line_size - 1]) + { + line.replace(line_size - 1, 1, ""); + return true; + } + return false; +} + +// replaces escaped characters with the correct characters from left to right +// "\\\\" ---> '\\' (two backslahes become one) +// "\\n" ---> '\n' (backslash n becomes carriage return) +void unescape_string(std::string& line) +{ + int line_size = line.size(); + int index = 0; + while (index < line_size - 1) + { + if ('\\' == line[index]) + { + if ('\\' == line[index + 1]) + { + line.replace(index, 2, "\\"); + line_size--; + } + else if ('n' == line[index + 1]) + { + line.replace(index, 2, "\n"); + line_size--; + } + } + index++; + } +} + +// replaces unescaped characters with expanded equivalents from left to right +// '\\' ---> "\\\\" (one backslash becomes two) +// '\n' ---> "\\n" (carriage return becomes backslash n) +void escape_string(std::string& line) +{ + int line_size = line.size(); + int index = 0; + while (index < line_size) + { + if ('\\' == line[index]) + { + line.replace(index, 1, "\\\\"); + line_size++; + index++; + } + else if ('\n' == line[index]) + { + line.replace(index, 1, "\\n"); + line_size++; + index++; + } + index++; + } +} + +// removes '\n' characters +void replace_newlines_with_whitespace(std::string& line) +{ + int line_size = line.size(); + int index = 0; + while (index < line_size) + { + if ('\n' == line[index]) + { + line.replace(index, 1, " "); + } + index++; + } +} + +// returns 1 for solitary "{" +// returns -1 for solitary "}" +// otherwise returns 0 +int get_brace_count(const std::string& line) +{ + int index = 0; + int line_size = line.size(); + char c = 0; + while (index < line_size) + { + c = line[index]; + index++; + if (!isspace(c)) + { + break; + } + } + char brace = c; + // make sure the rest of the line is whitespace + while (index < line_size) + { + c = line[index]; + if (!isspace(c)) + { + break; + } + index++; + } + if ('\n' != c) + { + return 0; + } + if ('{' == brace) + { + return 1; + } + else if ('}' == brace) + { + return -1; + } + return 0; +} + +// erases any double-quote characters in 'line' +void remove_double_quotes(std::string& line) +{ + int index = 0; + int line_size = line.size(); + while (index < line_size) + { + if ('"' == line[index]) + { + int count = 1; + while (index + count < line_size + && '"' == line[index + count]) + { + count++; + } + line.replace(index, count, ""); + line_size -= count; + } + else + { + index++; + } + } +} + +// the 'keyword' is defined as the first word on a line +// the 'value' is everything after the keyword on the same line +// starting at the first non-whitespace and ending right before the newline +void get_keyword_and_value(std::string& keyword, + std::string& value, + const std::string& line) +{ + // skip initial whitespace + int line_size = line.size(); + int line_index = 0; + char c; + while (line_index < line_size) + { + c = line[line_index]; + if (!isspace(c)) + { + break; + } + line_index++; + } + + // get the keyword + keyword.assign(""); + while (line_index < line_size) + { + c = line[line_index]; + if (isspace(c) || '\r' == c || '\n' == c) + { + break; + } + keyword += c; + line_index++; + } + + if (keyword.size() > 0 + && '\r' != line[line_index] + && '\n' != line[line_index]) + + { + // discard initial white spaces + while (line_index < line_size + && (' ' == line[line_index] + || '\t' == line[line_index]) ) + { + line_index++; + } + + // get the value + value.assign(""); + while (line_index < line_size) + { + c = line[line_index]; + if ('\r' == c || '\n' == c) + { + break; + } + value += c; + line_index++; + } + } +} + +std::istream& fullread(std::istream& str, char *buf, std::streamsize requested) +{ + std::streamsize got; + std::streamsize total = 0; + + str.read(buf, requested); /*Flawfinder: ignore*/ + got = str.gcount(); + total += got; + while (got && total < requested) + { + if (str.fail()) + str.clear(); + str.read(buf + total, requested - total); /*Flawfinder: ignore*/ + got = str.gcount(); + total += got; + } + return str; +} + +std::istream& operator>>(std::istream& str, const char *tocheck) +{ + char c; + const char *p; + p = tocheck; + while (*p && !str.bad()) + { + str.get(c); + if (c != *p) + { + str.setstate(std::ios::failbit); /*Flawfinder: ignore*/ + break; + } + p++; + } + return str; +} diff --git a/indra/llcommon/llstreamtools.h b/indra/llcommon/llstreamtools.h new file mode 100644 index 0000000000..e4099aac57 --- /dev/null +++ b/indra/llcommon/llstreamtools.h @@ -0,0 +1,98 @@ +/** + * @file llstreamtools.h + * @brief some helper functions for parsing legacy simstate and asset files. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_STREAM_TOOLS_H +#define LL_STREAM_TOOLS_H + +#include +#include + +// unless specifed otherwise these all return input_stream.good() + +// skips spaces and tabs +bool skip_whitespace(std::istream& input_stream); + +// skips whitespace and newlines +bool skip_emptyspace(std::istream& input_stream); + +// skips emptyspace and lines that start with a # +bool skip_comments_and_emptyspace(std::istream& input_stream); + +// skips to character after next newline +bool skip_line(std::istream& input_stream); + +// skips to beginning of next non-emptyspace +bool skip_to_next_word(std::istream& input_stream); + +// skips to character after the end of next keyword +// a 'keyword' is defined as the first word on a line +bool skip_to_end_of_next_keyword(const char* keyword, std::istream& input_stream); + +// skip_to_start_of_next_keyword() is disabled -- might tickle corruption bug +// in windows iostream +// skips to beginning of next keyword +// a 'keyword' is defined as the first word on a line +//bool skip_to_start_of_next_keyword(const char* keyword, std::istream& input_stream); + +// characters are pulled out of input_stream and appended to output_string +bool get_word(std::string& output_string, std::istream& input_stream); +bool get_line(std::string& output_string, std::istream& input_stream); + +// characters are pulled out of input_stream (up to a max of 'n') +// and appended to output_string +bool get_word(std::string& output_string, std::istream& input_stream, int n); +bool get_line(std::string& output_string, std::istream& input_stream, int n); + +// unget_line() is disabled -- might tickle corruption bug in windows iostream +//// backs up the input_stream by line_size + 1 characters +//bool unget_line(const std::string& line, std::istream& input_stream); + +// TODO -- move these string manipulator functions to a different file + +// removes the last char in 'line' if it matches 'c' +// returns true if removed last char +bool remove_last_char(char c, std::string& line); + +// replaces escaped characters with the correct characters from left to right +// "\\" ---> '\\' +// "\n" ---> '\n' +void unescape_string(std::string& line); + +// replaces unescaped characters with expanded equivalents from left to right +// '\\' ---> "\\" +// '\n' ---> "\n" +void escape_string(std::string& line); + +// replaces each '\n' character with ' ' +void replace_newlines_with_whitespace(std::string& line); + +// returns 1 for solitary "{" +// returns -1 for solitary "}" +// otherwise returns 0 +int get_brace_count(const std::string& line); + +// erases any double-quote characters in line +void remove_double_quotes(std::string& line); + +// the 'keyword' is defined as the first word on a line +// the 'value' is everything after the keyword on the same line +// starting at the first non-whitespace and ending right before the newline +void get_keyword_and_value(std::string& keyword, + std::string& value, + const std::string& line); + +// continue to read from the stream until you really can't +// read anymore or until we hit the count. Some istream +// implimentations have a max that they will read. +std::istream& fullread(std::istream& str, char *buf, std::streamsize requested); + +std::istream& operator>>(std::istream& str, const char *tocheck); + +#endif + + diff --git a/indra/llcommon/llstrider.h b/indra/llcommon/llstrider.h new file mode 100644 index 0000000000..0688e43940 --- /dev/null +++ b/indra/llcommon/llstrider.h @@ -0,0 +1,38 @@ +/** + * @file llstrider.h + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSTRIDER_H +#define LL_LLSTRIDER_H + +#include "stdtypes.h" + +template class LLStrider +{ + union + { + Object* mObjectp; + U8* mBytep; + }; + U32 mSkip; +public: + + LLStrider() { mObjectp = NULL; mSkip = sizeof(Object); } + ~LLStrider() { } + + const LLStrider& operator = (Object *first) { mObjectp = first; return *this;} + void setStride (S32 skipBytes) { mSkip = (skipBytes ? skipBytes : sizeof(Object));} + + void skip(const U32 index) { mBytep += mSkip*index;} + + Object* get() { return mObjectp; } + Object* operator->() { return mObjectp; } + Object& operator *() { return *mObjectp; } + Object* operator ++(int) { Object* old = mObjectp; mBytep += mSkip; return old; } + Object& operator[](U32 index) { return *(Object*)(mBytep + (mSkip * index)); } +}; + +#endif // LL_LLSTRIDER_H diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp new file mode 100644 index 0000000000..50fd881ad7 --- /dev/null +++ b/indra/llcommon/llstring.cpp @@ -0,0 +1,835 @@ +/** + * @file llstring.cpp + * @brief String utility functions and the LLString class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llstring.h" +#include "llerror.h" + +std::string ll_safe_string(const char* in) +{ + if(in) return std::string(in); + return std::string(); +} + +U8 hex_as_nybble(char hex) +{ + if((hex >= '0') && (hex <= '9')) + { + return (U8)(hex - '0'); + } + else if((hex >= 'a') && (hex <='f')) + { + return (U8)(10 + hex - 'a'); + } + else if((hex >= 'A') && (hex <='F')) + { + return (U8)(10 + hex - 'A'); + } + return 0; // uh - oh, not hex any more... +} + + +// See http://www.unicode.org/Public/BETA/CVTUTF-1-2/ConvertUTF.c +// for the Unicode implementation - this doesn't match because it was written before finding +// it. + + +std::ostream& operator<<(std::ostream &s, const LLWString &wstr) +{ + std::string utf8_str = wstring_to_utf8str(wstr); + s << utf8_str; + return s; +} + +std::string rawstr_to_utf8(const std::string& raw) +{ + LLWString wstr(utf8str_to_wstring(raw)); + return wstring_to_utf8str(wstr); +} + +S32 wchar_to_utf8chars(llwchar in_char, char* outchars) +{ + U32 cur_char = (U32)in_char; + char* base = outchars; + if (cur_char < 0x80) + { + *outchars++ = (U8)cur_char; + } + else if (cur_char < 0x800) + { + *outchars++ = 0xC0 | (cur_char >> 6); + *outchars++ = 0x80 | (cur_char & 0x3F); + } + else if (cur_char < 0x10000) + { + *outchars++ = 0xE0 | (cur_char >> 12); + *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); + *outchars++ = 0x80 | (cur_char & 0x3F); + } + else if (cur_char < 0x200000) + { + *outchars++ = 0xF0 | (cur_char >> 18); + *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); + *outchars++ = 0x80 | cur_char & 0x3F; + } + else if (cur_char < 0x4000000) + { + *outchars++ = 0xF8 | (cur_char >> 24); + *outchars++ = 0x80 | ((cur_char >> 18) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); + *outchars++ = 0x80 | cur_char & 0x3F; + } + else if (cur_char < 0x80000000) + { + *outchars++ = 0xFC | (cur_char >> 30); + *outchars++ = 0x80 | ((cur_char >> 24) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 18) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); + *outchars++ = 0x80 | cur_char & 0x3F; + } + else + { + llwarns << "Invalid Unicode character " << cur_char << "!" << llendl; + *outchars++ = LL_UNKNOWN_CHAR; + } + return outchars - base; +} + +S32 utf16chars_to_wchar(const U16* inchars, llwchar* outchar) +{ + const U16* base = inchars; + U16 cur_char = *inchars++; + llwchar char32 = cur_char; + if ((cur_char >= 0xD800) && (cur_char <= 0xDFFF)) + { + // Surrogates + char32 = ((llwchar)(cur_char - 0xD800)) << 10; + cur_char = *inchars++; + char32 += (llwchar)(cur_char - 0xDC00) + 0x0010000UL; + } + else + { + char32 = (llwchar)cur_char; + } + *outchar = char32; + return inchars - base; +} + +S32 utf16chars_to_utf8chars(const U16* inchars, char* outchars, S32* nchars8p) +{ + // Get 32 bit char32 + llwchar char32; + S32 nchars16 = utf16chars_to_wchar(inchars, &char32); + // Convert to utf8 + S32 nchars8 = wchar_to_utf8chars(char32, outchars); + if (nchars8p) + { + *nchars8p = nchars8; + } + return nchars16; +} + +llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len) +{ + llutf16string out; + + S32 i = 0; + while (i < len) + { + U32 cur_char = utf32str[i]; + if (cur_char > 0xFFFF) + { + out += (0xD7C0 + (cur_char >> 10)); + out += (0xDC00 | (cur_char & 0x3FF)); + } + else + { + out += cur_char; + } + i++; + } + return out; +} + +llutf16string wstring_to_utf16str(const LLWString &utf32str) +{ + const S32 len = (S32)utf32str.length(); + return wstring_to_utf16str(utf32str, len); +} + +llutf16string utf8str_to_utf16str ( const LLString& utf8str ) +{ + LLWString wstr = utf8str_to_wstring ( utf8str ); + return wstring_to_utf16str ( wstr ); +} + + +LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len) +{ + LLWString wout; + + S32 i = 0; + // craziness to make gcc happy (llutf16string.c_str() is tweaked on linux): + const U16* chars16 = &(*(utf16str.begin())); + while (i < len) + { + llwchar cur_char; + i += utf16chars_to_wchar(chars16+i, &cur_char); + wout += cur_char; + } + return wout; +} + +LLWString utf16str_to_wstring(const llutf16string &utf16str) +{ + const S32 len = (S32)utf16str.length(); + return utf16str_to_wstring(utf16str, len); +} + +S32 wchar_utf8_length(const llwchar wc) +{ + if (wc < 0x80) + { + // This case will also catch negative values which are + // technically invalid. + return 1; + } + else if (wc < 0x800) + { + return 2; + } + else if (wc < 0x10000) + { + return 3; + } + else if (wc < 0x200000) + { + return 4; + } + else if (wc < 0x4000000) + { + return 5; + } + else + { + return 6; + } +} + + +S32 wstring_utf8_length(const LLWString& wstr) +{ + S32 len = 0; + for (S32 i = 0; i < (S32)wstr.length(); i++) + { + len += wchar_utf8_length(wstr[i]); + } + return len; +} + + +LLWString utf8str_to_wstring(const std::string& utf8str, S32 len) +{ + LLWString wout; + + S32 i = 0; + while (i < len) + { + llwchar unichar; + U8 cur_char = utf8str[i]; + + if (cur_char < 0x80) + { + // Ascii character, just add it + unichar = cur_char; + } + else + { + S32 cont_bytes = 0; + if ((cur_char >> 5) == 0x6) // Two byte UTF8 -> 1 UTF32 + { + unichar = (0x1F&cur_char); + cont_bytes = 1; + } + else if ((cur_char >> 4) == 0xe) // Three byte UTF8 -> 1 UTF32 + { + unichar = (0x0F&cur_char); + cont_bytes = 2; + } + else if ((cur_char >> 3) == 0x1e) // Four byte UTF8 -> 1 UTF32 + { + unichar = (0x07&cur_char); + cont_bytes = 3; + } + else if ((cur_char >> 2) == 0x3e) // Five byte UTF8 -> 1 UTF32 + { + unichar = (0x03&cur_char); + cont_bytes = 4; + } + else if ((cur_char >> 1) == 0x7e) // Six byte UTF8 -> 1 UTF32 + { + unichar = (0x01&cur_char); + cont_bytes = 5; + } + else + { + wout += LL_UNKNOWN_CHAR; + ++i; + continue; + } + + // Check that this character doesn't go past the end of the string + S32 end = (len < (i + cont_bytes)) ? len : (i + cont_bytes); + do + { + ++i; + + cur_char = utf8str[i]; + if ( (cur_char >> 6) == 0x2 ) + { + unichar <<= 6; + unichar += (0x3F&cur_char); + } + else + { + // Malformed sequence - roll back to look at this as a new char + unichar = LL_UNKNOWN_CHAR; + --i; + break; + } + } while(i < end); + + // Handle overlong characters and NULL characters + if ( ((cont_bytes == 1) && (unichar < 0x80)) + || ((cont_bytes == 2) && (unichar < 0x800)) + || ((cont_bytes == 3) && (unichar < 0x10000)) + || ((cont_bytes == 4) && (unichar < 0x200000)) + || ((cont_bytes == 5) && (unichar < 0x4000000)) ) + { + unichar = LL_UNKNOWN_CHAR; + } + } + + wout += unichar; + ++i; + } + return wout; +} + +LLWString utf8str_to_wstring(const std::string& utf8str) +{ + const S32 len = (S32)utf8str.length(); + return utf8str_to_wstring(utf8str, len); +} + +std::string wstring_to_utf8str(const LLWString& utf32str, S32 len) +{ + std::string out; + + S32 i = 0; + while (i < len) + { + char tchars[8]; /* Flawfinder: ignore */ + S32 n = wchar_to_utf8chars(utf32str[i], tchars); + tchars[n] = 0; + out += tchars; + i++; + } + return out; +} + +std::string wstring_to_utf8str(const LLWString& utf32str) +{ + const S32 len = (S32)utf32str.length(); + return wstring_to_utf8str(utf32str, len); +} + +std::string utf16str_to_utf8str(const llutf16string& utf16str) +{ + return wstring_to_utf8str(utf16str_to_wstring(utf16str)); +} + +std::string utf16str_to_utf8str(const llutf16string& utf16str, S32 len) +{ + return wstring_to_utf8str(utf16str_to_wstring(utf16str, len), len); +} + + +//LLWString wstring_truncate(const LLWString &wstr, const S32 max_len) +//{ +// return wstr.substr(0, llmin((S32)wstr.length(), max_len)); +//} +// +// +//LLWString wstring_trim(const LLWString &wstr) +//{ +// LLWString outstr; +// outstr = wstring_trimhead(wstr); +// outstr = wstring_trimtail(outstr); +// return outstr; +//} +// +// +//LLWString wstring_trimhead(const LLWString &wstr) +//{ +// if(wstr.empty()) +// { +// return wstr; +// } +// +// S32 i = 0; +// while((i < (S32)wstr.length()) && iswspace(wstr[i])) +// { +// i++; +// } +// return wstr.substr(i, wstr.length() - i); +//} +// +// +//LLWString wstring_trimtail(const LLWString &wstr) +//{ +// if(wstr.empty()) +// { +// return wstr; +// } +// +// S32 len = (S32)wstr.length(); +// +// S32 i = len - 1; +// while (i >= 0 && iswspace(wstr[i])) +// { +// i--; +// } +// +// if (i >= 0) +// { +// return wstr.substr(0, i + 1); +// } +// return wstr; +//} +// +// +//LLWString wstring_copyinto(const LLWString &dest, const LLWString &src, const S32 insert_offset) +//{ +// llassert( insert_offset <= (S32)dest.length() ); +// +// LLWString out_str = dest.substr(0, insert_offset); +// out_str += src; +// LLWString tail = dest.substr(insert_offset); +// out_str += tail; +// +// return out_str; +//} + + +//LLWString wstring_detabify(const LLWString &wstr, const S32 num_spaces) +//{ +// LLWString out_str; +// // Replace tabs with spaces +// for (S32 i = 0; i < (S32)wstr.length(); i++) +// { +// if (wstr[i] == '\t') +// { +// for (S32 j = 0; j < num_spaces; j++) +// out_str += ' '; +// } +// else +// { +// out_str += wstr[i]; +// } +// } +// return out_str; +//} + + +//LLWString wstring_makeASCII(const LLWString &wstr) +//{ +// // Replace non-ASCII chars with replace_char +// LLWString out_str = wstr; +// for (S32 i = 0; i < (S32)out_str.length(); i++) +// { +// if (out_str[i] > 0x7f) +// { +// out_str[i] = LL_UNKNOWN_CHAR; +// } +// } +// return out_str; +//} + + +//LLWString wstring_substChar(const LLWString &wstr, const llwchar target_char, const llwchar replace_char) +//{ +// // Replace all occurences of target_char with replace_char +// LLWString out_str = wstr; +// for (S32 i = 0; i < (S32)out_str.length(); i++) +// { +// if (out_str[i] == target_char) +// { +// out_str[i] = replace_char; +// } +// } +// return out_str; +//} +// +// +//LLWString wstring_tolower(const LLWString &wstr) +//{ +// LLWString out_str = wstr; +// for (S32 i = 0; i < (S32)out_str.length(); i++) +// { +// out_str[i] = towlower(out_str[i]); +// } +// return out_str; +//} +// +// +//LLWString wstring_convert_to_lf(const LLWString &wstr) +//{ +// const llwchar CR = 13; +// // Remove carriage returns from string with CRLF +// LLWString out_str; +// +// for (S32 i = 0; i < (S32)wstr.length(); i++) +// { +// if (wstr[i] != CR) +// { +// out_str += wstr[i]; +// } +// } +// return out_str; +//} +// +// +//LLWString wstring_convert_to_crlf(const LLWString &wstr) +//{ +// const llwchar LF = 10; +// const llwchar CR = 13; +// // Remove carriage returns from string with CRLF +// LLWString out_str; +// +// for (S32 i = 0; i < (S32)wstr.length(); i++) +// { +// if (wstr[i] == LF) +// { +// out_str += CR; +// } +// out_str += wstr[i]; +// } +// return out_str; +//} + + +//S32 wstring_compare_insensitive(const LLWString &lhs, const LLWString &rhs) +//{ +// +// if (lhs == rhs) +// { +// return 0; +// } +// +// if (lhs.empty()) +// { +// return rhs.empty() ? 0 : 1; +// } +// +// if (rhs.empty()) +// { +// return -1; +// } +// +//#ifdef LL_LINUX +// // doesn't work because gcc 2.95 doesn't correctly implement c_str(). Sigh... +// llerrs << "wstring_compare_insensitive doesn't work on Linux!" << llendl; +// return 0; +//#else +// LLWString lhs_lower = lhs; +// LLWString::toLower(lhs_lower); +// std::string lhs_lower = wstring_to_utf8str(lhs_lower); +// LLWString rhs_lower = lhs; +// LLWString::toLower(rhs_lower); +// std::string rhs_lower = wstring_to_utf8str(rhs_lower); +// +// return strcmp(lhs_lower.c_str(), rhs_lower.c_str()); +//#endif +//} + + +std::string utf8str_trim(const std::string& utf8str) +{ + LLWString wstr = utf8str_to_wstring(utf8str); + LLWString::trim(wstr); + return wstring_to_utf8str(wstr); +} + + +std::string utf8str_tolower(const std::string& utf8str) +{ + LLWString out_str = utf8str_to_wstring(utf8str); + LLWString::toLower(out_str); + return wstring_to_utf8str(out_str); +} + + +S32 utf8str_compare_insensitive(const std::string& lhs, const std::string& rhs) +{ + LLWString wlhs = utf8str_to_wstring(lhs); + LLWString wrhs = utf8str_to_wstring(rhs); + return LLWString::compareInsensitive(wlhs.c_str(), wrhs.c_str()); +} + +std::string utf8str_truncate(const std::string& utf8str, const S32 max_len) +{ + if (0 == max_len) + { + return std::string(); + } + if ((S32)utf8str.length() <= max_len) + { + return utf8str; + } + else + { + S32 cur_char = max_len; + + // If we're ASCII, we don't need to do anything + if ((U8)utf8str[cur_char] > 0x7f) + { + // If first two bits are (10), it's the tail end of a multibyte char. We need to shift back + // to the first character + while (0x80 == (0xc0 & utf8str[cur_char])) + { + cur_char--; + // Keep moving forward until we hit the first char; + if (cur_char == 0) + { + // Make sure we don't trash memory if we've got a bogus string. + break; + } + } + } + // The byte index we're on is one we want to get rid of, so we only want to copy up to (cur_char-1) chars + return utf8str.substr(0, cur_char); + } +} + +std::string utf8str_substChar( + const std::string& utf8str, + const llwchar target_char, + const llwchar replace_char) +{ + LLWString wstr = utf8str_to_wstring(utf8str); + LLWString::replaceChar(wstr, target_char, replace_char); + //wstr = wstring_substChar(wstr, target_char, replace_char); + return wstring_to_utf8str(wstr); +} + +std::string utf8str_makeASCII(const std::string& utf8str) +{ + LLWString wstr = utf8str_to_wstring(utf8str); + LLWString::_makeASCII(wstr); + return wstring_to_utf8str(wstr); +} + +std::string mbcsstring_makeASCII(const std::string& wstr) +{ + // Replace non-ASCII chars with replace_char + std::string out_str = wstr; + for (S32 i = 0; i < (S32)out_str.length(); i++) + { + if ((U8)out_str[i] > 0x7f) + { + out_str[i] = LL_UNKNOWN_CHAR; + } + } + return out_str; +} + +S32 LLStringOps::collate(const llwchar* a, const llwchar* b) +{ + #if LL_WINDOWS + // in Windows, wide string functions operator on 16-bit strings, + // not the proper 32 bit wide string + return strcmp(wstring_to_utf8str(LLWString(a)).c_str(), wstring_to_utf8str(LLWString(b)).c_str()); + #else + return wcscoll(a, b); + #endif +} + +namespace LLStringFn +{ + void replace_nonprintable(std::basic_string& string, char replacement) + { + const char MIN = 0x20; + std::basic_string::size_type len = string.size(); + for(std::basic_string::size_type ii = 0; ii < len; ++ii) + { + if(string[ii] < MIN) + { + string[ii] = replacement; + } + } + } + + void replace_nonprintable( + std::basic_string& string, + llwchar replacement) + { + const llwchar MIN = 0x20; + const llwchar MAX = 0x7f; + std::basic_string::size_type len = string.size(); + for(std::basic_string::size_type ii = 0; ii < len; ++ii) + { + if((string[ii] < MIN) || (string[ii] > MAX)) + { + string[ii] = replacement; + } + } + } + + void replace_nonprintable_and_pipe(std::basic_string& str, + char replacement) + { + const char MIN = 0x20; + const char PIPE = 0x7c; + std::basic_string::size_type len = str.size(); + for(std::basic_string::size_type ii = 0; ii < len; ++ii) + { + if( (str[ii] < MIN) || (str[ii] == PIPE) ) + { + str[ii] = replacement; + } + } + } + + void replace_nonprintable_and_pipe(std::basic_string& str, + llwchar replacement) + { + const llwchar MIN = 0x20; + const llwchar MAX = 0x7f; + const llwchar PIPE = 0x7c; + std::basic_string::size_type len = str.size(); + for(std::basic_string::size_type ii = 0; ii < len; ++ii) + { + if( (str[ii] < MIN) || (str[ii] > MAX) || (str[ii] == PIPE) ) + { + str[ii] = replacement; + } + } + } +} + + +//////////////////////////////////////////////////////////// +// Testing + +#ifdef _DEBUG + +template +void LLStringBase::testHarness() +{ + LLString s1; + + llassert( s1.c_str() == NULL ); + llassert( s1.size() == 0 ); + llassert( s1.empty() ); + + LLString s2( "hello"); + llassert( !strcmp( s2.c_str(), "hello" ) ); + llassert( s2.size() == 5 ); + llassert( !s2.empty() ); + LLString s3( s2 ); + + llassert( "hello" == s2 ); + llassert( s2 == "hello" ); + llassert( s2 > "gello" ); + llassert( "gello" < s2 ); + llassert( "gello" != s2 ); + llassert( s2 != "gello" ); + + LLString s4 = s2; + llassert( !s4.empty() ); + s4.empty(); + llassert( s4.empty() ); + + LLString s5(""); + llassert( s5.empty() ); + + llassert( isValidIndex(s5, 0) ); + llassert( !isValidIndex(s5, 1) ); + + s3 = s2; + s4 = "hello again"; + + s4 += "!"; + s4 += s4; + llassert( s4 == "hello again!hello again!" ); + + + LLString s6 = s2 + " " + s2; + LLString s7 = s6; + llassert( s6 == s7 ); + llassert( !( s6 != s7) ); + llassert( !(s6 < s7) ); + llassert( !(s6 > s7) ); + + llassert( !(s6 == "hi")); + llassert( s6 == "hello hello"); + llassert( s6 < "hi"); + + llassert( s6[1] == 'e' ); + s6[1] = 'f'; + llassert( s6[1] == 'f' ); + + s2.erase( 4, 1 ); + llassert( s2 == "hell"); + s2.insert( 0, 'y' ); + llassert( s2 == "yhell"); + s2.erase( 1, 3 ); + llassert( s2 == "yl"); + s2.insert( 1, "awn, don't yel"); + llassert( s2 == "yawn, don't yell"); + + LLString s8 = s2.substr( 6, 5 ); + llassert( s8 == "don't" ); + + LLString s9 = " \t\ntest \t\t\n "; + trim(s9); + llassert( s9 == "test" ); + + s8 = "abc123&*(ABC"; + + s9 = s8; + toUpper(s9); + llassert( s9 == "ABC123&*(ABC" ); + + s9 = s8; + toLower(s9); + llassert( s9 == "abc123&*(abc" ); + + + LLString s10( 10, 'x' ); + llassert( s10 == "xxxxxxxxxx" ); + + LLString s11( "monkey in the middle", 7, 2 ); + llassert( s11 == "in" ); + + LLString s12; //empty + s12 += "foo"; + llassert( s12 == "foo" ); + + LLString s13; //empty + s13 += 'f'; + llassert( s13 == "f" ); +} + + +#endif // _DEBUG diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h new file mode 100644 index 0000000000..dca8ce4f3e --- /dev/null +++ b/indra/llcommon/llstring.h @@ -0,0 +1,1281 @@ +/** + * @file llstring.h + * @brief String utility functions and LLString class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSTRING_H +#define LL_LLSTRING_H + +#include "stdtypes.h" +#include "llerror.h" +#include +#include +#include +#include +#include +#include +#include +#if LL_LINUX +#include +#include +#endif + +const char LL_UNKNOWN_CHAR = '?'; + +class LLVector3; +class LLVector3d; +class LLQuaternion; +class LLUUID; +class LLColor4; +class LLColor4U; + +#if (LL_DARWIN || (LL_LINUX && __GNUC__ > 2)) +// Template specialization of char_traits for U16s. Only necessary on Mac for now (exists on Windows, unused/broken on Linux/gcc2.95) +namespace std +{ +template<> +struct char_traits +{ + typedef U16 char_type; + typedef int int_type; + typedef streampos pos_type; + typedef streamoff off_type; + typedef mbstate_t state_type; + + static void + assign(char_type& __c1, const char_type& __c2) + { __c1 = __c2; } + + static bool + eq(const char_type& __c1, const char_type& __c2) + { return __c1 == __c2; } + + static bool + lt(const char_type& __c1, const char_type& __c2) + { return __c1 < __c2; } + + static int + compare(const char_type* __s1, const char_type* __s2, size_t __n) + { return memcmp(__s1, __s2, __n * sizeof(char_type)); } + + static size_t + length(const char_type* __s) + { + const char_type *cur_char = __s; + while (*cur_char != 0) + { + ++cur_char; + } + return cur_char - __s; + } + + static const char_type* + find(const char_type* __s, size_t __n, const char_type& __a) + { return static_cast(memchr(__s, __a, __n * sizeof(char_type))); } + + static char_type* + move(char_type* __s1, const char_type* __s2, size_t __n) + { return static_cast(memmove(__s1, __s2, __n * sizeof(char_type))); } + + static char_type* + copy(char_type* __s1, const char_type* __s2, size_t __n) + { return static_cast(memcpy(__s1, __s2, __n * sizeof(char_type))); } + + static char_type* + assign(char_type* __s, size_t __n, char_type __a) + { + // This isn't right. + //return static_cast(memset(__s, __a, __n * sizeof(char_type))); + + // I don't think there's a standard 'memset' for 16-bit values. + // Do this the old-fashioned way. + + size_t __i; + for(__i = 0; __i < __n; __i++) + { + __s[__i] = __a; + } + return __s; + } + + static char_type + to_char_type(const int_type& __c) + { return static_cast(__c); } + + static int_type + to_int_type(const char_type& __c) + { return static_cast(__c); } + + static bool + eq_int_type(const int_type& __c1, const int_type& __c2) + { return __c1 == __c2; } + + static int_type + eof() { return static_cast(EOF); } + + static int_type + not_eof(const int_type& __c) + { return (__c == eof()) ? 0 : __c; } + }; +}; +#endif + +class LLStringOps +{ +public: + static char toUpper(char elem) { return toupper(elem); } + static llwchar toUpper(llwchar elem) { return towupper(elem); } + + static char toLower(char elem) { return tolower(elem); } + static llwchar toLower(llwchar elem) { return towlower(elem); } + + static BOOL isSpace(char elem) { return isspace(elem) != 0; } + static BOOL isSpace(llwchar elem) { return iswspace(elem) != 0; } + + static BOOL isUpper(char elem) { return isupper(elem) != 0; } + static BOOL isUpper(llwchar elem) { return iswupper(elem) != 0; } + + static BOOL isLower(char elem) { return islower(elem) != 0; } + static BOOL isLower(llwchar elem) { return iswlower(elem) != 0; } + + static S32 collate(const char* a, const char* b) { return strcoll(a, b); } + static S32 collate(const llwchar* a, const llwchar* b); + + static BOOL isDigit(char a) { return isdigit(a) != 0; } + static BOOL isDigit(llwchar a) { return iswdigit(a) != 0; } +}; + +//RN: I used a templated base class instead of a pure interface class to minimize code duplication +// but it might be worthwhile to just go with two implementations (LLString and LLWString) of +// an interface class, unless we can think of a good reason to have a std::basic_string polymorphic base + +//**************************************************************** +// NOTA BENE: do *NOT* dynamically allocate memory inside of LLStringBase as the {*()^#%*)#%W^*)#%*)STL implentation +// of basic_string doesn't provide a virtual destructor. If we need to allocate resources specific to LLString +// then we should either customize std::basic_string to linden::basic_string or change LLString to be a wrapper +// that contains an instance of std::basic_string. Similarly, overriding methods defined in std::basic_string will *not* +// be called in a polymorphic manner (passing an instance of basic_string to a particular function) +//**************************************************************** + +template +class LLStringBase : public std::basic_string +{ +public: + typedef typename std::basic_string::size_type size_type; + + // naming convention follows those set for LLUUID +// static LLStringBase null; // deprecated for std::string compliance +// static LLStringBase zero_length; // deprecated for std::string compliance + + + // standard constructors + LLStringBase() : std::basic_string() {} + LLStringBase(const LLStringBase& s): std::basic_string(s) {} + LLStringBase(const std::basic_string& s) : std::basic_string(s) {} + LLStringBase(const std::basic_string& s, size_type pos, size_type n = std::basic_string::npos) + : std::basic_string(s, pos, n) {} + LLStringBase(size_type count, const T& c) : std::basic_string() { assign(count, c);} + // custom constructors + LLStringBase(const T* s); + LLStringBase(const T* s, size_type n); + LLStringBase(const T* s, size_type pos, size_type n ); + +#if LL_LINUX + void clear() { assign(null); } + + LLStringBase& assign(const T* s); + LLStringBase& assign(const T* s, size_type n); + LLStringBase& assign(const LLStringBase& s); + LLStringBase& assign(size_type n, const T& c); + LLStringBase& assign(const T* a, const T* b); + LLStringBase& assign(typename LLStringBase::iterator &it1, typename LLStringBase::iterator &it2); + LLStringBase& assign(typename LLStringBase::const_iterator &it1, typename LLStringBase::const_iterator &it2); + + // workaround for bug in gcc2 STL headers. + #if ((__GNUC__ <= 2) && (!defined _STLPORT_VERSION)) + const T* c_str () const + { + if (length () == 0) + { + static const T zero = 0; + return &zero; + } + + //terminate (); + { string_char_traits::assign(const_cast(data())[length()], string_char_traits::eos()); } + + return data (); + } + #endif +#endif + + bool operator==(const T* _Right) const { return _Right ? (std::basic_string::compare(_Right) == 0) : this->empty(); } + +public: + ///////////////////////////////////////////////////////////////////////////////////////// + // Static Utility functions that operate on std::strings + + static LLStringBase null; + + typedef std::map format_map_t; + static S32 format(std::basic_string& s, const format_map_t& fmt_map); + + static BOOL isValidIndex(const std::basic_string& string, size_type i) + { + return !string.empty() && (0 <= i) && (i <= string.size()); + } + + static void trimHead(std::basic_string& string); + static void trimTail(std::basic_string& string); + static void trim(std::basic_string& string) { trimHead(string); trimTail(string); } + static void truncate(std::basic_string& string, size_type count); + + static void toUpper(std::basic_string& string); + static void toLower(std::basic_string& string); + + // True if this is the head of s. + static BOOL isHead( const std::basic_string& string, const T* s ); + + static void addCRLF(std::basic_string& string); + static void removeCRLF(std::basic_string& string); + + static void replaceTabsWithSpaces( std::basic_string& string, size_type spaces_per_tab ); + static void replaceNonstandardASCII( std::basic_string& string, T replacement ); + static void replaceChar( std::basic_string& string, T target, T replacement ); + + static BOOL containsNonprintable(const std::basic_string& string); + static void stripNonprintable(std::basic_string& string); + + /** + * @brief Unsafe way to make ascii characters. You should probably + * only call this when interacting with the host operating system. + * The 1 byte LLString does not work correctly. + * The 2 and 4 byte LLString probably work, so LLWString::_makeASCII + * should work. + */ + static void _makeASCII(std::basic_string& string); + + static BOOL read(std::basic_string& string, const char* filename); /*Flawfinder: ignore*/ + static BOOL write(std::basic_string& string, const char* filename); + + // Conversion to other data types + static BOOL convertToBOOL(const std::basic_string& string, BOOL& value); + static BOOL convertToU8(const std::basic_string& string, U8& value); + static BOOL convertToS8(const std::basic_string& string, S8& value); + static BOOL convertToS16(const std::basic_string& string, S16& value); + static BOOL convertToU16(const std::basic_string& string, U16& value); + static BOOL convertToU32(const std::basic_string& string, U32& value); + static BOOL convertToS32(const std::basic_string& string, S32& value); + static BOOL convertToF32(const std::basic_string& string, F32& value); + static BOOL convertToF64(const std::basic_string& string, F64& value); + + ///////////////////////////////////////////////////////////////////////////////////////// + // Utility functions for working with char*'s and strings + + // Like strcmp but also handles empty strings. Uses + // current locale. + static S32 compareStrings(const T* lhs, const T* rhs); + + // case insensitive version of above. Uses current locale on + // Win32, and falls back to a non-locale aware comparison on + // Linux. + static S32 compareInsensitive(const T* lhs, const T* rhs); + + // Case sensitive comparison with good handling of numbers. Does not use current locale. + // a.k.a. strdictcmp() + static S32 compareDict(const std::basic_string& a, const std::basic_string& b); + + // Puts compareDict() in a form appropriate for LL container classes to use for sorting. + static BOOL precedesDict( const std::basic_string& a, const std::basic_string& b ); + + // A replacement for strncpy. + // If the dst buffer is dst_size bytes long or more, ensures that dst is null terminated and holds + // up to dst_size-1 characters of src. + static void copy(T* dst, const T* src, size_type dst_size); + + // Copies src into dst at a given offset. + static void copyInto(std::basic_string& dst, const std::basic_string& src, size_type offset); + +#ifdef _DEBUG + static void testHarness(); +#endif + +}; + +template LLStringBase LLStringBase::null; + +typedef LLStringBase LLString; +typedef LLStringBase LLWString; + +struct LLDictionaryLess +{ +public: + bool operator()(const std::string& a, const std::string& b) + { + return (LLString::precedesDict(a, b) ? true : false); + } +}; + + +/** + * Simple support functions + */ + +/** + * @breif chop off the trailing characters in a string. + * + * This function works on bytes rather than glyphs, so this will + * incorrectly truncate non-single byte strings. + * Use utf8str_truncate() for utf8 strings + * @return a copy of in string minus the trailing count characters. + */ +inline std::string chop_tail_copy( + const std::string& in, + std::string::size_type count) +{ + return std::string(in, 0, in.length() - count); +} + +/** + * @brief Return a string constructed from in without crashing if the + * pointer is NULL. + */ +std::string ll_safe_string(const char* in); + +/** + * @brief This translates a nybble stored as a hex value from 0-f back + * to a nybble in the low order bits of the return byte. + */ +U8 hex_as_nybble(char hex); + + +/** + * Unicode support + */ + +// Make the incoming string a utf8 string. Replaces any unknown glyph +// with the UNKOWN_CHARACTER. Once any unknown glph is found, the rest +// of the data may not be recovered. +std::string rawstr_to_utf8(const std::string& raw); + +// +// We should never use UTF16 except when communicating with Win32! +// +typedef std::basic_string llutf16string; + +LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len); +LLWString utf16str_to_wstring(const llutf16string &utf16str); + +llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len); +llutf16string wstring_to_utf16str(const LLWString &utf32str); + +llutf16string utf8str_to_utf16str ( const LLString& utf8str, S32 len); +llutf16string utf8str_to_utf16str ( const LLString& utf8str ); + +LLWString utf8str_to_wstring(const std::string &utf8str, S32 len); +LLWString utf8str_to_wstring(const std::string &utf8str); +// Same function, better name. JC +inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); } + +// Special hack for llfilepicker.cpp: +S32 utf16chars_to_utf8chars(const U16* inchars, char* outchars, S32* nchars8 = 0); +S32 utf16chars_to_wchar(const U16* inchars, llwchar* outchar); +S32 wchar_to_utf8chars(llwchar inchar, char* outchars); + +// +std::string wstring_to_utf8str(const LLWString &utf32str, S32 len); +std::string wstring_to_utf8str(const LLWString &utf32str); + +std::string utf16str_to_utf8str(const llutf16string &utf16str, S32 len); +std::string utf16str_to_utf8str(const llutf16string &utf16str); + +// Length of this UTF32 string in bytes when transformed to UTF8 +S32 wstring_utf8_length(const LLWString& wstr); + +// Length in bytes of this wide char in a UTF8 string +S32 wchar_utf8_length(const llwchar wc); + +std::string utf8str_tolower(const std::string& utf8str); + +/** + * @brief Properly truncate a utf8 string to a maximum byte count. + * + * The returned string may be less than max_len if the truncation + * happens in the middle of a glyph. If max_len is longer than the + * string passed in, the return value == utf8str. + * @param utf8str A valid utf8 string to truncate. + * @param max_len The maximum number of bytes in the returne + * @return Returns a valid utf8 string with byte count <= max_len. + */ +std::string utf8str_truncate(const std::string& utf8str, const S32 max_len); + +std::string utf8str_trim(const std::string& utf8str); + +S32 utf8str_compare_insensitive( + const std::string& lhs, + const std::string& rhs); + +/** + * @brief Replace all occurences of target_char with replace_char + * + * @param utf8str A utf8 string to process. + * @param target_char The wchar to be replaced + * @param replace_char The wchar which is written on replace + */ +std::string utf8str_substChar( + const std::string& utf8str, + const llwchar target_char, + const llwchar replace_char); + +std::string utf8str_makeASCII(const std::string& utf8str); + +// Hack - used for evil notecards. +std::string mbcsstring_makeASCII(const std::string& str); + +template +std::ostream& operator<<(std::ostream &s, const LLStringBase &str) +{ + s << ((std::basic_string)str); + return s; +} + +std::ostream& operator<<(std::ostream &s, const LLWString &wstr); + + +/** + * Many of the 'strip' and 'replace' methods of LLStringBase need + * specialization to work with the signed char type. + * Sadly, it is not possible (AFAIK) to specialize a single method of + * a template class. + * That stuff should go here. + */ +namespace LLStringFn +{ + /** + * @brief Replace all non-printable characters with replacement in + * string. + * + * @param [in,out] string the to modify. out value is the string + * with zero non-printable characters. + * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. + */ + void replace_nonprintable( + std::basic_string& string, + char replacement); + + /** + * @brief Replace all non-printable characters with replacement in + * a wide string. + * + * @param [in,out] string the to modify. out value is the string + * with zero non-printable characters. + * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. + */ + void replace_nonprintable( + std::basic_string& string, + llwchar replacement); + + /** + * @brief Replace all non-printable characters and pipe characters + * with replacement in a string. + * + * @param [in,out] the string to modify. out value is the string + * with zero non-printable characters and zero pipe characters. + * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. + */ + void replace_nonprintable_and_pipe(std::basic_string& str, + char replacement); + + /** + * @brief Replace all non-printable characters and pipe characters + * with replacement in a wide string. + * + * @param [in,out] the string to modify. out value is the string + * with zero non-printable characters and zero pipe characters. + * @param The replacement wide character. use LL_UNKNOWN_CHAR if unsure. + */ + void replace_nonprintable_and_pipe(std::basic_string& str, + llwchar replacement); +} + +//////////////////////////////////////////////////////////// + +// static +template +S32 LLStringBase::format(std::basic_string& s, const format_map_t& fmt_map) +{ + typedef typename std::basic_string::size_type string_size_type_t; + S32 res = 0; + for (format_map_t::const_iterator iter = fmt_map.begin(); iter != fmt_map.end(); ++iter) + { + U32 fmtlen = iter->first.size(); + string_size_type_t n = 0; + while (1) + { + n = s.find(iter->first, n); + if (n == std::basic_string::npos) + { + break; + } + s.erase(n, fmtlen); + s.insert(n, iter->second); + n += fmtlen; + ++res; + } + } + return res; +} + +// static +template +S32 LLStringBase::compareStrings(const T* lhs, const T* rhs) +{ + S32 result; + if( lhs == rhs ) + { + result = 0; + } + else + if ( !lhs || !lhs[0] ) + { + result = ((!rhs || !rhs[0]) ? 0 : 1); + } + else + if ( !rhs || !rhs[0]) + { + result = -1; + } + else + { + result = LLStringOps::collate(lhs, rhs); + } + return result; +} + +// static +template +S32 LLStringBase::compareInsensitive(const T* lhs, const T* rhs ) +{ + S32 result; + if( lhs == rhs ) + { + result = 0; + } + else + if ( !lhs || !lhs[0] ) + { + result = ((!rhs || !rhs[0]) ? 0 : 1); + } + else + if ( !rhs || !rhs[0] ) + { + result = -1; + } + else + { + LLStringBase lhs_string(lhs); + LLStringBase rhs_string(rhs); + LLStringBase::toUpper(lhs_string); + LLStringBase::toUpper(rhs_string); + result = LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str()); + } + return result; +} + + +// Case sensitive comparison with good handling of numbers. Does not use current locale. +// a.k.a. strdictcmp() + +//static +template +S32 LLStringBase::compareDict(const std::basic_string& astr, const std::basic_string& bstr) +{ + const T* a = astr.c_str(); + const T* b = bstr.c_str(); + T ca, cb; + S32 ai, bi, cnt = 0; + S32 bias = 0; + + ca = *(a++); + cb = *(b++); + while( ca && cb ){ + if( bias==0 ){ + if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); bias--; } + if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); bias++; } + }else{ + if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); } + if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); } + } + if( LLStringOps::isDigit(ca) ){ + if( cnt-->0 ){ + if( cb!=ca ) break; + }else{ + if( !LLStringOps::isDigit(cb) ) break; + for(ai=0; LLStringOps::isDigit(a[ai]); ai++); + for(bi=0; LLStringOps::isDigit(b[bi]); bi++); + if( ai +BOOL LLStringBase::precedesDict( const std::basic_string& a, const std::basic_string& b ) +{ + if( a.size() && b.size() ) + { + return (LLStringBase::compareDict(a.c_str(), b.c_str()) < 0); + } + else + { + return (!b.empty()); + } +} + +// Constructors +template +LLStringBase::LLStringBase(const T* s ) : std::basic_string() +{ + if (s) assign(s); +} + +template +LLStringBase::LLStringBase(const T* s, size_type n ) : std::basic_string() +{ + if (s) assign(s, n); +} + +// Init from a substring +template +LLStringBase::LLStringBase(const T* s, size_type pos, size_type n ) : std::basic_string() +{ + if( s ) + { + assign(s + pos, n); + } + else + { + assign(LLStringBase::null); + } +} + +#if LL_LINUX +template +LLStringBase& LLStringBase::assign(const T* s) +{ + if (s) + { + std::basic_string::assign(s); + } + else + { + assign(LLStringBase::null); + } + return *this; +} + +template +LLStringBase& LLStringBase::assign(const T* s, size_type n) +{ + if (s) + { + std::basic_string::assign(s, n); + } + else + { + assign(LLStringBase::null); + } + return *this; +} + +template +LLStringBase& LLStringBase::assign(const LLStringBase& s) +{ + std::basic_string::assign(s); + return *this; +} + +template +LLStringBase& LLStringBase::assign(size_type n, const T& c) +{ + std::basic_string::assign(n, c); + return *this; +} + +template +LLStringBase& LLStringBase::assign(const T* a, const T* b) +{ + if (a > b) + assign(LLStringBase::null); + else + assign(a, (size_type) (b-a)); + return *this; +} + +template +LLStringBase& LLStringBase::assign(typename LLStringBase::iterator &it1, typename LLStringBase::iterator &it2) +{ + assign(LLStringBase::null); + while(it1 != it2) + *this += *it1++; + return *this; +} + +template +LLStringBase& LLStringBase::assign(typename LLStringBase::const_iterator &it1, typename LLStringBase::const_iterator &it2) +{ + assign(LLStringBase::null); + while(it1 != it2) + *this += *it1++; + return *this; +} +#endif + +//static +template +void LLStringBase::toUpper(std::basic_string& string) +{ + if( !string.empty() ) + { + std::transform( + string.begin(), + string.end(), + string.begin(), + (T(*)(T)) &LLStringOps::toUpper); + } +} + +//static +template +void LLStringBase::toLower(std::basic_string& string) +{ + if( !string.empty() ) + { + std::transform( + string.begin(), + string.end(), + string.begin(), + (T(*)(T)) &LLStringOps::toLower); + } +} + +//static +template +void LLStringBase::trimHead(std::basic_string& string) +{ + if( !string.empty() ) + { + size_type i = 0; + while( i < string.length() && LLStringOps::isSpace( string[i] ) ) + { + i++; + } + string.erase(0, i); + } +} + +//static +template +void LLStringBase::trimTail(std::basic_string& string) +{ + if( string.size() ) + { + size_type len = string.length(); + size_type i = len; + while( i > 0 && LLStringOps::isSpace( string[i-1] ) ) + { + i--; + } + + string.erase( i, len - i ); + } +} + + +// Replace line feeds with carriage return-line feed pairs. +//static +template +void LLStringBase::addCRLF(std::basic_string& string) +{ + const T LF = 10; + const T CR = 13; + + // Count the number of line feeds + size_type count = 0; + size_type len = string.size(); + size_type i; + for( i = 0; i < len; i++ ) + { + if( string[i] == LF ) + { + count++; + } + } + + // Insert a carriage return before each line feed + if( count ) + { + size_type size = len + count; + T *t = new T[size]; + size_type j = 0; + for( i = 0; i < len; ++i ) + { + if( string[i] == LF ) + { + t[j] = CR; + ++j; + } + t[j] = string[i]; + ++j; + } + + string.assign(t, size); + } +} + +// Remove all carriage returns +//static +template +void LLStringBase::removeCRLF(std::basic_string& string) +{ + const T CR = 13; + + size_type cr_count = 0; + size_type len = string.size(); + size_type i; + for( i = 0; i < len - cr_count; i++ ) + { + if( string[i+cr_count] == CR ) + { + cr_count++; + } + + string[i] = string[i+cr_count]; + } + string.erase(i, cr_count); +} + +//static +template +void LLStringBase::replaceChar( std::basic_string& string, T target, T replacement ) +{ + size_type found_pos = 0; + for (found_pos = string.find(target, found_pos); + found_pos != std::basic_string::npos; + found_pos = string.find(target, found_pos)) + { + string[found_pos] = replacement; + } +} + +//static +template +void LLStringBase::replaceNonstandardASCII( std::basic_string& string, T replacement ) +{ + const char LF = 10; + const S8 MIN = 32; +// const S8 MAX = 127; + + size_type len = string.size(); + for( size_type i = 0; i < len; i++ ) + { + // No need to test MAX < mText[i] because we treat mText[i] as a signed char, + // which has a max value of 127. + if( ( S8(string[i]) < MIN ) && (string[i] != LF) ) + { + string[i] = replacement; + } + } +} + +//static +template +void LLStringBase::replaceTabsWithSpaces( std::basic_string& string, size_type spaces_per_tab ) +{ + llassert( spaces_per_tab >= 0 ); + + const T TAB = '\t'; + const T SPACE = ' '; + + LLStringBase out_str; + // Replace tabs with spaces + for (size_type i = 0; i < string.length(); i++) + { + if (string[i] == TAB) + { + for (size_type j = 0; j < spaces_per_tab; j++) + out_str += SPACE; + } + else + { + out_str += string[i]; + } + } + string = out_str; +} + +//static +template +BOOL LLStringBase::containsNonprintable(const std::basic_string& string) +{ + const char MIN = 32; + BOOL rv = FALSE; + for (size_type i = 0; i < string.size(); i++) + { + if(string[i] < MIN) + { + rv = TRUE; + break; + } + } + return rv; +} + +//static +template +void LLStringBase::stripNonprintable(std::basic_string& string) +{ + const char MIN = 32; + size_type j = 0; + if (string.empty()) + { + return; + } + char* c_string = new char[string.size() + 1]; + if(c_string == NULL) + { + return; + } + strcpy(c_string, string.c_str()); /*Flawfinder: ignore*/ + char* write_head = &c_string[0]; + for (size_type i = 0; i < string.size(); i++) + { + char* read_head = &string[i]; + write_head = &c_string[j]; + if(!(*read_head < MIN)) + { + *write_head = *read_head; + ++j; + } + } + c_string[j]= '\0'; + string = c_string; + delete []c_string; +} + +template +void LLStringBase::_makeASCII(std::basic_string& string) +{ + // Replace non-ASCII chars with LL_UNKNOWN_CHAR + for (size_type i = 0; i < string.length(); i++) + { + if (string[i] > 0x7f) + { + string[i] = LL_UNKNOWN_CHAR; + } + } +} + +// static +template +void LLStringBase::copy( T* dst, const T* src, size_type dst_size ) +{ + if( dst_size > 0 ) + { + size_type min_len = 0; + if( src ) + { + min_len = llmin( dst_size - 1, strlen( src ) ); /* Flawfinder: ignore */ + memcpy(dst, src, min_len * sizeof(T)); /* Flawfinder: ignore */ + } + dst[min_len] = '\0'; + } +} + +// static +template +void LLStringBase::copyInto(std::basic_string& dst, const std::basic_string& src, size_type offset) +{ + llassert( offset <= dst.length() ); + + // special case - append to end of string and avoid expensive (when strings are large) string manipulations + if ( offset == dst.length() ) + { + dst += src; + } + else + { + LLWString tail = dst.substr(offset); + + dst = dst.substr(0, offset); + dst += src; + dst += tail; + }; +} + +// True if this is the head of s. +//static +template +BOOL LLStringBase::isHead( const std::basic_string& string, const T* s ) +{ + if( string.empty() ) + { + // Early exit + return FALSE; + } + else + { + return (strncmp( s, string.c_str(), string.size() ) == 0); + } +} + +//static +template +BOOL LLStringBase::read(std::basic_string& string, const char* filename) /*Flawfinder: ignore*/ +{ +#ifdef LL_LINUX + printf("STUBBED: LLStringBase::read at %s:%d\n", __FILE__, __LINE__); +#else + llifstream ifs(filename, llifstream::binary); + if (!ifs.is_open()) + { + llinfos << "Unable to open file" << filename << llendl; + return FALSE; + } + + std::basic_ostringstream oss; + + oss << ifs.rdbuf(); + + string = oss.str(); + + ifs.close(); +#endif + return TRUE; +} + +//static +template +BOOL LLStringBase::write(std::basic_string& string, const char* filename) +{ +#ifdef LL_LINUX + printf("STUBBED: LLStringBase::write at %s:%d\n", __FILE__, __LINE__); +#else + llofstream ofs(filename, llofstream::binary); + if (!ofs.is_open()) + { + llinfos << "Unable to open file" << filename << llendl; + return FALSE; + } + + ofs << string; + + ofs.close(); +#endif + return TRUE; +} + +template +BOOL LLStringBase::convertToBOOL(const std::basic_string& string, BOOL& value) +{ + if( string.empty() ) + { + return FALSE; + } + + LLStringBase temp( string ); + trim(temp); + if( + (temp == "1") || + (temp == "T") || + (temp == "t") || + (temp == "TRUE") || + (temp == "true") || + (temp == "True") ) + { + value = TRUE; + return TRUE; + } + else + if( + (temp == "0") || + (temp == "F") || + (temp == "f") || + (temp == "FALSE") || + (temp == "false") || + (temp == "False") ) + { + value = FALSE; + return TRUE; + } + + return FALSE; +} + +template +BOOL LLStringBase::convertToU8(const std::basic_string& string, U8& value) +{ + S32 value32 = 0; + BOOL success = convertToS32(string, value32); + if( success && (U8_MIN <= value32) && (value32 <= U8_MAX) ) + { + value = (U8) value32; + return TRUE; + } + return FALSE; +} + +template +BOOL LLStringBase::convertToS8(const std::basic_string& string, S8& value) +{ + S32 value32 = 0; + BOOL success = convertToS32(string, value32); + if( success && (S8_MIN <= value32) && (value32 <= S8_MAX) ) + { + value = (S8) value32; + return TRUE; + } + return FALSE; +} + +template +BOOL LLStringBase::convertToS16(const std::basic_string& string, S16& value) +{ + S32 value32 = 0; + BOOL success = convertToS32(string, value32); + if( success && (S16_MIN <= value32) && (value32 <= S16_MAX) ) + { + value = (S16) value32; + return TRUE; + } + return FALSE; +} + +template +BOOL LLStringBase::convertToU16(const std::basic_string& string, U16& value) +{ + S32 value32 = 0; + BOOL success = convertToS32(string, value32); + if( success && (U16_MIN <= value32) && (value32 <= U16_MAX) ) + { + value = (U16) value32; + return TRUE; + } + return FALSE; +} + +template +BOOL LLStringBase::convertToU32(const std::basic_string& string, U32& value) +{ + if( string.empty() ) + { + return FALSE; + } + + LLStringBase temp( string ); + trim(temp); + U32 v; + std::basic_istringstream i_stream((std::basic_string)temp); + if(i_stream >> v) + { + //TODO: figure out overflow reporting here + //if( ULONG_MAX == v ) + //{ + // // Underflow or overflow + // return FALSE; + //} + + value = v; + return TRUE; + } + return FALSE; +} + +template +BOOL LLStringBase::convertToS32(const std::basic_string& string, S32& value) +{ + if( string.empty() ) + { + return FALSE; + } + + LLStringBase temp( string ); + trim(temp); + S32 v; + std::basic_istringstream i_stream((std::basic_string)temp); + if(i_stream >> v) + { + //TODO: figure out overflow and underflow reporting here + //if((LONG_MAX == v) || (LONG_MIN == v)) + //{ + // // Underflow or overflow + // return FALSE; + //} + + value = v; + return TRUE; + } + return FALSE; +} + +template +BOOL LLStringBase::convertToF32(const std::basic_string& string, F32& value) +{ + F64 value64 = 0.0; + BOOL success = convertToF64(string, value64); + if( success && (-F32_MAX <= value64) && (value64 <= F32_MAX) ) + { + value = (F32) value64; + return TRUE; + } + return FALSE; +} + +template +BOOL LLStringBase::convertToF64(const std::basic_string& string, F64& value) +{ + if( string.empty() ) + { + return FALSE; + } + + LLStringBase temp( string ); + trim(temp); + F64 v; + std::basic_istringstream i_stream((std::basic_string)temp); + if(i_stream >> v) + { + //TODO: figure out overflow and underflow reporting here + //if( ((-HUGE_VAL == v) || (HUGE_VAL == v))) ) + //{ + // // Underflow or overflow + // return FALSE; + //} + + value = v; + return TRUE; + } + return FALSE; +} + +template +void LLStringBase::truncate(std::basic_string& string, size_type count) +{ + size_type cur_size = string.size(); + string.resize(count < cur_size ? count : cur_size); +} + +#endif // LL_STRING_H diff --git a/indra/llcommon/llstringtable.cpp b/indra/llcommon/llstringtable.cpp new file mode 100644 index 0000000000..483c4fe502 --- /dev/null +++ b/indra/llcommon/llstringtable.cpp @@ -0,0 +1,323 @@ +/** + * @file llstringtable.cpp + * @brief The LLStringTable class provides a _fast_ method for finding + * unique copies of strings. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llstringtable.h" +#include "llstl.h" + +LLStringTable gStringTable(32768); + +LLStringTable::LLStringTable(int tablesize) +: mUniqueEntries(0) +{ + S32 i; + if (!tablesize) + tablesize = 4096; // some arbitrary default + // Make sure tablesize is power of 2 + for (i = 31; i>0; i--) + { + if (tablesize & (1<= (3<<(i-1))) + tablesize = (1<<(i+1)); + else + tablesize = (1<begin(); iter != mStringList[i]->end(); iter++) + delete *iter; // *iter = (LLStringTableEntry*) + } + delete mStringList[i]; + } + delete [] mStringList; + mStringList = NULL; + } +#else + // Need to clean up the string hash + for_each(mStringHash.begin(), mStringHash.end(), DeletePairedPointer()); + mStringHash.clear(); +#endif +} + + +static U32 hash_my_string(const char *str, int max_entries) +{ + U32 retval = 0; +#if 0 + while (*str) + { + retval <<= 1; + retval += *str++; + } +#else + while (*str) + { + retval = (retval<<4) + *str; + U32 x = (retval & 0xf0000000); + if (x) retval = retval ^ (x>>24); + retval = retval & (~x); + str++; + } +#endif + return (retval & (max_entries-1)); // max_entries is gauranteed to be power of 2 +} + +char* LLStringTable::checkString(const std::string& str) +{ + return checkString(str.c_str()); +} + +char* LLStringTable::checkString(const char *str) +{ + LLStringTableEntry* entry = checkStringEntry(str); + if (entry) + { + return entry->mString; + } + else + { + return NULL; + } +} + +LLStringTableEntry* LLStringTable::checkStringEntry(const std::string& str) +{ + return checkStringEntry(str.c_str()); +} + +LLStringTableEntry* LLStringTable::checkStringEntry(const char *str) +{ + if (str) + { + char *ret_val; + LLStringTableEntry *entry; + U32 hash_value = hash_my_string(str, mMaxEntries); +#if STRING_TABLE_HASH_MAP +#if 1 // Microsoft + string_hash_t::iterator lower = mStringHash.lower_bound(hash_value); + string_hash_t::iterator upper = mStringHash.upper_bound(hash_value); +#else // stlport + std::pair P = mStringHash.equal_range(hash_value); + string_hash_t::iterator lower = P.first; + string_hash_t::iterator upper = P.second; +#endif + for (string_hash_t::iterator iter = lower; iter != upper; iter++) + { + entry = iter->second; + ret_val = entry->mString; + if (!strncmp(ret_val, str, MAX_STRINGS_LENGTH)) + { + return entry; + } + } +#else + string_list_t *strlist = mStringList[hash_value]; + if (strlist) + { + string_list_t::iterator iter; + for (iter = strlist->begin(); iter != strlist->end(); iter++) + { + entry = *iter; + ret_val = entry->mString; + if (!strncmp(ret_val, str, MAX_STRINGS_LENGTH)) + { + return entry; + } + } + } +#endif + } + return NULL; +} + +char* LLStringTable::addString(const std::string& str) +{ + //RN: safe to use temporary c_str since string is copied + return addString(str.c_str()); +} + +char* LLStringTable::addString(const char *str) +{ + + LLStringTableEntry* entry = addStringEntry(str); + if (entry) + { + return entry->mString; + } + else + { + return NULL; + } +} + +LLStringTableEntry* LLStringTable::addStringEntry(const std::string& str) +{ + return addStringEntry(str.c_str()); +} + +LLStringTableEntry* LLStringTable::addStringEntry(const char *str) +{ + if (str) + { + char *ret_val = NULL; + LLStringTableEntry *entry; + U32 hash_value = hash_my_string(str, mMaxEntries); +#if STRING_TABLE_HASH_MAP +#if 1 // Microsoft + string_hash_t::iterator lower = mStringHash.lower_bound(hash_value); + string_hash_t::iterator upper = mStringHash.upper_bound(hash_value); +#else // stlport + std::pair P = mStringHash.equal_range(hash_value); + string_hash_t::iterator lower = P.first; + string_hash_t::iterator upper = P.second; +#endif + for (string_hash_t::iterator iter = lower; iter != upper; iter++) + { + entry = iter->second; + ret_val = entry->mString; + if (!strncmp(ret_val, str, MAX_STRINGS_LENGTH)) + { + entry->incCount(); + return entry; + } + } + + // not found, so add! + LLStringTableEntry* newentry = new LLStringTableEntry(str); + ret_val = newentry->mString; + mStringHash.insert(string_hash_t::value_type(hash_value, newentry)); +#else + string_list_t *strlist = mStringList[hash_value]; + + if (strlist) + { + string_list_t::iterator iter; + for (iter = strlist->begin(); iter != strlist->end(); iter++) + { + entry = *iter; + ret_val = entry->mString; + if (!strncmp(ret_val, str, MAX_STRINGS_LENGTH)) + { + entry->incCount(); + return entry; + } + } + } + else + { + mStringList[hash_value] = new string_list_t; + strlist = mStringList[hash_value]; + } + + // not found, so add! + LLStringTableEntry *newentry = new LLStringTableEntry(str); + //ret_val = newentry->mString; + strlist->push_front(newentry); +#endif + mUniqueEntries++; + return newentry; + } + else + { + return NULL; + } +} + +void LLStringTable::removeString(const char *str) +{ + if (str) + { + char *ret_val; + LLStringTableEntry *entry; + U32 hash_value = hash_my_string(str, mMaxEntries); +#if STRING_TABLE_HASH_MAP + { +#if 1 // Microsoft + string_hash_t::iterator lower = mStringHash.lower_bound(hash_value); + string_hash_t::iterator upper = mStringHash.upper_bound(hash_value); +#else // stlport + std::pair P = mStringHash.equal_range(hash_value); + string_hash_t::iterator lower = P.first; + string_hash_t::iterator upper = P.second; +#endif + for (string_hash_t::iterator iter = lower; iter != upper; iter++) + { + entry = iter->second; + ret_val = entry->mString; + if (!strncmp(ret_val, str, MAX_STRINGS_LENGTH)) + { + if (!entry->decCount()) + { + mUniqueEntries--; + if (mUniqueEntries < 0) + { + llerror("LLStringTable:removeString trying to remove too many strings!", 0); + } + delete iter->second; + mStringHash.erase(iter); + } + return; + } + } + } +#else + string_list_t *strlist = mStringList[hash_value]; + + if (strlist) + { + string_list_t::iterator iter; + for (iter = strlist->begin(); iter != strlist->end(); iter++) + { + entry = *iter; + ret_val = entry->mString; + if (!strncmp(ret_val, str, MAX_STRINGS_LENGTH)) + { + if (!entry->decCount()) + { + mUniqueEntries--; + if (mUniqueEntries < 0) + { + llerror("LLStringTable:removeString trying to remove too many strings!", 0); + } + strlist->remove(entry); + delete entry; + } + return; + } + } + } +#endif + } +} + diff --git a/indra/llcommon/llstringtable.h b/indra/llcommon/llstringtable.h new file mode 100644 index 0000000000..ad428ce565 --- /dev/null +++ b/indra/llcommon/llstringtable.h @@ -0,0 +1,208 @@ +/** + * @file llstringtable.h + * @brief The LLStringTable class provides a _fast_ method for finding + * unique copies of strings. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_STRING_TABLE_H +#define LL_STRING_TABLE_H + +#include "llstl.h" +#include +#include + +#if LL_WINDOWS +# if (_MSC_VER >= 1300) +# define STRING_TABLE_HASH_MAP 1 +# pragma warning(disable : 4996) +# endif +#else +//# define STRING_TABLE_HASH_MAP 1 +#endif + +#if STRING_TABLE_HASH_MAP +#include +#endif + +// string_table.h +// LLStringTable class header file +// Provides a _fast_ method for finding unique copies of strings +// +// Copyright 2001-2002, Linden Research, Inc. + +const U32 MAX_STRINGS_LENGTH = 256; + +class LLStringTableEntry +{ +public: + LLStringTableEntry(const char *str) + : mString(NULL), mCount(1) + { + // Copy string + U32 length = (U32)strlen(str) + 1; /*Flawfinder: ignore*/ + length = llmin(length, MAX_STRINGS_LENGTH); + mString = new char[length]; + strncpy(mString, str, length); /*Flawfinder: ignore*/ + mString[length - 1] = 0; + } + ~LLStringTableEntry() + { + delete [] mString; + mCount = 0; + } + void incCount() { mCount++; } + BOOL decCount() { return --mCount; } + + char *mString; + S32 mCount; +}; + +class LLStringTable +{ +public: + LLStringTable(int tablesize); + ~LLStringTable(); + + char *checkString(const char *str); + char *checkString(const std::string& str); + LLStringTableEntry *checkStringEntry(const char *str); + LLStringTableEntry *checkStringEntry(const std::string& str); + + char *addString(const char *str); + char *addString(const std::string& str); + LLStringTableEntry *addStringEntry(const char *str); + LLStringTableEntry *addStringEntry(const std::string& str); + void removeString(const char *str); + + S32 mMaxEntries; + S32 mUniqueEntries; + +#if STRING_TABLE_HASH_MAP + typedef std::hash_multimap string_hash_t; + string_hash_t mStringHash; +#else + typedef std::list string_list_t; + typedef string_list_t * string_list_ptr_t; + string_list_ptr_t *mStringList; +#endif +}; + +extern LLStringTable gStringTable; + +//============================================================================ + +// This class is designed to be used locally, +// e.g. as a member of an LLXmlTree +// Strings can be inserted only, then quickly looked up + +typedef const std::string* LLStdStringHandle; + +class LLStdStringTable +{ +public: + LLStdStringTable(S32 tablesize = 0) + { + if (tablesize == 0) + { + tablesize = 256; // default + } + // Make sure tablesize is power of 2 + for (S32 i = 31; i>0; i--) + { + if (tablesize & (1<= (3<<(i-1))) + tablesize = (1<<(i+1)); + else + tablesize = (1< > string_set_t; + string_set_t* mStringList; // [mTableSize] +}; + + +#endif diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp new file mode 100644 index 0000000000..65fa4a5c9c --- /dev/null +++ b/indra/llcommon/llsys.cpp @@ -0,0 +1,534 @@ +/** + * @file llsys.cpp + * @brief Impelementation of the basic system query functions. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include +#include +#include "processor.h" + +#if LL_DARWIN +#include +#include +#elif LL_LINUX +#include +const char MEMINFO_FILE[] = "/proc/meminfo"; +const char CPUINFO_FILE[] = "/proc/cpuinfo"; +#endif + + +static const S32 CPUINFO_BUFFER_SIZE = 16383; +LLCPUInfo gSysCPU; + +LLOSInfo::LLOSInfo() : + mMajorVer(0), mMinorVer(0), mBuild(0), + mOSString("") +{ + +#if LL_WINDOWS + OSVERSIONINFOEX osvi; + BOOL bOsVersionInfoEx; + + // Try calling GetVersionEx using the OSVERSIONINFOEX structure. + ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + if(!(bOsVersionInfoEx = GetVersionEx((OSVERSIONINFO *) &osvi))) + { + // If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO. + osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); + if(!GetVersionEx( (OSVERSIONINFO *) &osvi)) + return; + } + mMajorVer = osvi.dwMajorVersion; + mMinorVer = osvi.dwMinorVersion; + mBuild = osvi.dwBuildNumber; + + switch(osvi.dwPlatformId) + { + case VER_PLATFORM_WIN32_NT: + { + // Test for the product. + if(osvi.dwMajorVersion <= 4) + { + mOSString = "Microsoft Windows NT "; + } + else if(osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) + { + mOSString = "Microsoft Windows 2000 "; + } + else if(osvi.dwMajorVersion ==5 && osvi.dwMinorVersion == 1) + { + mOSString = "Microsoft Windows XP "; + } + else if(osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2) + { + if(osvi.wProductType == VER_NT_WORKSTATION) + mOSString = "Microsoft Windows XP x64 Edition "; + else mOSString = "Microsoft Windows Server 2003 "; + } + else if(osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0) + { + if(osvi.wProductType == VER_NT_WORKSTATION) + mOSString = "Microsoft Windows Vista "; + else mOSString = "Microsoft Windows Vista Server "; + } + else // Use the registry on early versions of Windows NT. + { + HKEY hKey; + WCHAR szProductType[80]; + DWORD dwBufLen; + RegOpenKeyEx( HKEY_LOCAL_MACHINE, + L"SYSTEM\\CurrentControlSet\\Control\\ProductOptions", + 0, KEY_QUERY_VALUE, &hKey ); + RegQueryValueEx( hKey, L"ProductType", NULL, NULL, + (LPBYTE) szProductType, &dwBufLen); + RegCloseKey( hKey ); + if ( lstrcmpi( L"WINNT", szProductType) == 0 ) + { + mOSString += "Professional "; + } + else if ( lstrcmpi( L"LANMANNT", szProductType) == 0 ) + { + mOSString += "Server "; + } + else if ( lstrcmpi( L"SERVERNT", szProductType) == 0 ) + { + mOSString += "Advanced Server "; + } + } + + std::string csdversion = utf16str_to_utf8str(osvi.szCSDVersion); + // Display version, service pack (if any), and build number. + char tmp[MAX_STRING]; /* Flawfinder: ignore */ + if(osvi.dwMajorVersion <= 4) + { + snprintf( + tmp, + sizeof(tmp), + "version %d.%d %s (Build %d)", + osvi.dwMajorVersion, + osvi.dwMinorVersion, + csdversion.c_str(), + (osvi.dwBuildNumber & 0xffff)); /* Flawfinder: ignore */ + } + else + { + snprintf( + tmp, + sizeof(tmp), + "%s (Build %d)", + csdversion.c_str(), + (osvi.dwBuildNumber & 0xffff)); /*Flawfinder: ignore*/ + } + mOSString += tmp; + } + break; + + case VER_PLATFORM_WIN32_WINDOWS: + // Test for the Windows 95 product family. + if(osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 0) + { + mOSString = "Microsoft Windows 95 "; + if ( osvi.szCSDVersion[1] == 'C' || osvi.szCSDVersion[1] == 'B' ) + { + mOSString += "OSR2 "; + } + } + if(osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 10) + { + mOSString = "Microsoft Windows 98 "; + if ( osvi.szCSDVersion[1] == 'A' ) + { + mOSString += "SE "; + } + } + if(osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 90) + { + mOSString = "Microsoft Windows Millennium Edition "; + } + break; + } +#else + struct utsname un; + if(0==uname(&un)) + { + mOSString.append(un.sysname); + mOSString.append(" "); + mOSString.append(un.release); + mOSString.append(" "); + mOSString.append(un.version); + mOSString.append(" "); + mOSString.append(un.machine); + } + else + { + mOSString.append("Unable to collect OS info"); + } +#endif + +} + +#ifndef LL_WINDOWS +// static +S32 LLOSInfo::getMaxOpenFiles() +{ + const S32 OPEN_MAX_GUESS = 256; + +#ifdef OPEN_MAX + static S32 open_max = OPEN_MAX; +#else + static S32 open_max = 0; +#endif + + if (0 == open_max) + { + // First time through. + errno = 0; + if ( (open_max = sysconf(_SC_OPEN_MAX)) < 0) + { + if (0 == errno) + { + // Indeterminate. + open_max = OPEN_MAX_GUESS; + } + else + { + llerrs << "LLOSInfo::getMaxOpenFiles: sysconf error for _SC_OPEN_MAX" << llendl; + } + } + } + return open_max; +} +#endif + +void LLOSInfo::stream(std::ostream& s) const +{ + s << mOSString; +} + +const std::string& LLOSInfo::getOSString() const +{ + return mOSString; +} + +const S32 STATUS_SIZE = 8192; + +//static +U32 LLOSInfo::getProcessVirtualSizeKB() +{ + U32 virtual_size = 0; +#if LL_WINDOWS +#endif +#if LL_LINUX + FILE *status_filep = LLFile::fopen("/proc/self/status", "r"); + S32 numRead = 0; + char buff[STATUS_SIZE]; /* Flawfinder: ignore */ + bzero(buff, STATUS_SIZE); + + rewind(status_filep); + fread(buff, 1, STATUS_SIZE-2, status_filep); + + // All these guys return numbers in KB + char *memp = strstr(buff, "VmSize:"); + if (memp) + { + numRead += sscanf(memp, "%*s %u", &virtual_size); + } + fclose(status_filep); +#endif + return virtual_size; +} + +//static +U32 LLOSInfo::getProcessResidentSizeKB() +{ + U32 resident_size = 0; +#if LL_WINDOWS +#endif +#if LL_LINUX + FILE *status_filep = LLFile::fopen("/proc/self/status", "r"); + if (status_filep != NULL) + { + S32 numRead = 0; + char buff[STATUS_SIZE]; /* Flawfinder: ignore */ + bzero(buff, STATUS_SIZE); + + rewind(status_filep); + fread(buff, 1, STATUS_SIZE-2, status_filep); + + // All these guys return numbers in KB + char *memp = strstr(buff, "VmRSS:"); + if (memp) + { + numRead += sscanf(memp, "%*s %u", &resident_size); + } + fclose(status_filep); + } +#endif + return resident_size; +} + +LLCPUInfo::LLCPUInfo() +{ + CProcessor proc; + const ProcessorInfo* info = proc.GetCPUInfo(); + mHasSSE = (info->_Ext.SSE_StreamingSIMD_Extensions != 0); + mHasSSE2 = (info->_Ext.SSE2_StreamingSIMD2_Extensions != 0); + mCPUMhz = (S32)(proc.GetCPUFrequency(50)/1000000.0); + mFamily.assign( info->strFamily ); +} + +std::string LLCPUInfo::getCPUString() const +{ + std::string cpu_string; + +#if LL_WINDOWS || LL_DARWIN + // gather machine information. + char proc_buf[CPUINFO_BUFFER_SIZE]; /* Flawfinder: ignore */ + CProcessor proc; + if(proc.CPUInfoToText(proc_buf, CPUINFO_BUFFER_SIZE)) + { + cpu_string.append(proc_buf); + } +#else + cpu_string.append("Can't get CPU information"); +#endif + + return cpu_string; +} + +std::string LLCPUInfo::getCPUStringTerse() const +{ + std::string cpu_string; + +#if LL_WINDOWS || LL_DARWIN + CProcessor proc; + const ProcessorInfo *info = proc.GetCPUInfo(); + + cpu_string.append(info->strBrandID); + + F64 freq = (F64)(S64)proc.GetCPUFrequency(50) / 1000000.f; + + // cpu speed is often way wrong, do a sanity check + if (freq < 10000.f && freq > 200.f ) + { + char tmp[MAX_STRING]; /* Flawfinder: ignore */ + snprintf(tmp, sizeof(tmp), " (%.0f Mhz)", freq); /* Flawfinder: ignore */ + + cpu_string.append(tmp); + } +#else + cpu_string.append("Can't get terse CPU information"); +#endif + + return cpu_string; +} + +void LLCPUInfo::stream(std::ostream& s) const +{ +#if LL_WINDOWS || LL_DARWIN + // gather machine information. + char proc_buf[CPUINFO_BUFFER_SIZE]; /* Flawfinder: ignore */ + CProcessor proc; + if(proc.CPUInfoToText(proc_buf, CPUINFO_BUFFER_SIZE)) + { + s << proc_buf; + } + else + { + s << "Unable to collect processor info"; + } +#else + // *FIX: This works on linux. What will it do on other systems? + FILE* cpuinfo = LLFile::fopen(CPUINFO_FILE, "r"); /* Flawfinder: ignore */ + if(cpuinfo) + { + char line[MAX_STRING]; /* Flawfinder: ignore */ + memset(line, 0, MAX_STRING); + while(fgets(line, MAX_STRING, cpuinfo)) + { + line[strlen(line)-1] = ' '; /*Flawfinder: ignore*/ + s << line; + } + fclose(cpuinfo); + } + else + { + s << "Unable to collect memory information"; + } +#endif +} + +LLMemoryInfo::LLMemoryInfo() +{ +} + +#if LL_LINUX +#include +#include +#endif + +U32 LLMemoryInfo::getPhysicalMemory() const +{ +#if LL_WINDOWS + MEMORYSTATUS state; + state.dwLength = sizeof(state); + GlobalMemoryStatus(&state); + + return (U32)state.dwTotalPhys; + +#elif LL_DARWIN + // This might work on Linux as well. Someone check... + unsigned int phys = 0; + int mib[2] = { CTL_HW, HW_PHYSMEM }; + + size_t len = sizeof(phys); + sysctl(mib, 2, &phys, &len, NULL, 0); + + return phys; +#elif LL_LINUX + + return getpagesize() * get_phys_pages(); + +#else + return 0; + +#endif +} + +void LLMemoryInfo::stream(std::ostream& s) const +{ +#if LL_WINDOWS + MEMORYSTATUS state; + state.dwLength = sizeof(state); + GlobalMemoryStatus(&state); + + s << "Percent Memory use: " << (U32)state.dwMemoryLoad << '%' << std::endl; + s << "Total Physical Kb: " << (U32)state.dwTotalPhys/1024 << std::endl; + s << "Avail Physical Kb: " << (U32)state.dwAvailPhys/1024 << std::endl; + s << "Total page Kb: " << (U32)state.dwTotalPageFile/1024 << std::endl; + s << "Avail page Kb: " << (U32)state.dwAvailPageFile/1024 << std::endl; + s << "Total Virtual Kb: " << (U32)state.dwTotalVirtual/1024 << std::endl; + s << "Avail Virtual Kb: " << (U32)state.dwAvailVirtual/1024 << std::endl; +#elif LL_DARWIN + U64 phys = 0; + + size_t len = sizeof(phys); + + if(sysctlbyname("hw.memsize", &phys, &len, NULL, 0) == 0) + { + s << "Total Physical Kb: " << phys/1024 << std::endl; + } + else + { + s << "Unable to collect memory information"; + } + +#else + // *FIX: This works on linux. What will it do on other systems? + FILE* meminfo = LLFile::fopen(MEMINFO_FILE,"r"); /* Flawfinder: ignore */ + if(meminfo) + { + char line[MAX_STRING]; /* Flawfinder: ignore */ + memset(line, 0, MAX_STRING); + while(fgets(line, MAX_STRING, meminfo)) + { + line[strlen(line)-1] = ' '; /*Flawfinder: ignore*/ + s << line; + } + fclose(meminfo); + } + else + { + s << "Unable to collect memory information"; + } +#endif +} + +std::ostream& operator<<(std::ostream& s, const LLOSInfo& info) +{ + info.stream(s); + return s; +} + +std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info) +{ + info.stream(s); + return s; +} + +std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info) +{ + info.stream(s); + return s; +} + +BOOL gunzip_file(const char *srcfile, const char *dstfile) +{ + char tmpfile[LL_MAX_PATH]; /* Flawfinder: ignore */ + const S32 UNCOMPRESS_BUFFER_SIZE = 32768; + BOOL retval = FALSE; + gzFile src = NULL; + U8 buffer[UNCOMPRESS_BUFFER_SIZE]; + FILE *dst = NULL; + S32 bytes = 0; + (void *) strcpy(tmpfile, dstfile); /* Flawfinder: ignore */ + (void *) strncat(tmpfile, ".t", sizeof(tmpfile) - strlen(tmpfile) -1); /* Flawfinder: ignore */ + src = gzopen(srcfile, "rb"); + if (! src) goto err; + dst = LLFile::fopen(tmpfile, "wb"); /* Flawfinder: ignore */ + if (! dst) goto err; + do + { + bytes = gzread(src, buffer, UNCOMPRESS_BUFFER_SIZE); + fwrite(buffer, sizeof(U8), bytes, dst); + } while(gzeof(src) == 0); + fclose(dst); + dst = NULL; + if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */ + retval = TRUE; +err: + if (src != NULL) gzclose(src); + if (dst != NULL) fclose(dst); + return retval; +} + +BOOL gzip_file(const char *srcfile, const char *dstfile) +{ + const S32 COMPRESS_BUFFER_SIZE = 32768; + char tmpfile[LL_MAX_PATH]; /* Flawfinder: ignore */ + BOOL retval = FALSE; + U8 buffer[COMPRESS_BUFFER_SIZE]; + gzFile dst = NULL; + FILE *src = NULL; + S32 bytes = 0; + (void *) strcpy(tmpfile, dstfile); /* Flawfinder: ignore */ + (void *) strncat(tmpfile, ".t", sizeof(tmpfile) - strlen(tmpfile) -1); /* Flawfinder: ignore */ + dst = gzopen(tmpfile, "wb"); /* Flawfinder: ignore */ + if (! dst) goto err; + src = LLFile::fopen(srcfile, "rb"); /* Flawfinder: ignore */ + if (! src) goto err; + + do + { + bytes = (S32)fread(buffer, sizeof(U8), COMPRESS_BUFFER_SIZE,src); + gzwrite(dst, buffer, bytes); + } while(feof(src) == 0); + gzclose(dst); + dst = NULL; +#if LL_WINDOWS + // Rename in windows needs the dstfile to not exist. + LLFile::remove(dstfile); +#endif + if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */ + retval = TRUE; + err: + if (src != NULL) fclose(src); + if (dst != NULL) gzclose(dst); + return retval; +} diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h new file mode 100644 index 0000000000..05c975a5fa --- /dev/null +++ b/indra/llcommon/llsys.h @@ -0,0 +1,91 @@ +/** + * @file llsys.h + * @brief System information debugging classes. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_SYS_H +#define LL_SYS_H + +// +// The LLOSInfo, LLCPUInfo, and LLMemoryInfo classes are essentially +// the same, but query different machine subsystems. Here's how you +// use an LLCPUInfo object: +// +// LLCPUInfo info; +// llinfos << info << llendl; +// + +#include +#include + +class LLOSInfo +{ +public: + LLOSInfo(); + void stream(std::ostream& s) const; + + const std::string& getOSString() const; + + S32 mMajorVer; + S32 mMinorVer; + S32 mBuild; + +#ifndef LL_WINDOWS + static S32 getMaxOpenFiles(); +#endif + + static U32 getProcessVirtualSizeKB(); + static U32 getProcessResidentSizeKB(); +private: + std::string mOSString; +}; + + +class LLCPUInfo +{ +public: + LLCPUInfo(); + void stream(std::ostream& s) const; + + std::string getCPUString() const; + std::string getCPUStringTerse() const; + + BOOL hasSSE() const { return mHasSSE; } + BOOL hasSSE2() const { return mHasSSE2; } + S32 getMhz() const { return mCPUMhz; } + + // Family is "AMD Duron" or "Intel Pentium Pro" + const std::string& getFamily() const { return mFamily; } + +private: + BOOL mHasSSE; + BOOL mHasSSE2; + S32 mCPUMhz; + std::string mFamily; +}; + +class LLMemoryInfo +{ +public: + LLMemoryInfo(); + void stream(std::ostream& s) const; + + U32 getPhysicalMemory() const; +}; + + +std::ostream& operator<<(std::ostream& s, const LLOSInfo& info); +std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info); +std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info); + +// gunzip srcfile into dstfile. Returns FALSE on error. +BOOL gunzip_file(const char *srcfile, const char *dstfile); +// gzip srcfile into dstfile. Returns FALSE on error. +BOOL gzip_file(const char *srcfile, const char *dstfile); + +extern LLCPUInfo gSysCPU; + +#endif // LL_LLSYS_H diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp new file mode 100644 index 0000000000..bd2dd7c8f5 --- /dev/null +++ b/indra/llcommon/llthread.cpp @@ -0,0 +1,330 @@ +/** + * @file llthread.cpp + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llapr.h" + +#include "llthread.h" + +#include "lltimer.h" + +#if LL_LINUX +#include +#endif + +//---------------------------------------------------------------------------- +// Usage: +// void run_func(LLThread* thread) +// { +// } +// LLThread* thread = new LLThread(); +// thread->run(run_func); +// ... +// thread->setQuitting(); +// while(!timeout) +// { +// if (thread->isStopped()) +// { +// delete thread; +// break; +// } +// } +// +//---------------------------------------------------------------------------- + +// +// Handed to the APR thread creation function +// +void *APR_THREAD_FUNC LLThread::staticRun(apr_thread_t *apr_threadp, void *datap) +{ + LLThread *threadp = (LLThread *)datap; + + // Set thread state to running + threadp->mStatus = RUNNING; + + // Run the user supplied function + threadp->run(); + + llinfos << "LLThread::staticRun() Exiting: " << threadp->mName << llendl; + + // We're done with the run function, this thread is done executing now. + threadp->mStatus = STOPPED; + + return NULL; +} + + +LLThread::LLThread(const std::string& name, apr_pool_t *poolp) : + mPaused(FALSE), + mName(name), + mAPRThreadp(NULL), + mStatus(STOPPED) +{ + // Thread creation probably CAN be paranoid about APR being initialized, if necessary + if (poolp) + { + mIsLocalPool = FALSE; + mAPRPoolp = poolp; + } + else + { + mIsLocalPool = TRUE; + apr_pool_create(&mAPRPoolp, NULL); // Create a subpool for this thread + } + mRunCondition = new LLCondition(mAPRPoolp); +} + + +LLThread::~LLThread() +{ + // Warning! If you somehow call the thread destructor from itself, + // the thread will die in an unclean fashion! + if (mAPRThreadp) + { + if (!isStopped()) + { + // The thread isn't already stopped + // First, set the flag that indicates that we're ready to die + setQuitting(); + + llinfos << "LLThread::~LLThread() Killing thread " << mName << " Status: " << mStatus << llendl; + // Now wait a bit for the thread to exit + // It's unclear whether I should even bother doing this - this destructor + // should netver get called unless we're already stopped, really... + S32 counter = 0; + const S32 MAX_WAIT = 600; + while (counter < MAX_WAIT) + { + if (isStopped()) + { + break; + } + // Sleep for a tenth of a second + ms_sleep(100); + yield(); + counter++; + } + } + + if (!isStopped()) + { + // This thread just wouldn't stop, even though we gave it time + llwarns << "LLThread::~LLThread() exiting thread before clean exit!" << llendl; + return; + } + mAPRThreadp = NULL; + } + + delete mRunCondition; + + if (mIsLocalPool) + { + apr_pool_destroy(mAPRPoolp); + } +} + + +void LLThread::start() +{ + apr_thread_create(&mAPRThreadp, NULL, staticRun, (void *)this, mAPRPoolp); + + // We won't bother joining + apr_thread_detach(mAPRThreadp); +} + +//============================================================================ +// Called from MAIN THREAD. + +// Request that the thread pause/resume. +// The thread will pause when (and if) it calls checkPause() +void LLThread::pause() +{ + if (!mPaused) + { + // this will cause the thread to stop execution as soon as checkPause() is called + mPaused = 1; // Does not need to be atomic since this is only set/unset from the main thread + } +} + +void LLThread::unpause() +{ + if (mPaused) + { + mPaused = 0; + } + + wake(); // wake up the thread if necessary +} + +// virtual predicate function -- returns true if the thread should wake up, false if it should sleep. +bool LLThread::runCondition(void) +{ + // by default, always run. Handling of pause/unpause is done regardless of this function's result. + return true; +} + +//============================================================================ +// Called from run() (CHILD THREAD). +// Stop thread execution if requested until unpaused. +void LLThread::checkPause() +{ + mRunCondition->lock(); + + // This is in a while loop because the pthread API allows for spurious wakeups. + while(shouldSleep()) + { + mRunCondition->wait(); // unlocks mRunCondition + // mRunCondition is locked when the thread wakes up + } + + mRunCondition->unlock(); +} + +//============================================================================ + +bool LLThread::isQuitting() const +{ + return (QUITTING == mStatus); +} + + +bool LLThread::isStopped() const +{ + return (STOPPED == mStatus); +} + + +void LLThread::setQuitting() +{ + mRunCondition->lock(); + if (mStatus == RUNNING) + { + mStatus = QUITTING; + } + mRunCondition->unlock(); + wake(); +} + + +// static +void LLThread::yield() +{ +#if LL_LINUX + sched_yield(); // annoyingly, apr_thread_yield is a noop on linux... +#else + apr_thread_yield(); +#endif +} + +void LLThread::wake() +{ + mRunCondition->lock(); + if(!shouldSleep()) + { + mRunCondition->signal(); + } + mRunCondition->unlock(); +} + +void LLThread::wakeLocked() +{ + if(!shouldSleep()) + { + mRunCondition->signal(); + } +} + +//============================================================================ + +LLMutex::LLMutex(apr_pool_t *poolp) : + mAPRMutexp(NULL) +{ + if (poolp) + { + mIsLocalPool = FALSE; + mAPRPoolp = poolp; + } + else + { + mIsLocalPool = TRUE; + apr_pool_create(&mAPRPoolp, NULL); // Create a subpool for this thread + } + apr_thread_mutex_create(&mAPRMutexp, APR_THREAD_MUTEX_DEFAULT, mAPRPoolp); +} + + +LLMutex::~LLMutex() +{ +#if _DEBUG + llassert(!isLocked()); // better not be locked! +#endif + apr_thread_mutex_destroy(mAPRMutexp); + mAPRMutexp = NULL; + if (mIsLocalPool) + { + apr_pool_destroy(mAPRPoolp); + } +} + + +void LLMutex::lock() +{ + apr_thread_mutex_lock(mAPRMutexp); +} + +void LLMutex::unlock() +{ + apr_thread_mutex_unlock(mAPRMutexp); +} + +bool LLMutex::isLocked() +{ + apr_status_t status = apr_thread_mutex_trylock(mAPRMutexp); + if (APR_STATUS_IS_EBUSY(status)) + { + return true; + } + else + { + apr_thread_mutex_unlock(mAPRMutexp); + return false; + } +} + +//============================================================================ + +LLCondition::LLCondition(apr_pool_t *poolp) : + LLMutex(poolp) +{ + // base class (LLMutex) has already ensured that mAPRPoolp is set up. + + apr_thread_cond_create(&mAPRCondp, mAPRPoolp); +} + + +LLCondition::~LLCondition() +{ + apr_thread_cond_destroy(mAPRCondp); + mAPRCondp = NULL; +} + + +void LLCondition::wait() +{ + apr_thread_cond_wait(mAPRCondp, mAPRMutexp); +} + +void LLCondition::signal() +{ + apr_thread_cond_signal(mAPRCondp); +} + +void LLCondition::broadcast() +{ + apr_thread_cond_broadcast(mAPRCondp); +} + diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h new file mode 100644 index 0000000000..f6f6bd210a --- /dev/null +++ b/indra/llcommon/llthread.h @@ -0,0 +1,164 @@ +/** + * @file llthread.h + * @brief Base classes for thread, mutex and condition handling. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTHREAD_H +#define LL_LLTHREAD_H + +#include "llapr.h" +#include "llapp.h" + +#include "apr-1/apr_thread_cond.h" + +class LLThread; +class LLMutex; +class LLCondition; + +class LLThread +{ +public: + typedef enum e_thread_status + { + STOPPED = 0, // The thread is not running. Not started, or has exited its run function + RUNNING = 1, // The thread is currently running + QUITTING= 2 // Someone wants this thread to quit + } EThreadStatus; + + LLThread(const std::string& name, apr_pool_t *poolp = NULL); + virtual ~LLThread(); // Warning! You almost NEVER want to destroy a thread unless it's in the STOPPED state. + + static void yield(); // Static because it can be called by the main thread, which doesn't have an LLThread data structure. + + + bool isQuitting() const; + bool isStopped() const; + + // PAUSE / RESUME functionality. See source code for important usage notes. +public: + // Called from MAIN THREAD. + void pause(); + void unpause(); + bool isPaused() { return mPaused ? true : false; } + + // Cause the thread to wake up and check its condition + void wake(); + + // Same as above, but to be used when the condition is already locked. + void wakeLocked(); + + // Called from run() (CHILD THREAD). Pause the thread if requested until unpaused. + void checkPause(); + + // this kicks off the apr thread + void start(void); + + apr_pool_t *getAPRPool() { return mAPRPoolp; } + +private: + BOOL mPaused; + + // static function passed to APR thread creation routine + static void *APR_THREAD_FUNC staticRun(apr_thread_t *apr_threadp, void *datap); + +protected: + std::string mName; + LLCondition* mRunCondition; + + apr_thread_t *mAPRThreadp; + apr_pool_t *mAPRPoolp; + BOOL mIsLocalPool; + EThreadStatus mStatus; + + void setQuitting(); + + // virtual function overridden by subclass -- this will be called when the thread runs + virtual void run(void) = 0; + + // virtual predicate function -- returns true if the thread should wake up, false if it should sleep. + virtual bool runCondition(void); + + // Lock/Unlock Run Condition -- use around modification of any variable used in runCondition() + inline void lockData(); + inline void unlockData(); + + // This is the predicate that decides whether the thread should sleep. + // It should only be called with mRunCondition locked, since the virtual runCondition() function may need to access + // data structures that are thread-unsafe. + bool shouldSleep(void) { return (mStatus == RUNNING) && (isPaused() || (!runCondition())); } + + // To avoid spurious signals (and the associated context switches) when the condition may or may not have changed, you can do the following: + // mRunCondition->lock(); + // if(!shouldSleep()) + // mRunCondition->signal(); + // mRunCondition->unlock(); +}; + +//============================================================================ + +class LLMutex +{ +public: + LLMutex(apr_pool_t *apr_poolp); // Defaults to global pool, could use the thread pool as well. + ~LLMutex(); + + void lock(); // blocks + void unlock(); + bool isLocked(); // non-blocking, but does do a lock/unlock so not free + +protected: + apr_thread_mutex_t *mAPRMutexp; + apr_pool_t *mAPRPoolp; + BOOL mIsLocalPool; +}; + +// Actually a condition/mutex pair (since each condition needs to be associated with a mutex). +class LLCondition : public LLMutex +{ +public: + LLCondition(apr_pool_t *apr_poolp); // Defaults to global pool, could use the thread pool as well. + ~LLCondition(); + + void wait(); // blocks + void signal(); + void broadcast(); + +protected: + apr_thread_cond_t *mAPRCondp; +}; + +class LLMutexLock +{ +public: + LLMutexLock(LLMutex* mutex) + { + mMutex = mutex; + mMutex->lock(); + } + ~LLMutexLock() + { + mMutex->unlock(); + } +private: + LLMutex* mMutex; +}; + +//============================================================================ + +void LLThread::lockData() +{ + mRunCondition->lock(); +} + +void LLThread::unlockData() +{ + mRunCondition->unlock(); +} + + +//============================================================================ + +#endif // LL_LLTHREAD_H diff --git a/indra/llcommon/lltimer.cpp b/indra/llcommon/lltimer.cpp new file mode 100644 index 0000000000..bd054f02d8 --- /dev/null +++ b/indra/llcommon/lltimer.cpp @@ -0,0 +1,517 @@ +/** + * @file lltimer.cpp + * @brief Cross-platform objects for doing timing + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + + +#if LL_WINDOWS +#include + +#elif LL_LINUX +#include +#include +#include + +#elif LL_DARWIN +# include +# include +#else +# error "architecture not supported" +#endif + + +#include "lltimer.h" +#include "u64.h" + +// +// Locally used constants +// +const U32 SEC_PER_DAY = 86400; +const F64 SEC_TO_MICROSEC = 1000000.f; +const U64 SEC_TO_MICROSEC_U64 = 1000000; +const F64 USEC_TO_SEC_F64 = 0.000001; + + +//--------------------------------------------------------------------------- +// Globals and statics +//--------------------------------------------------------------------------- + +S32 gUTCOffset = 0; // viewer's offset from server UTC, in seconds +LLTimer* LLTimer::sTimer = NULL; + +F64 gClockFrequency = 0.0; +F64 gClockFrequencyInv = 0.0; +F64 gClocksToMicroseconds = 0.0; +U64 gTotalTimeClockCount = 0; +U64 gLastTotalTimeClockCount = 0; + +// +// Forward declarations +// + + +//--------------------------------------------------------------------------- +// Implementation +//--------------------------------------------------------------------------- + +#if LL_WINDOWS +void ms_sleep(long ms) +{ + Sleep((U32)ms); +} + +void llyield() +{ + SleepEx(0, TRUE); // Relinquishes time slice to any thread of equal priority, can be woken up by extended IO functions +} +#elif LL_LINUX +void ms_sleep(long ms) +{ + struct timespec t; + t.tv_sec = ms / 1000; + t.tv_nsec = (ms % 1000) * 1000000l; + nanosleep(&t, NULL); +} + +void llyield() +{ + sched_yield(); +} +#elif LL_DARWIN +void ms_sleep(long ms) +{ + struct timespec t; + t.tv_sec = ms / 1000; + t.tv_nsec = (ms % 1000) * 1000000l; + nanosleep(&t, NULL); +} + +void llyield() +{ +// sched_yield(); +} +#else +# error "architecture not supported" +#endif + +// +// CPU clock/other clock frequency and count functions +// + +#if LL_WINDOWS +U64 get_clock_count() +{ + static bool firstTime = true; + static U64 offset; + // ensures that callers to this function never have to deal with wrap + + // QueryPerformanceCounter implementation + LARGE_INTEGER clock_count; + QueryPerformanceCounter(&clock_count); + if (firstTime) { + offset = clock_count.QuadPart; + firstTime = false; + } + return clock_count.QuadPart - offset; +} + +F64 calc_clock_frequency(U32 uiMeasureMSecs) +{ + __int64 freq; + QueryPerformanceFrequency((LARGE_INTEGER *) &freq); + return (F64)freq; +} +#endif // LL_WINDOWS + + +#if LL_LINUX || LL_DARWIN +// Both Linux and Mac use gettimeofday for accurate time +F64 calc_clock_frequency(unsigned int uiMeasureMSecs) +{ + return 1000000.0; // microseconds, so 1 Mhz. +} + +U64 get_clock_count() +{ + // Linux clocks are in microseconds + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec*SEC_TO_MICROSEC_U64 + tv.tv_usec; +} +#endif + + +void update_clock_frequencies() +{ + gClockFrequency = calc_clock_frequency(50); + gClockFrequencyInv = 1.0/gClockFrequency; + gClocksToMicroseconds = gClockFrequencyInv * SEC_TO_MICROSEC; +} + + +/////////////////////////////////////////////////////////////////////////////// + +// returns a U64 number that represents the number of +// microseconds since the unix epoch - Jan 1, 1970 +U64 totalTime() +{ + U64 current_clock_count = get_clock_count(); + if (!gTotalTimeClockCount) + { + update_clock_frequencies(); + gTotalTimeClockCount = current_clock_count; + +#if LL_WINDOWS + // Synch us up with local time (even though we PROBABLY don't need to, this is how it was implemented) + // Unix platforms use gettimeofday so they are synced, although this probably isn't a good assumption to + // make in the future. + + gTotalTimeClockCount = (U64)(time(NULL) * gClockFrequency); +#endif + + // Update the last clock count + gLastTotalTimeClockCount = current_clock_count; + } + else + { + if (current_clock_count >= gLastTotalTimeClockCount) + { + // No wrapping, we're all okay. + gTotalTimeClockCount += current_clock_count - gLastTotalTimeClockCount; + } + else + { + // We've wrapped. Compensate correctly + gTotalTimeClockCount += (0xFFFFFFFFFFFFFFFFULL - gLastTotalTimeClockCount) + current_clock_count; + } + + // Update the last clock count + gLastTotalTimeClockCount = current_clock_count; + } + + // Return the total clock tick count in microseconds. + return (U64)(gTotalTimeClockCount*gClocksToMicroseconds); +} + + +/////////////////////////////////////////////////////////////////////////////// + +LLTimer::LLTimer() +{ + if (!gClockFrequency) + { + update_clock_frequencies(); + } + + mStarted = TRUE; + reset(); +} + +LLTimer::~LLTimer() +{ +} + +// static +U64 LLTimer::getTotalTime() +{ + // simply call into the implementation function. + return totalTime(); +} + +// static +F64 LLTimer::getTotalSeconds() +{ + return U64_to_F64(getTotalTime()) * USEC_TO_SEC_F64; +} + +void LLTimer::reset() +{ + mLastClockCount = get_clock_count(); + mExpirationTicks = 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +U64 LLTimer::getCurrentClockCount() +{ + return get_clock_count(); +} + +/////////////////////////////////////////////////////////////////////////////// + +void LLTimer::setLastClockCount(U64 current_count) +{ + mLastClockCount = current_count; +} + +/////////////////////////////////////////////////////////////////////////////// + +static +U64 getElapsedTimeAndUpdate(U64& lastClockCount) +{ + U64 current_clock_count = get_clock_count(); + U64 result; + + if (current_clock_count >= lastClockCount) + { + result = current_clock_count - lastClockCount; + } + else + { + // time has gone backward + result = 0; + } + + lastClockCount = current_clock_count; + + return result; +} + + +F64 LLTimer::getElapsedTimeF64() const +{ + U64 last = mLastClockCount; + return (F64)getElapsedTimeAndUpdate(last) * gClockFrequencyInv; +} + +F32 LLTimer::getElapsedTimeF32() const +{ + return (F32)getElapsedTimeF64(); +} + +F64 LLTimer::getElapsedTimeAndResetF64() +{ + return (F64)getElapsedTimeAndUpdate(mLastClockCount) * gClockFrequencyInv; +} + +F32 LLTimer::getElapsedTimeAndResetF32() +{ + return (F32)getElapsedTimeAndResetF64(); +} + +/////////////////////////////////////////////////////////////////////////////// + +void LLTimer::setTimerExpirySec(F32 expiration) +{ + mExpirationTicks = get_clock_count() + + (U64)((F32)(expiration * gClockFrequency)); +} + +F32 LLTimer::getRemainingTimeF32() +{ + U64 cur_ticks = get_clock_count(); + if (cur_ticks > mExpirationTicks) + { + return 0.0f; + } + return F32((mExpirationTicks - cur_ticks) * gClockFrequencyInv); +} + + +BOOL LLTimer::checkExpirationAndReset(F32 expiration) +{ + U64 cur_ticks = get_clock_count(); + if (cur_ticks < mExpirationTicks) + { + return FALSE; + } + + mExpirationTicks = cur_ticks + + (U64)((F32)(expiration * gClockFrequency)); + return TRUE; +} + + +BOOL LLTimer::hasExpired() +{ + return (get_clock_count() >= mExpirationTicks) + ? TRUE : FALSE; +} + +/////////////////////////////////////////////////////////////////////////////// + +BOOL LLTimer::knownBadTimer() +{ + BOOL failed = FALSE; + +#if LL_WINDOWS + WCHAR bad_pci_list[][10] = {L"1039:0530", + L"1039:0620", + L"10B9:0533", + L"10B9:1533", + L"1106:0596", + L"1106:0686", + L"1166:004F", + L"1166:0050", + L"8086:7110", + L"\0" + }; + + HKEY hKey = NULL; + LONG nResult = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE,L"SYSTEM\\CurrentControlSet\\Enum\\PCI", 0, + KEY_EXECUTE | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &hKey); + + WCHAR name[1024]; + DWORD name_len = 1024; + FILETIME scrap; + + S32 key_num = 0; + WCHAR pci_id[10]; + + wcscpy(pci_id, L"0000:0000"); /*Flawfinder: ignore*/ + + while (nResult == ERROR_SUCCESS) + { + nResult = ::RegEnumKeyEx(hKey, key_num++, name, &name_len, NULL, NULL, NULL, &scrap); + + if (nResult == ERROR_SUCCESS) + { + memcpy(&pci_id[0],&name[4],4); /* Flawfinder: ignore */ + memcpy(&pci_id[5],&name[13],4); /* Flawfinder: ignore */ + + for (S32 check = 0; bad_pci_list[check][0]; check++) + { + if (!wcscmp(pci_id, bad_pci_list[check])) + { +// llwarns << "unreliable PCI chipset found!! " << pci_id << endl; + failed = TRUE; + break; + } + } +// llinfo << "PCI chipset found: " << pci_id << endl; + name_len = 1024; + } + } +#endif + return(failed); +} + +/////////////////////////////////////////////////////////////////////////////// +// +// NON-MEMBER FUNCTIONS +// +/////////////////////////////////////////////////////////////////////////////// + +U32 time_corrected() +{ + U32 corrected_time = (U32)time(NULL) + gUTCOffset; + return corrected_time; +} + + +// Is the current computer (in its current time zone) +// observing daylight savings time? +BOOL is_daylight_savings() +{ + time_t now = time(NULL); + + // Internal buffer to local server time + struct tm* internal_time = localtime(&now); + + // tm_isdst > 0 => daylight savings + // tm_isdst = 0 => not daylight savings + // tm_isdst < 0 => can't tell + return (internal_time->tm_isdst > 0); +} + + +struct tm* utc_to_pacific_time(S32 utc_time, BOOL pacific_daylight_time) +{ + time_t unix_time = (time_t)utc_time; + + S32 pacific_offset_hours; + if (pacific_daylight_time) + { + pacific_offset_hours = -7; + } + else + { + pacific_offset_hours = -8; + } + + // We subtract off the PST/PDT offset _before_ getting + // "UTC" time, because this will handle wrapping around + // for 5 AM UTC -> 10 PM PDT of the previous day. + unix_time += pacific_offset_hours * MIN_PER_HOUR * SEC_PER_MIN; + + // Internal buffer to PST/PDT (see above) + struct tm* internal_time = gmtime(&unix_time); + + /* + // Don't do this, this won't correctly tell you if daylight savings is active in CA or not. + if (pacific_daylight_time) + { + internal_time->tm_isdst = 1; + } + */ + + return internal_time; +} + + +void microsecondsToTimecodeString(U64 current_time, char *tcstring) +{ + U64 hours; + U64 minutes; + U64 seconds; + U64 frames; + U64 subframes; + + hours = current_time / (U64)3600000000ul; + minutes = current_time / (U64)60000000; + minutes %= 60; + seconds = current_time / (U64)1000000; + seconds %= 60; + frames = current_time / (U64)41667; + frames %= 24; + subframes = current_time / (U64)42; + subframes %= 100; + + sprintf(tcstring,"%3.3d:%2.2d:%2.2d:%2.2d.%2.2d",(int)hours,(int)minutes,(int)seconds,(int)frames,(int)subframes); /* Flawfinder: ignore */ +} + + +void secondsToTimecodeString(F32 current_time, char *tcstring) +{ + microsecondsToTimecodeString((U64)((F64)(SEC_TO_MICROSEC*current_time)), tcstring); +} + + +////////////////////////////////////////////////////////////////////////////// +// +// LLEventTimer Implementation +// +////////////////////////////////////////////////////////////////////////////// + +std::list LLEventTimer::sActiveList; + +LLEventTimer::LLEventTimer(F32 period) +: mTimer() +{ + mPeriod = period; + sActiveList.push_back(this); +} + +LLEventTimer::~LLEventTimer() +{ + sActiveList.remove(this); +} + +void LLEventTimer::updateClass() +{ + for (std::list::iterator iter = sActiveList.begin(); iter != sActiveList.end(); ) + { + LLEventTimer* timer = *iter++; + F32 et = timer->mTimer.getElapsedTimeF32(); + if (et > timer->mPeriod) { + timer->mTimer.reset(); + timer->tick(); + } + } +} + diff --git a/indra/llcommon/lltimer.h b/indra/llcommon/lltimer.h new file mode 100644 index 0000000000..69e786c50a --- /dev/null +++ b/indra/llcommon/lltimer.h @@ -0,0 +1,142 @@ +/** + * @file lltimer.h + * @brief Cross-platform objects for doing timing + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_TIMER_H +#define LL_TIMER_H + +#if LL_LINUX || LL_DARWIN +# include +# include +#endif + +#include + +// units conversions +#ifndef USEC_PER_SEC + const U32 USEC_PER_SEC = 1000000; +#endif +const U32 SEC_PER_MIN = 60; +const U32 MIN_PER_HOUR = 60; +const U32 USEC_PER_MIN = USEC_PER_SEC * SEC_PER_MIN; +const U32 USEC_PER_HOUR = USEC_PER_MIN * MIN_PER_HOUR; +const U32 SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR; +const F64 SEC_PER_USEC = 1.0 / (F64) USEC_PER_SEC; + +class LLTimer +{ +public: + static LLTimer *sTimer; // global timer + +protected: + U64 mLastClockCount; + U64 mExpirationTicks; + BOOL mStarted; + +public: + LLTimer(); + ~LLTimer(); + + static void initClass() { if (!sTimer) sTimer = new LLTimer; } + static void cleanupClass() { delete sTimer; sTimer = NULL; } + + // Return a high precision number of seconds since the start of + // this application instance. + static F64 getElapsedSeconds() + { + return sTimer->getElapsedTimeF64(); + } + + // Return a high precision usec since epoch + static U64 getTotalTime(); + + // Return a high precision seconds since epoch + static F64 getTotalSeconds(); + + + // MANIPULATORS + void start() { reset(); mStarted = TRUE; } + void stop() { mStarted = FALSE; } + void reset(); // Resets the timer + void setLastClockCount(U64 current_count); // Sets the timer so that the next elapsed call will be relative to this time + void setTimerExpirySec(F32 expiration); + BOOL checkExpirationAndReset(F32 expiration); + BOOL hasExpired(); + F32 getElapsedTimeAndResetF32(); // Returns elapsed time in seconds with reset + F64 getElapsedTimeAndResetF64(); + + F32 getRemainingTimeF32(); + + static BOOL knownBadTimer(); + + // ACCESSORS + F32 getElapsedTimeF32() const; // Returns elapsed time in seconds + F64 getElapsedTimeF64() const; // Returns elapsed time in seconds + + BOOL getStarted() const { return mStarted; } + + + static U64 getCurrentClockCount(); // Returns the raw clockticks +}; + +// +// Various functions for initializing/accessing clock and timing stuff. Don't use these without REALLY knowing how they work. +// +U64 get_clock_count(); +F64 calc_clock_frequency(U32 msecs); +void update_clock_frequencies(); + + +// Sleep for milliseconds +void ms_sleep(long ms); + +// Yield +//void llyield(); // Yield your timeslice - not implemented yet for Mac, so commented out. + +// Returns the correct UTC time in seconds, like time(NULL). +// Useful on the viewer, which may have its local clock set wrong. +U32 time_corrected(); + +// Correction factor used by time_corrected() above. +extern S32 gUTCOffset; + +// Is the current computer (in its current time zone) +// observing daylight savings time? +BOOL is_daylight_savings(); + +// Converts internal "struct tm" time buffer to Pacific Standard/Daylight Time +// Usage: +// S32 utc_time; +// utc_time = time_corrected(); +// struct tm* internal_time = utc_to_pacific_time(utc_time, gDaylight); +struct tm* utc_to_pacific_time(S32 utc_time, BOOL pacific_daylight_time); + +void microsecondsToTimecodeString(U64 current_time, char *tcstring); +void secondsToTimecodeString(F32 current_time, char *tcstring); + +// class for scheduling a function to be called at a given frequency (approximate, inprecise) +class LLEventTimer +{ +public: + LLEventTimer(F32 period); // period is the amount of time between each call to tick() + virtual ~LLEventTimer(); + + //function to be called at the supplied frequency + virtual void tick() = 0; + + static void updateClass(); + +protected: + LLTimer mTimer; + F32 mPeriod; + +private: + //list of active timers + static std::list sActiveList; +}; + +#endif diff --git a/indra/llcommon/lluri.cpp b/indra/llcommon/lluri.cpp new file mode 100644 index 0000000000..1b66173399 --- /dev/null +++ b/indra/llcommon/lluri.cpp @@ -0,0 +1,579 @@ +/** + * @file lluri.cpp + * @author Phoenix + * @date 2006-02-08 + * @brief Implementation of the LLURI class. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "lluri.h" +#include "llsd.h" + +// uric = reserved | unreserved | escaped +// reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," +// unreserved = alphanum | mark +// mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" +// escaped = "%" hex hex +static const char* ESCAPED_CHARACTERS[256] = +{ + "%00", // 0 + "%01", // 1 + "%02", // 2 + "%03", // 3 + "%04", // 4 + "%05", // 5 + "%06", // 6 + "%07", // 7 + "%08", // 8 + "%09", // 9 + "%0a", // 10 + "%0b", // 11 + "%0c", // 12 + "%0d", // 13 + "%0e", // 14 + "%0f", // 15 + "%10", // 16 + "%11", // 17 + "%12", // 18 + "%13", // 19 + "%14", // 20 + "%15", // 21 + "%16", // 22 + "%17", // 23 + "%18", // 24 + "%19", // 25 + "%1a", // 26 + "%1b", // 27 + "%1c", // 28 + "%1d", // 29 + "%1e", // 30 + "%1f", // 31 + "%20", // 32 + "!", // 33 + "%22", // 34 + "%23", // 35 + "$", // 36 + "%25", // 37 + "&", // 38 + "'", // 39 + "(", // 40 + ")", // 41 + "*", // 42 + "+", // 43 + ",", // 44 + "-", // 45 + ".", // 46 + "/", // 47 + "0", // 48 + "1", // 49 + "2", // 50 + "3", // 51 + "4", // 52 + "5", // 53 + "6", // 54 + "7", // 55 + "8", // 56 + "9", // 57 + ":", // 58 + ";", // 59 + "%3c", // 60 + "=", // 61 + "%3e", // 62 + "?", // 63 + "@", // 64 + "A", // 65 + "B", // 66 + "C", // 67 + "D", // 68 + "E", // 69 + "F", // 70 + "G", // 71 + "H", // 72 + "I", // 73 + "J", // 74 + "K", // 75 + "L", // 76 + "M", // 77 + "N", // 78 + "O", // 79 + "P", // 80 + "Q", // 81 + "R", // 82 + "S", // 83 + "T", // 84 + "U", // 85 + "V", // 86 + "W", // 87 + "X", // 88 + "Y", // 89 + "Z", // 90 + "%5b", // 91 + "%5c", // 92 + "%5d", // 93 + "%5e", // 94 + "_", // 95 + "%60", // 96 + "a", // 97 + "b", // 98 + "c", // 99 + "d", // 100 + "e", // 101 + "f", // 102 + "g", // 103 + "h", // 104 + "i", // 105 + "j", // 106 + "k", // 107 + "l", // 108 + "m", // 109 + "n", // 110 + "o", // 111 + "p", // 112 + "q", // 113 + "r", // 114 + "s", // 115 + "t", // 116 + "u", // 117 + "v", // 118 + "w", // 119 + "x", // 120 + "y", // 121 + "z", // 122 + "%7b", // 123 + "%7c", // 124 + "%7d", // 125 + "~", // 126 + "%7f", // 127 + "%80", // 128 + "%81", // 129 + "%82", // 130 + "%83", // 131 + "%84", // 132 + "%85", // 133 + "%86", // 134 + "%87", // 135 + "%88", // 136 + "%89", // 137 + "%8a", // 138 + "%8b", // 139 + "%8c", // 140 + "%8d", // 141 + "%8e", // 142 + "%8f", // 143 + "%90", // 144 + "%91", // 145 + "%92", // 146 + "%93", // 147 + "%94", // 148 + "%95", // 149 + "%96", // 150 + "%97", // 151 + "%98", // 152 + "%99", // 153 + "%9a", // 154 + "%9b", // 155 + "%9c", // 156 + "%9d", // 157 + "%9e", // 158 + "%9f", // 159 + "%a0", // 160 + "%a1", // 161 + "%a2", // 162 + "%a3", // 163 + "%a4", // 164 + "%a5", // 165 + "%a6", // 166 + "%a7", // 167 + "%a8", // 168 + "%a9", // 169 + "%aa", // 170 + "%ab", // 171 + "%ac", // 172 + "%ad", // 173 + "%ae", // 174 + "%af", // 175 + "%b0", // 176 + "%b1", // 177 + "%b2", // 178 + "%b3", // 179 + "%b4", // 180 + "%b5", // 181 + "%b6", // 182 + "%b7", // 183 + "%b8", // 184 + "%b9", // 185 + "%ba", // 186 + "%bb", // 187 + "%bc", // 188 + "%bd", // 189 + "%be", // 190 + "%bf", // 191 + "%c0", // 192 + "%c1", // 193 + "%c2", // 194 + "%c3", // 195 + "%c4", // 196 + "%c5", // 197 + "%c6", // 198 + "%c7", // 199 + "%c8", // 200 + "%c9", // 201 + "%ca", // 202 + "%cb", // 203 + "%cc", // 204 + "%cd", // 205 + "%ce", // 206 + "%cf", // 207 + "%d0", // 208 + "%d1", // 209 + "%d2", // 210 + "%d3", // 211 + "%d4", // 212 + "%d5", // 213 + "%d6", // 214 + "%d7", // 215 + "%d8", // 216 + "%d9", // 217 + "%da", // 218 + "%db", // 219 + "%dc", // 220 + "%dd", // 221 + "%de", // 222 + "%df", // 223 + "%e0", // 224 + "%e1", // 225 + "%e2", // 226 + "%e3", // 227 + "%e4", // 228 + "%e5", // 229 + "%e6", // 230 + "%e7", // 231 + "%e8", // 232 + "%e9", // 233 + "%ea", // 234 + "%eb", // 235 + "%ec", // 236 + "%ed", // 237 + "%ee", // 238 + "%ef", // 239 + "%f0", // 240 + "%f1", // 241 + "%f2", // 242 + "%f3", // 243 + "%f4", // 244 + "%f5", // 245 + "%f6", // 246 + "%f7", // 247 + "%f8", // 248 + "%f9", // 249 + "%fa", // 250 + "%fb", // 251 + "%fc", // 252 + "%fd", // 253 + "%fe", // 254 + "%ff" // 255 +}; + +LLURI::LLURI() +{ +} + +LLURI::LLURI(const std::string& escaped_str) +{ + std::string::size_type delim_pos, delim_pos2; + delim_pos = escaped_str.find(':'); + std::string temp; + if (delim_pos == std::string::npos) + { + mScheme = ""; + mEscapedOpaque = escaped_str; + } + else + { + mScheme = escaped_str.substr(0, delim_pos); + mEscapedOpaque = escaped_str.substr(delim_pos+1); + } + + if (mScheme == "http" || mScheme == "https" || mScheme == "ftp") + { + if (mEscapedOpaque.substr(0,2) != "//") + { + return; + } + + delim_pos = mEscapedOpaque.find('/', 2); + delim_pos2 = mEscapedOpaque.find('?', 2); + // no path, no query + if (delim_pos == std::string::npos && + delim_pos2 == std::string::npos) + { + mEscapedAuthority = mEscapedOpaque.substr(2); + mEscapedPath = ""; + } + // path exist, no query + else if (delim_pos2 == std::string::npos) + { + mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2); + mEscapedPath = mEscapedOpaque.substr(delim_pos); + } + // no path, only query + else if (delim_pos == std::string::npos || + delim_pos2 < delim_pos) + { + mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos2-2); + // query part will be broken out later + mEscapedPath = mEscapedOpaque.substr(delim_pos2); + } + // path and query + else + { + mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2); + // query part will be broken out later + mEscapedPath = mEscapedOpaque.substr(delim_pos); + } + } + + delim_pos = mEscapedPath.find('?'); + if (delim_pos != std::string::npos) + { + mEscapedQuery = mEscapedPath.substr(delim_pos+1); + mEscapedPath = mEscapedPath.substr(0,delim_pos); + } +} + +LLURI::~LLURI() +{ +} + + +LLURI LLURI::buildHTTP(const std::string& host_port, + const LLSD& path) +{ + LLURI result; + result.mScheme = "HTTP"; + // TODO: deal with '/' '?' '#' in host_port + result.mEscapedAuthority = "//" + escape(host_port); + if (path.isArray()) + { + // break out and escape each path component + for (LLSD::array_const_iterator it = path.beginArray(); + it != path.endArray(); + ++it) + { + lldebugs << "PATH: inserting " << it->asString() << llendl; + result.mEscapedPath += "/" + escape(it->asString()); + } + } + result.mEscapedOpaque = result.mEscapedAuthority + + result.mEscapedPath; + return result; +} + +// static +LLURI LLURI::buildHTTP(const std::string& host_port, + const LLSD& path, + const LLSD& query) +{ + LLURI result = buildHTTP(host_port, path); + // break out and escape each query component + if (query.isMap()) + { + for (LLSD::map_const_iterator it = query.beginMap(); + it != query.endMap(); + it++) + { + result.mEscapedQuery += escape(it->first) + + (it->second.isUndefined() ? "" : "=" + it->second.asString()) + + "&"; + } + if (query.size() > 0) + { + result.mEscapedOpaque += "?" + result.mEscapedQuery; + } + } + return result; +} + +std::string LLURI::asString() const +{ + if (mScheme.empty()) + { + return mEscapedOpaque; + } + else + { + return mScheme + ":" + mEscapedOpaque; + } +} + +std::string LLURI::scheme() const +{ + return mScheme; +} + +std::string LLURI::opaque() const +{ + return unescape(mEscapedOpaque); +} + +std::string LLURI::authority() const +{ + return unescape(mEscapedAuthority); +} + + +namespace { + void findAuthorityParts(const std::string& authority, + std::string& user, + std::string& host, + std::string& port) + { + std::string::size_type start_pos = authority.find('@'); + if (start_pos == std::string::npos) + { + user = ""; + start_pos = 0; + } + else + { + user = authority.substr(0, start_pos); + start_pos += 1; + } + + std::string::size_type end_pos = authority.find(':', start_pos); + if (end_pos == std::string::npos) + { + host = authority.substr(start_pos); + port = ""; + } + else + { + host = authority.substr(start_pos, end_pos - start_pos); + port = authority.substr(end_pos + 1); + } + } +} + +std::string LLURI::hostName() const +{ + std::string user, host, port; + findAuthorityParts(mEscapedAuthority, user, host, port); + return unescape(host); +} + +U16 LLURI::hostPort() const +{ + std::string user, host, port; + findAuthorityParts(mEscapedAuthority, user, host, port); + if (port.empty()) + { + if (mScheme == "http") + return 80; + if (mScheme == "https") + return 443; + if (mScheme == "ftp") + return 21; + return 0; + } + return atoi(port.c_str()); +} + +std::string LLURI::path() const +{ + return unescape(mEscapedPath); +} + +std::string LLURI::query() const +{ + return unescape(mEscapedQuery); +} + +LLSD LLURI::queryMap() const +{ + return queryMap(mEscapedQuery); +} + +// static +LLSD LLURI::queryMap(std::string escaped_query_string) +{ + lldebugs << "LLURI::queryMap query params: " << escaped_query_string << llendl; + + LLSD result = LLSD::emptyArray(); + while(!escaped_query_string.empty()) + { + // get tuple first + std::string tuple; + std::string::size_type tuple_begin = escaped_query_string.find('&'); + if (tuple_begin != std::string::npos) + { + tuple = escaped_query_string.substr(0, tuple_begin); + escaped_query_string = escaped_query_string.substr(tuple_begin+1); + } + else + { + tuple = escaped_query_string; + escaped_query_string = ""; + } + if (tuple.empty()) continue; + + // parse tuple + std::string::size_type key_end = tuple.find('='); + if (key_end != std::string::npos) + { + std::string key = unescape(tuple.substr(0,key_end)); + std::string value = unescape(tuple.substr(key_end+1)); + lldebugs << "inserting key " << key << " value " << value << llendl; + result[key] = value; + } + else + { + lldebugs << "inserting key " << unescape(tuple) << " value true" << llendl; + result[unescape(tuple)] = true; + } + } + return result; +} + +// static +std::string LLURI::escape(const std::string& str) +{ + std::ostringstream ostr; + std::string::const_iterator it = str.begin(); + std::string::const_iterator end = str.end(); + S32 c; + for(; it != end; ++it) + { + c = (S32)(*it); + ostr << ESCAPED_CHARACTERS[c]; + } + return ostr.str(); +} + +// static +std::string LLURI::unescape(const std::string& str) +{ + std::ostringstream ostr; + std::string::const_iterator it = str.begin(); + std::string::const_iterator end = str.end(); + for(; it != end; ++it) + { + if((*it) == '%') + { + ++it; + if(it == end) break; + U8 c = hex_as_nybble(*it++); + c = c << 4; + if (it == end) break; + c |= hex_as_nybble(*it); + ostr.put((char)c); + } + else + { + ostr.put(*it); + } + } + return ostr.str(); +} diff --git a/indra/llcommon/lluri.h b/indra/llcommon/lluri.h new file mode 100644 index 0000000000..3fc62aeb98 --- /dev/null +++ b/indra/llcommon/lluri.h @@ -0,0 +1,72 @@ +/** + * @file lluri.h + * @author Phoenix + * @date 2006-02-05 + * @brief Declaration of the URI class. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLURI_H +#define LL_LLURI_H + +#include + +class LLSD; + +/** + * + * LLURI instances are immutable + * See: http://www.ietf.org/rfc/rfc3986.txt + * + */ +class LLURI +{ +public: + LLURI(); + LLURI(const std::string& escaped_str); + // construct from escaped string, as would be transmitted on the net + + ~LLURI(); + + static LLURI buildHTTP(const std::string& host_port, + const LLSD& path); + static LLURI buildHTTP(const std::string& host_port, + const LLSD& path, + const LLSD& query); + + std::string asString() const; + // the whole URI, escaped as needed + + // Parts of a URI + // These functions return parts of the decoded URI. The returned + // strings are un-escaped as needed + + // for all schemes + std::string scheme() const; // ex.: "http", note lack of colon + std::string opaque() const; // everything after the colon + + // for schemes that follow path like syntax (http, https, ftp) + std::string authority() const; // ex.: "bob@host.com:80" + std::string hostName() const; // ex.: "host.com" + U16 hostPort() const; // ex.: 80, will include implicit port + std::string path() const; // ex.: "/abc/def", includes leading slash +// LLSD pathArray() const; // above decoded into an array of strings + std::string query() const; // ex.: "x=34", section after "?" + LLSD queryMap() const; // above decoded into a map + static LLSD queryMap(std::string escaped_query_string); + + // Escaping Utilities + static std::string escape(const std::string& str); + static std::string unescape(const std::string& str); + +private: + std::string mScheme; + std::string mEscapedOpaque; + std::string mEscapedAuthority; + std::string mEscapedPath; + std::string mEscapedQuery; +}; + +#endif // LL_LLURI_H diff --git a/indra/llcommon/lluuidhashmap.h b/indra/llcommon/lluuidhashmap.h new file mode 100644 index 0000000000..f7d32b1fe0 --- /dev/null +++ b/indra/llcommon/lluuidhashmap.h @@ -0,0 +1,557 @@ +/** + * @file lluuidhashmap.h + * @brief A uuid based hash map. + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLUUIDHASHMAP_H +#define LL_LLUUIDHASHMAP_H + +#include "stdtypes.h" +#include "llerror.h" +#include "lluuid.h" + +// UUID hash map + + /* + LLUUIDHashMap foo(test_equals); + LLUUIDHashMapIter bar(&foo); + + LLDynamicArray source_ids; + const S32 COUNT = 100000; + S32 q; + for (q = 0; q < COUNT; q++) + { + llinfos << "Creating" << llendl; + LLUUID id; + id.generate(); + //llinfos << q << ":" << id << llendl; + uuid_pair pair; + pair.mUUID = id; + pair.mValue = q; + foo.set(id, pair); + source_ids.put(id); + //ms_sleep(1); + } + + uuid_pair cur; + llinfos << "Iterating" << llendl; + for (cur = bar.first(); !bar.done(); cur = bar.next()) + { + if (source_ids[cur.mValue] != cur.mUUID) + { + llerrs << "Incorrect value iterated!" << llendl; + } + //llinfos << cur.mValue << ":" << cur.mUUID << llendl; + //ms_sleep(1); + } + + llinfos << "Finding" << llendl; + for (q = 0; q < COUNT; q++) + { + cur = foo.get(source_ids[q]); + if (source_ids[cur.mValue] != cur.mUUID) + { + llerrs << "Incorrect value found!" << llendl; + } + //llinfos << res.mValue << ":" << res.mUUID << llendl; + //ms_sleep(1); + } + + llinfos << "Removing" << llendl; + for (q = 0; q < COUNT/2; q++) + { + if (!foo.remove(source_ids[q])) + { + llerrs << "Remove failed!" << llendl; + } + //ms_sleep(1); + } + + llinfos << "Iterating" << llendl; + for (cur = bar.first(); !bar.done(); cur = bar.next()) + { + if (source_ids[cur.mValue] != cur.mUUID) + { + llerrs << "Incorrect value found!" << llendl; + } + //llinfos << cur.mValue << ":" << cur.mUUID << llendl; + //ms_sleep(1); + } + llinfos << "Done with UUID map test" << llendl; + + return 0; + */ + + +// +// LLUUIDHashNode +// + +template +class LLUUIDHashNode +{ +public: + LLUUIDHashNode(); + +public: + S32 mCount; + U8 mKey[SIZE]; + DATA mData[SIZE]; + LLUUIDHashNode *mNextNodep; +}; + + +// +// LLUUIDHashNode implementation +// +template +LLUUIDHashNode::LLUUIDHashNode() +{ + mCount = 0; + mNextNodep = NULL; +} + + +template +class LLUUIDHashMap +{ +public: + // basic constructor including sorter + LLUUIDHashMap(BOOL (*equals)(const LLUUID &uuid, const DATA_TYPE &data), + const DATA_TYPE &null_data); + ~LLUUIDHashMap(); + + inline DATA_TYPE &get(const LLUUID &uuid); + inline BOOL check(const LLUUID &uuid) const; + inline DATA_TYPE &set(const LLUUID &uuid, const DATA_TYPE &type); + inline BOOL remove(const LLUUID &uuid); + void removeAll(); + + inline S32 getLength() const; // Warning, NOT O(1!) +public: + BOOL (*mEquals)(const LLUUID &uuid, const DATA_TYPE &data); + LLUUIDHashNode mNodes[256]; + + S32 mIterCount; +protected: + DATA_TYPE mNull; +}; + + +// +// LLUUIDHashMap implementation +// + +template +LLUUIDHashMap::LLUUIDHashMap(BOOL (*equals)(const LLUUID &uuid, const DATA_TYPE &data), + const DATA_TYPE &null_data) +: mEquals(equals), + mIterCount(0), + mNull(null_data) +{ } + +template +LLUUIDHashMap::~LLUUIDHashMap() +{ + removeAll(); +} + +template +void LLUUIDHashMap::removeAll() +{ + S32 bin; + for (bin = 0; bin < 256; bin++) + { + LLUUIDHashNode* nodep = &mNodes[bin]; + + BOOL first = TRUE; + while (nodep) + { + S32 i; + const S32 count = nodep->mCount; + + // Iterate through all members of this node + for (i = 0; i < count; i++) + { + nodep->mData[i] = mNull; + } + + nodep->mCount = 0; + // Done with all objects in this node, go to the next. + + LLUUIDHashNode* curp = nodep; + nodep = nodep->mNextNodep; + + // Delete the node if it's not the first node + if (first) + { + first = FALSE; + curp->mNextNodep = NULL; + } + else + { + delete curp; + } + } + } +} + +template +inline S32 LLUUIDHashMap::getLength() const +{ + S32 count = 0; + S32 bin; + for (bin = 0; bin < 256; bin++) + { + LLUUIDHashNode* nodep = &mNodes[bin]; + while (nodep) + { + count += nodep->mCount; + nodep = nodep->mNextNodep; + } + } + return count; +} + +template +inline DATA_TYPE &LLUUIDHashMap::get(const LLUUID &uuid) +{ + LLUUIDHashNode* nodep = &mNodes[uuid.mData[0]]; + + // Grab the second byte of the UUID, which is the key for the node data + const S32 second_byte = uuid.mData[1]; + while (nodep) + { + S32 i; + const S32 count = nodep->mCount; + + // Iterate through all members of this node + for (i = 0; i < count; i++) + { + if ((nodep->mKey[i] == second_byte) && mEquals(uuid, nodep->mData[i])) + { + // The second byte matched, and our equality test passed. + // We found it. + return nodep->mData[i]; + } + } + + // Done with all objects in this node, go to the next. + nodep = nodep->mNextNodep; + } + return mNull; +} + + +template +inline BOOL LLUUIDHashMap::check(const LLUUID &uuid) const +{ + const LLUUIDHashNode* nodep = &mNodes[uuid.mData[0]]; + + // Grab the second byte of the UUID, which is the key for the node data + const S32 second_byte = uuid.mData[1]; + while (nodep) + { + S32 i; + const S32 count = nodep->mCount; + + // Iterate through all members of this node + for (i = 0; i < count; i++) + { + if ((nodep->mKey[i] == second_byte) && mEquals(uuid, nodep->mData[i])) + { + // The second byte matched, and our equality test passed. + // We found it. + return TRUE; + } + } + + // Done with all objects in this node, go to the next. + nodep = nodep->mNextNodep; + } + + // Didn't find anything + return FALSE; +} + + +template +inline DATA_TYPE &LLUUIDHashMap::set(const LLUUID &uuid, const DATA_TYPE &data) +{ + // Set is just like a normal find, except that if we find a match + // we replace it with the input value. + // If we don't find a match, we append to the end of the list. + + LLUUIDHashNode* nodep = &mNodes[uuid.mData[0]]; + + const S32 second_byte = uuid.mData[1]; + while (1) + { + const S32 count = nodep->mCount; + + S32 i; + for (i = 0; i < count; i++) + { + if ((nodep->mKey[i] == second_byte) && mEquals(uuid, nodep->mData[i])) + { + // We found a match for this key, replace the data with + // the incoming data. + nodep->mData[i] = data; + return nodep->mData[i]; + } + } + if (!nodep->mNextNodep) + { + // We've iterated through all of the keys without finding a match + if (i < SIZE) + { + // There's still some space on this node, append + // the key and data to it. + nodep->mKey[i] = second_byte; + nodep->mData[i] = data; + nodep->mCount++; + + return nodep->mData[i]; + } + else + { + // This node is full, append a new node to the end. + nodep->mNextNodep = new LLUUIDHashNode; + nodep->mNextNodep->mKey[0] = second_byte; + nodep->mNextNodep->mData[0] = data; + nodep->mNextNodep->mCount = 1; + + return nodep->mNextNodep->mData[0]; + } + } + + // No match on this node, go to the next + nodep = nodep->mNextNodep; + } +} + + +template +inline BOOL LLUUIDHashMap::remove(const LLUUID &uuid) +{ + if (mIterCount) + { + // We don't allow remove when we're iterating, it's bad karma! + llerrs << "Attempted remove while an outstanding iterator in LLUUIDHashMap!" << llendl; + } + // Remove is the trickiest operation. + // What we want to do is swap the last element of the last + // node if we find the one that we want to remove, but we have + // to deal with deleting the node from the tail if it's empty, but + // NOT if it's the only node left. + + LLUUIDHashNode *nodep = &mNodes[uuid.mData[0]]; + + // Not empty, we need to search through the nodes + const S32 second_byte = uuid.mData[1]; + + // A modification of the standard search algorithm. + while (nodep) + { + const S32 count = nodep->mCount; + + S32 i; + for (i = 0; i < count; i++) + { + if ((nodep->mKey[i] == second_byte) && mEquals(uuid, nodep->mData[i])) + { + // We found the node that we want to remove. + // Find the last (and next-to-last) node, and the index of the last + // element. We could conceviably start from the node we're on, + // but that makes it more complicated, this is easier. + + LLUUIDHashNode *prevp = &mNodes[uuid.mData[0]]; + LLUUIDHashNode *lastp = prevp; + + // Find the last and next-to-last + while (lastp->mNextNodep) + { + prevp = lastp; + lastp = lastp->mNextNodep; + } + + // First, swap in the last to the current location. + nodep->mKey[i] = lastp->mKey[lastp->mCount - 1]; + nodep->mData[i] = lastp->mData[lastp->mCount - 1]; + + // Now, we delete the entry + lastp->mCount--; + lastp->mData[lastp->mCount] = mNull; + + if (!lastp->mCount) + { + // We deleted the last element! + if (lastp != &mNodes[uuid.mData[0]]) + { + // Only blitz the node if it's not the head + // Set the previous node to point to NULL, then + // blitz the empty last node + prevp->mNextNodep = NULL; + delete lastp; + } + } + return TRUE; + } + } + + // Iterate to the next node, we've scanned all the entries in this one. + nodep = nodep->mNextNodep; + } + return FALSE; +} + + +// +// LLUUIDHashMapIter +// + +template +class LLUUIDHashMapIter +{ +public: + LLUUIDHashMapIter(LLUUIDHashMap *hash_mapp); + ~LLUUIDHashMapIter(); + + + inline void first(); + inline void next(); + inline BOOL done() const; + + DATA_TYPE& operator*() const + { + return mCurHashNodep->mData[mCurHashNodeKey]; + } + DATA_TYPE* operator->() const + { + return &(operator*()); + } + +protected: + LLUUIDHashMap *mHashMapp; + LLUUIDHashNode *mCurHashNodep; + + S32 mCurHashMapNodeNum; + S32 mCurHashNodeKey; + + DATA_TYPE mNull; +}; + + +// +// LLUUIDHashMapIter Implementation +// +template +LLUUIDHashMapIter::LLUUIDHashMapIter(LLUUIDHashMap *hash_mapp) +{ + mHashMapp = hash_mapp; + mCurHashNodep = NULL; + mCurHashMapNodeNum = 0; + mCurHashNodeKey = 0; +} + +template +LLUUIDHashMapIter::~LLUUIDHashMapIter() +{ + if (mCurHashNodep) + { + // We're partway through an iteration, we can clean up now + mHashMapp->mIterCount--; + } +} + +template +inline void LLUUIDHashMapIter::first() +{ + // Iterate through until we find the first non-empty node; + S32 i; + for (i = 0; i < 256; i++) + { + if (mHashMapp->mNodes[i].mCount) + { + if (!mCurHashNodep) + { + // Increment, since it's no longer safe for us to do a remove + mHashMapp->mIterCount++; + } + + mCurHashNodep = &mHashMapp->mNodes[i]; + mCurHashMapNodeNum = i; + mCurHashNodeKey = 0; + //return mCurHashNodep->mData[0]; + return; + } + } + + // Completely empty! + mCurHashNodep = NULL; + //return mNull; + return; +} + +template +inline BOOL LLUUIDHashMapIter::done() const +{ + return mCurHashNodep ? FALSE : TRUE; +} + +template +inline void LLUUIDHashMapIter::next() +{ + // No current entry, this iterator is done + if (!mCurHashNodep) + { + //return mNull; + return; + } + + // Go to the next element + mCurHashNodeKey++; + if (mCurHashNodeKey < mCurHashNodep->mCount) + { + // We're not done with this node, return the current element + //return mCurHashNodep->mData[mCurHashNodeKey]; + return; + } + + // Done with this node, move to the next + mCurHashNodep = mCurHashNodep->mNextNodep; + if (mCurHashNodep) + { + // Return the first element + mCurHashNodeKey = 0; + //return mCurHashNodep->mData[0]; + return; + } + + // Find the next non-empty node (keyed on the first byte) + mCurHashMapNodeNum++; + + S32 i; + for (i = mCurHashMapNodeNum; i < 256; i++) + { + if (mHashMapp->mNodes[i].mCount) + { + // We found one that wasn't empty + mCurHashNodep = &mHashMapp->mNodes[i]; + mCurHashMapNodeNum = i; + mCurHashNodeKey = 0; + //return mCurHashNodep->mData[0]; + return; + } + } + + // OK, we're done, nothing else to iterate + mCurHashNodep = NULL; + mHashMapp->mIterCount--; // Decrement since we're safe to do removes now + //return mNull; +} + +#endif // LL_LLUUIDHASHMAP_H diff --git a/indra/llcommon/llworkerthread.cpp b/indra/llcommon/llworkerthread.cpp new file mode 100644 index 0000000000..a9370c8f6d --- /dev/null +++ b/indra/llcommon/llworkerthread.cpp @@ -0,0 +1,284 @@ +/** + * @file llworkerthread.cpp + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llworkerthread.h" +#include "llstl.h" + +#if USE_FRAME_CALLBACK_MANAGER +#include "llframecallbackmanager.h" +#endif + +//============================================================================ + +/*static*/ LLWorkerThread* LLWorkerThread::sLocal = NULL; +/*static*/ std::set LLWorkerThread::sThreadList; + +//============================================================================ +// Run on MAIN thread + +//static +void LLWorkerThread::initClass(bool local_is_threaded, bool local_run_always) +{ + if (!sLocal) + { + sLocal = new LLWorkerThread(local_is_threaded, local_run_always); + } +} + +//static +void LLWorkerThread::cleanupClass() +{ + if (sLocal) + { + while (sLocal->getPending()) + { + sLocal->update(0); + } + delete sLocal; + sLocal = NULL; + llassert(sThreadList.size() == 0); + } +} + +//static +S32 LLWorkerThread::updateClass(U32 ms_elapsed) +{ + for (std::set::iterator iter = sThreadList.begin(); iter != sThreadList.end(); iter++) + { + (*iter)->update(ms_elapsed); + } + return getAllPending(); +} + +//static +S32 LLWorkerThread::getAllPending() +{ + S32 res = 0; + for (std::set::iterator iter = sThreadList.begin(); iter != sThreadList.end(); iter++) + { + res += (*iter)->getPending(); + } + return res; +} + +//static +void LLWorkerThread::pauseAll() +{ + for (std::set::iterator iter = sThreadList.begin(); iter != sThreadList.end(); iter++) + { + (*iter)->pause(); + } +} + +//static +void LLWorkerThread::waitOnAllPending() +{ + for (std::set::iterator iter = sThreadList.begin(); iter != sThreadList.end(); iter++) + { + (*iter)->waitOnPending(); + } +} + +//---------------------------------------------------------------------------- + +LLWorkerThread::LLWorkerThread(bool threaded, bool runalways) : + LLQueuedThread("Worker", threaded, runalways) +{ + sThreadList.insert(this); +} + +LLWorkerThread::~LLWorkerThread() +{ + llverify(sThreadList.erase(this) == 1); + // ~LLQueuedThread() will be called here +} + +//---------------------------------------------------------------------------- + + +LLWorkerThread::handle_t LLWorkerThread::add(LLWorkerClass* workerclass, S32 param, U32 priority) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, priority, workerclass, param); + + bool res = addRequest(req); + if (!res) + { + llerrs << "add called after LLWorkerThread::cleanupClass()" << llendl; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +//============================================================================ +// Runs on its OWN thread + +bool LLWorkerThread::processRequest(QueuedRequest* qreq) +{ + Request *req = (Request*)qreq; + + req->getWorkerClass()->setWorking(true); + + bool complete = req->getWorkerClass()->doWork(req->getParam()); + + req->getWorkerClass()->setWorking(false); + + LLThread::yield(); // worker thread should yield after each request + + return complete; +} + +//============================================================================ + +LLWorkerThread::Request::Request(handle_t handle, U32 priority, LLWorkerClass* workerclass, S32 param) : + LLQueuedThread::QueuedRequest(handle, priority), + mWorkerClass(workerclass), + mParam(param) +{ +} + +void LLWorkerThread::Request::deleteRequest() +{ + LLQueuedThread::QueuedRequest::deleteRequest(); +} + +//============================================================================ +// LLWorkerClass:: operates in main thread + +LLWorkerClass::LLWorkerClass(LLWorkerThread* workerthread, const std::string& name) + : mWorkerThread(workerthread), + mWorkerClassName(name), + mWorkHandle(LLWorkerThread::nullHandle()), + mWorkFlags(0) +{ + if (!mWorkerThread) + { + mWorkerThread = LLWorkerThread::sLocal; + } +} +LLWorkerClass::~LLWorkerClass() +{ + if (mWorkHandle != LLWorkerThread::nullHandle()) + { + LLWorkerThread::Request* workreq = (LLWorkerThread::Request*)mWorkerThread->getRequest(mWorkHandle); + if (!workreq) + { + llerrs << "LLWorkerClass destroyed with stale work handle" << llendl; + } + if (workreq->getStatus() != LLWorkerThread::STATUS_ABORT && + workreq->getStatus() != LLWorkerThread::STATUS_ABORTED && + workreq->getStatus() != LLWorkerThread::STATUS_COMPLETE) + { + llerrs << "LLWorkerClass destroyed with active worker! Worker Status: " << workreq->getStatus() << llendl; + } + } +} + +void LLWorkerClass::setWorkerThread(LLWorkerThread* workerthread) +{ + if (mWorkHandle != LLWorkerThread::nullHandle()) + { + llerrs << "LLWorkerClass attempt to change WorkerThread with active worker!" << llendl; + } + mWorkerThread = workerthread; +} + +//---------------------------------------------------------------------------- + +bool LLWorkerClass::yield() +{ + llassert(mWorkFlags & WCF_WORKING); + LLThread::yield(); + mWorkerThread->checkPause(); + return (getFlags() & WCF_ABORT_REQUESTED) ? true : false; +} + +//---------------------------------------------------------------------------- + +// calls startWork, adds doWork() to queue +void LLWorkerClass::addWork(S32 param, U32 priority) +{ + if (mWorkHandle != LLWorkerThread::nullHandle()) + { + llerrs << "LLWorkerClass attempt to add work with active worker!" << llendl; + } +#if _DEBUG +// llinfos << "addWork: " << mWorkerClassName << " Param: " << param << llendl; +#endif + startWork(param); + mWorkHandle = mWorkerThread->add(this, param, priority); +} + +void LLWorkerClass::abortWork() +{ +#if _DEBUG +// LLWorkerThread::Request* workreq = mWorkerThread->getRequest(mWorkHandle); +// if (workreq) +// llinfos << "abortWork: " << mWorkerClassName << " Param: " << workreq->getParam() << llendl; +#endif + mWorkerThread->abortRequest(mWorkHandle); + setFlags(WCF_ABORT_REQUESTED); +} + +// if doWork is complete or aborted, call endWork() and return true +bool LLWorkerClass::checkWork() +{ + bool complete = false, abort = false; + LLWorkerThread::Request* workreq = (LLWorkerThread::Request*)mWorkerThread->getRequest(mWorkHandle); + llassert(workreq); + if (getFlags(WCF_ABORT_REQUESTED) || workreq->getStatus() == LLWorkerThread::STATUS_ABORTED) + { + complete = true; + abort = true; + } + else if (workreq->getStatus() == LLWorkerThread::STATUS_COMPLETE) + { + complete = true; + } + if (complete) + { +#if _DEBUG +// llinfos << "endWork: " << mWorkerClassName << " Param: " << workreq->getParam() << llendl; +#endif + endWork(workreq->getParam(), abort); + mWorkerThread->completeRequest(mWorkHandle); + mWorkHandle = LLWorkerThread::nullHandle(); + } + return complete; +} + +void LLWorkerClass::killWork() +{ + if (haveWork()) + { + abortWork(); + bool paused = mWorkerThread->isPaused(); + while (!checkWork()) + { + mWorkerThread->updateQueue(0); + } + if (paused) + { + mWorkerThread->pause(); + } + } +} + +void LLWorkerClass::setPriority(U32 priority) +{ + if (haveWork()) + { + mWorkerThread->setPriority(mWorkHandle, priority); + } +} + +//============================================================================ + diff --git a/indra/llcommon/llworkerthread.h b/indra/llcommon/llworkerthread.h new file mode 100644 index 0000000000..bf5887e797 --- /dev/null +++ b/indra/llcommon/llworkerthread.h @@ -0,0 +1,168 @@ +/** + * @file llworkerthread.h + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLWORKERTHREAD_H +#define LL_LLWORKERTHREAD_H + +#include +#include +#include +#include + +#include "llqueuedthread.h" + +#define USE_FRAME_CALLBACK_MANAGER 0 + +//============================================================================ + +class LLWorkerClass; + +//============================================================================ +// Note: ~LLWorkerThread is O(N) N=# of worker threads, assumed to be small +// It is assumed that LLWorkerThreads are rarely created/destroyed. + +class LLWorkerThread : public LLQueuedThread +{ +public: + class Request : public LLQueuedThread::QueuedRequest + { + protected: + ~Request() {}; // use deleteRequest() + + public: + Request(handle_t handle, U32 priority, LLWorkerClass* workerclass, S32 param); + + S32 getParam() + { + return mParam; + } + LLWorkerClass* getWorkerClass() + { + return mWorkerClass; + } + + /*virtual*/ void deleteRequest(); + + private: + LLWorkerClass* mWorkerClass; + S32 mParam; + }; + +public: + LLWorkerThread(bool threaded = true, bool runalways = true); + ~LLWorkerThread(); + +protected: + /*virtual*/ bool processRequest(QueuedRequest* req); + +public: + handle_t add(LLWorkerClass* workerclass, S32 param, U32 priority = PRIORITY_NORMAL); + + static void initClass(bool local_is_threaded = true, bool local_run_always = true); // Setup sLocal + static S32 updateClass(U32 ms_elapsed); + static S32 getAllPending(); + static void pauseAll(); + static void waitOnAllPending(); + static void cleanupClass(); // Delete sLocal + +public: + static LLWorkerThread* sLocal; // Default worker thread + static std::set sThreadList; // array of threads (includes sLocal) +}; + +//============================================================================ + +// This is a base class which any class with worker functions should derive from. +// Example Usage: +// LLMyWorkerClass* foo = new LLMyWorkerClass(); +// foo->fetchData(); // calls addWork() +// while(1) // main loop +// { +// if (foo->hasData()) // calls checkWork() +// foo->processData(); +// } +// +// WorkerClasses only have one set of work functions. If they need to do multiple +// background tasks, use 'param' to switch amnong them. +// Only one background task can be active at a time (per instance). +// i.e. don't call addWork() if haveWork() returns true + +class LLWorkerClass +{ +public: + typedef LLWorkerThread::handle_t handle_t; + enum FLAGS + { + WCF_WORKING = 0x01, + WCF_ABORT_REQUESTED = 0x80 + }; + +public: + LLWorkerClass(LLWorkerThread* workerthread, const std::string& name); + virtual ~LLWorkerClass(); + + // pure virtual, called from WORKER THREAD, returns TRUE if done + virtual bool doWork(S32 param)=0; // Called from LLWorkerThread::processRequest() + + // called from WORKER THREAD + void setWorking(bool working) { working ? setFlags(WCF_WORKING) : clearFlags(WCF_WORKING); } + + bool isWorking() { return getFlags(WCF_WORKING); } + bool wasAborted() { return getFlags(WCF_ABORT_REQUESTED); } + + const std::string& getName() const { return mWorkerClassName; } + +protected: + // Call from doWork only to avoid eating up cpu time. + // Returns true if work has been aborted + // yields the current thread and calls mWorkerThread->checkPause() + bool yield(); + + void setWorkerThread(LLWorkerThread* workerthread); + + // addWork(): calls startWork, adds doWork() to queue + void addWork(S32 param, U32 priority = LLWorkerThread::PRIORITY_NORMAL); + + // abortWork(): requests that work be aborted + void abortWork(); + + // checkWork(): if doWork is complete or aborted, call endWork() and return true + bool checkWork(); + + // haveWork(): return true if mWorkHandle != null + bool haveWork() { return mWorkHandle != LLWorkerThread::nullHandle(); } + + // killWork(): aborts work and waits for the abort to process + void killWork(); + + // setPriority(): changes the priority of a request + void setPriority(U32 priority); + +private: + void setFlags(U32 flags) { mWorkFlags = mWorkFlags | flags; } + void clearFlags(U32 flags) { mWorkFlags = mWorkFlags & ~flags; } + U32 getFlags() { return mWorkFlags; } + bool getFlags(U32 flags) { return mWorkFlags & flags ? true : false; } + +private: + // pure virtuals + virtual void startWork(S32 param)=0; // called from addWork() (MAIN THREAD) + virtual void endWork(S32 param, bool aborted)=0; // called from doWork() (MAIN THREAD) + +protected: + LLWorkerThread* mWorkerThread; + std::string mWorkerClassName; + handle_t mWorkHandle; + +private: + LLAtomicU32 mWorkFlags; +}; + +//============================================================================ + + +#endif // LL_LLWORKERTHREAD_H diff --git a/indra/llcommon/metaclass.cpp b/indra/llcommon/metaclass.cpp new file mode 100644 index 0000000000..29ad20e6b6 --- /dev/null +++ b/indra/llcommon/metaclass.cpp @@ -0,0 +1,60 @@ +/** + * @file metaclass.cpp + * @author Babbage + * @date 2006-05-15 + * @brief Implementation of LLMetaClass + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "metaclass.h" +#include "metaproperty.h" +#include "reflective.h" + +LLMetaClass::LLMetaClass() +{ +} + +//virtual +LLMetaClass::~LLMetaClass() +{ +} + +const LLMetaProperty* LLMetaClass::findProperty(const std::string& name) const +{ + PropertyIterator iter = mProperties.find(name); + if(iter == mProperties.end()) + { + return NULL; + } + return (*iter).second; +} + +void LLMetaClass::addProperty(const LLMetaProperty* property) +{ + mProperties.insert(std::make_pair(property->getName(), property)); +} + +U32 LLMetaClass::getPropertyCount() const +{ + return mProperties.size(); +} + +LLMetaClass::PropertyIterator LLMetaClass::beginProperties() const +{ + return mProperties.begin(); +} + +LLMetaClass::PropertyIterator LLMetaClass::endProperties() const +{ + return mProperties.end(); +} + +bool LLMetaClass::isInstance(const LLReflective* object) const +{ + // TODO: Babbage: Search through super classes of objects MetaClass. + const LLMetaClass* object_meta_class = &(object->getMetaClass()); + return (object_meta_class == this); +} + diff --git a/indra/llcommon/metaclass.h b/indra/llcommon/metaclass.h new file mode 100644 index 0000000000..3424cea087 --- /dev/null +++ b/indra/llcommon/metaclass.h @@ -0,0 +1,64 @@ +/** + * @file metaclass.h + * @author Babbage + * @date 2006-05-15 + * @brief Reflective meta information describing a class. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_METACLASS_H +#define LL_METACLASS_H + +#include +#include + +#include "stdtypes.h" + +class LLReflective; +class LLMetaProperty; +class LLMetaMethod; +class LLMetaClass +{ +public: + + LLMetaClass(); + virtual ~LLMetaClass(); + + // Create instance of this MetaClass. NULL if class is abstract. + // Gives ownership of returned object. + // virtual LLReflective* create() const = 0; + + // Returns named property or NULL. + const LLMetaProperty* findProperty(const std::string& name) const; + + // Add property to metaclass. Takes ownership of given property. + void addProperty(const LLMetaProperty* property); + + typedef std::map::const_iterator PropertyIterator; + + U32 getPropertyCount() const; + + PropertyIterator beginProperties() const; + PropertyIterator endProperties() const; + + // Returns named property or NULL. + // const LLMetaMethod* findMethod(const std::string& name) const; + + // Add method to metaclass. Takes ownership of given method. + // void addMethod(const LLMetaMethod* method); + + // Find MetaClass by name. NULL if name is unknown. + // static LLMetaClass* findClass(const std::string& name); + + // True if object is instance of this meta class. + bool isInstance(const LLReflective* object) const; + +private: + + typedef std::map PropertyMap; + PropertyMap mProperties; +}; + +#endif // LL_METACLASS_H diff --git a/indra/llcommon/metaclasst.h b/indra/llcommon/metaclasst.h new file mode 100644 index 0000000000..8ae9016b57 --- /dev/null +++ b/indra/llcommon/metaclasst.h @@ -0,0 +1,42 @@ +/** + * @file metaclasst.h + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_METACLASST_H +#define LL_METACLASST_H + +#include "metaclass.h" + +template +class LLMetaClassT : public LLMetaClass +{ + public: + + virtual ~LLMetaClassT() {;} + + static const LLMetaClassT& instance() + { + static const LLMetaClassT& instance = buildMetaClass(); + return instance; + } + + private: + + static const LLMetaClassT& buildMetaClass() + { + LLMetaClassT& meta_class = *(new LLMetaClassT()); + reflectProperties(meta_class); + return meta_class; + } + + LLMetaClassT() {;} + + static void reflectProperties(LLMetaClass&) + { + } +}; + +#endif // LL_METACLASST_H diff --git a/indra/llcommon/metaproperty.cpp b/indra/llcommon/metaproperty.cpp new file mode 100644 index 0000000000..befee61a8a --- /dev/null +++ b/indra/llcommon/metaproperty.cpp @@ -0,0 +1,35 @@ +/** + * @file metaproperty.cpp + * @author Babbage + * @date 2006-05-15 + * @brief Implementation of LLMetaProperty. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "metaproperty.h" +#include "metaclass.h" + +LLMetaProperty::LLMetaProperty(const std::string& name, const LLMetaClass& object_class) : + mName(name), mObjectClass(object_class) +{ +} + +//virtual +LLMetaProperty::~LLMetaProperty() +{ +} + +const LLMetaClass& LLMetaProperty::getObjectMetaClass() const +{ + return mObjectClass; +} + +void LLMetaProperty::checkObjectClass(const LLReflective* object) const +{ + if(! mObjectClass.isInstance(object)) + { + throw "class cast exception"; + } +} diff --git a/indra/llcommon/metaproperty.h b/indra/llcommon/metaproperty.h new file mode 100644 index 0000000000..be615f2c67 --- /dev/null +++ b/indra/llcommon/metaproperty.h @@ -0,0 +1,55 @@ +/** + * @file metaproperty.h + * @author Babbage + * @date 2006-05-15 + * @brief Reflective meta information describing a property of a class. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_METAPROPERTY_H +#define LL_METAPROPERTY_H + +#include "stdtypes.h" +#include "llsd.h" +#include "reflective.h" + +class LLMetaClass; +class LLReflective; +class LLMetaProperty +{ +public: + LLMetaProperty(const std::string& name, const LLMetaClass& object_class); + virtual ~LLMetaProperty(); + + // Get property name. + const std::string& getName() const {return mName;} + + // Get value of this property. + virtual const LLReflective* get(const LLReflective* object) const = 0; + + // Set value of this property. + // virtual void set(LLReflective* object, const LLReflective* value) = 0; + + // Get value of this property as LLSD. Default returns undefined LLSD. + virtual LLSD getLLSD(const LLReflective* object) const = 0; + + // Get the MetaClass of legal values of this property. + // const LLMetaClass& getValueMetaClass(); + + // Get the meta class that this property is a member of. + const LLMetaClass& getObjectMetaClass() const; + +protected: + + // Check object is instance of object class, throw exception if not. + void checkObjectClass(const LLReflective* object) const; + +private: + + std::string mName; + const LLMetaClass& mObjectClass; +}; + +#endif // LL_METAPROPERTY_H diff --git a/indra/llcommon/metapropertyt.h b/indra/llcommon/metapropertyt.h new file mode 100644 index 0000000000..056e422221 --- /dev/null +++ b/indra/llcommon/metapropertyt.h @@ -0,0 +1,165 @@ +/** + * @file metapropertyt.h + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_METAPROPERTYT_H +#define LL_METAPROPERTYT_H + +#include "llsd.h" +#include "llstring.h" +#include "metaclasst.h" +#include "metaproperty.h" +#include "reflectivet.h" + +template +class LLMetaPropertyT : public LLMetaProperty +{ +public: + + virtual ~LLMetaPropertyT() {;} + + // Get value of this property. Gives ownership of returned value. + virtual const LLReflective* get(const LLReflective* object) const + { + checkObjectClass(object); + return getProperty(object); + } + + // Set value of this property. + /*virtual void set(LLReflective* object, const LLReflective* value) + { + // TODO: Babbage: Check types. + ref(object) = static_cast* >(value)->getValue(); + }*/ + + // Get value of this property as LLSD. + virtual LLSD getLLSD(const LLReflective* object) const + { + return LLSD(); + } + +protected: + + LLMetaPropertyT(const std::string& name, const LLMetaClass& object_class) : LLMetaProperty(name, object_class) {;} + + virtual const TProperty* getProperty(const LLReflective* object) const = 0; +}; + +template <> +inline const LLReflective* LLMetaPropertyT::get(const LLReflective* object) const +{ + checkObjectClass(object); + return NULL; +} + +template <> +inline const LLReflective* LLMetaPropertyT::get(const LLReflective* object) const +{ + checkObjectClass(object); + return NULL; +} + +template <> +inline const LLReflective* LLMetaPropertyT::get(const LLReflective* object) const +{ + checkObjectClass(object); + return NULL; +} + +template <> +inline const LLReflective* LLMetaPropertyT::get(const LLReflective* object) const +{ + checkObjectClass(object); + return NULL; +} + +template <> +inline LLSD LLMetaPropertyT::getLLSD(const LLReflective* object) const +{ + return *(getProperty(object)); +} + +template <> +inline LLSD LLMetaPropertyT::getLLSD(const LLReflective* object) const +{ + return *(getProperty(object)); +} + +template <> +inline LLSD LLMetaPropertyT::getLLSD(const LLReflective* object) const +{ + return *(getProperty(object)); +} + +template <> +inline LLSD LLMetaPropertyT::getLLSD(const LLReflective* object) const +{ + return *(getProperty(object)); +} + +template +class LLMetaPropertyTT : public LLMetaPropertyT +{ +public: + + LLMetaPropertyTT(const std::string& name, const LLMetaClass& object_class, TProperty (TObject::*property)) : + LLMetaPropertyT(name, object_class), mProperty(property) {;} + +protected: + + // Get void* to property. + virtual const TProperty* getProperty(const LLReflective* object) const + { + const TObject* typed_object = static_cast(object); + return &(typed_object->*mProperty); + }; + +private: + + TProperty (TObject::*mProperty); +}; + +template +class LLMetaPropertyPtrTT : public LLMetaPropertyT +{ +public: + + LLMetaPropertyPtrTT(const std::string& name, const LLMetaClass& object_class, TProperty* (TObject::*property)) : + LLMetaPropertyT(name, object_class), mProperty(property) {;} + +protected: + + // Get void* to property. + virtual const TProperty* getProperty(const LLReflective* object) const + { + const TObject* typed_object = static_cast(object); + return typed_object->*mProperty; + }; + +private: + + TProperty* (TObject::*mProperty); +}; + +// Utility function to simplify the registration of members. +template +void reflectProperty(LLMetaClass& meta_class, const std::string& name, TProperty (TObject::*property)) +{ + typedef LLMetaPropertyTT PropertyType; + const LLMetaProperty* meta_property = new PropertyType(name, meta_class, property); + meta_class.addProperty(meta_property); +} + +// Utility function to simplify the registration of ptr properties. +template +void reflectPtrProperty(LLMetaClass& meta_class, const std::string& name, TProperty* (TObject::*property)) +{ + typedef LLMetaPropertyPtrTT PropertyType; + const LLMetaProperty* meta_property = new PropertyType(name, meta_class, property); + meta_class.addProperty(meta_property); +} + +#endif // LL_METAPROPERTYT_H diff --git a/indra/llcommon/reflective.cpp b/indra/llcommon/reflective.cpp new file mode 100644 index 0000000000..039a500575 --- /dev/null +++ b/indra/llcommon/reflective.cpp @@ -0,0 +1,20 @@ +/** + * @file reflective.cpp + * @author Babbage + * @date 2006-05-15 + * @brief Implementation of LLReflective. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "reflective.h" + +LLReflective::LLReflective() +{ +} + +//virtual +LLReflective::~LLReflective() +{ +} diff --git a/indra/llcommon/reflective.h b/indra/llcommon/reflective.h new file mode 100644 index 0000000000..1e3501390e --- /dev/null +++ b/indra/llcommon/reflective.h @@ -0,0 +1,24 @@ +/** + * @file reflective.h + * @author Babbage + * @date 2006-05-15 + * @brief Interface that must be implemented by all reflective classes. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_REFLECTIVE_H +#define LL_REFLECTIVE_H + +class LLMetaClass; +class LLReflective +{ +public: + LLReflective(); + virtual ~LLReflective(); + + virtual const LLMetaClass& getMetaClass() const = 0; +}; + +#endif // LL_REFLECTIVE_H diff --git a/indra/llcommon/reflectivet.h b/indra/llcommon/reflectivet.h new file mode 100644 index 0000000000..e16b4386ed --- /dev/null +++ b/indra/llcommon/reflectivet.h @@ -0,0 +1,30 @@ +/** + * @file reflectivet.h + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_REFLECTIVET_H +#define LL_REFLECTIVET_H + +#include "reflective.h" + +template +class LLReflectiveT : public LLReflective +{ +public: + + LLReflectiveT(const T& value) : mValue(value) {;} + virtual ~LLReflectiveT() {;} + + virtual const LLMetaClass& getMetaClass() const {return LLMetaClassT >::instance();} + + const T& getValue() const {return mValue;} + +private: + + T mValue; +}; + +#endif diff --git a/indra/llcommon/roles_constants.h b/indra/llcommon/roles_constants.h new file mode 100644 index 0000000000..a4771dfeb6 --- /dev/null +++ b/indra/llcommon/roles_constants.h @@ -0,0 +1,168 @@ +/** + * @file roles_constants.h + * @brief General Roles Constants + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_ROLES_CONSTANTS_H +#define LL_ROLES_CONSTANTS_H + +// This value includes the everyone group. +const S32 MAX_ROLES = 10; + +enum LLRoleMemberChangeType +{ + RMC_ADD, + RMC_REMOVE, + RMC_NONE +}; + +enum LLRoleChangeType +{ + RC_UPDATE_NONE, + RC_UPDATE_DATA, + RC_UPDATE_POWERS, + RC_UPDATE_ALL, + RC_CREATE, + RC_DELETE +}; + +// +// Powers +// + +// KNOWN HOLES: +// bit 0x1 << 37 (GP_OBJECT_RETURN) + +// These powers were removed to make group roles simpler +// bit 0x1 << 27 (GP_LAND_ALLOW_SCRIPTS) +// bit 0x1 << 16 (GP_LAND_VIEW_OWNED) +// bit 0x1 << 41 (GP_ACCOUNTING_VIEW) +// bit 0x1 << 46 (GP_PROPOSAL_VIEW) + +const U64 GP_NO_POWERS = 0x0; +const U64 GP_ALL_POWERS = 0xFFFFFFFFFFFFFFFFLL; + +// Membership +const U64 GP_MEMBER_INVITE = 0x1 << 1; // Invite member +const U64 GP_MEMBER_EJECT = 0x1 << 2; // Eject member from group +const U64 GP_MEMBER_OPTIONS = 0x1 << 3; // Toggle "Open enrollment" and change "Signup Fee" +const U64 GP_MEMBER_VISIBLE_IN_DIR = 0x1LL << 47; + +// Roles +const U64 GP_ROLE_CREATE = 0x1 << 4; // Create new roles +const U64 GP_ROLE_DELETE = 0x1 << 5; // Delete roles +const U64 GP_ROLE_PROPERTIES = 0x1 << 6; // Change Role Names, Titles, and Descriptions (Of roles the user is in, only, or any role in group?) +const U64 GP_ROLE_ASSIGN_MEMBER_LIMITED = 0x1 << 7; // Assign Member to a Role that the assigner is in +const U64 GP_ROLE_ASSIGN_MEMBER = 0x1 << 8; // Assign Member to Role +const U64 GP_ROLE_REMOVE_MEMBER = 0x1 << 9; // Remove Member from Role +const U64 GP_ROLE_CHANGE_ACTIONS = 0x1 << 10; // Change actions a role can perform + +// Group Identity +const U64 GP_GROUP_CHANGE_IDENTITY = 0x1 << 11; // Charter, insignia, 'Show In Group List', 'Publish on the web', 'Mature', all 'Show Member In Group Profile' checkboxes + +// Parcel Management +const U64 GP_LAND_DEED = 0x1 << 12; // Deed Land and Buy Land for Group +const U64 GP_LAND_RELEASE = 0x1 << 13; // Release Land (to Gov. Linden) +const U64 GP_LAND_SET_SALE_INFO = 0x1 << 14; // Set for sale info (Toggle "For Sale", Set Price, Set Target, Toggle "Sell objects with the land") +const U64 GP_LAND_DIVIDE_JOIN = 0x1 << 15; // Divide and Join Parcels + +// Parcel Identity +const U64 GP_LAND_FIND_PLACES = 0x1 << 17; // Toggle "Show in Find Places" and Set Category. +const U64 GP_LAND_CHANGE_IDENTITY = 0x1 << 18; // Change Parcel Identity: Parcel Name, Parcel Description, Snapshot, 'Publish on the web', and 'Mature' checkbox +const U64 GP_LAND_SET_LANDING_POINT = 0x1 << 19; // Set Landing Point + +// Parcel Settings +const U64 GP_LAND_CHANGE_MEDIA = 0x1 << 20; // Change Media Settings +const U64 GP_LAND_EDIT = 0x1 << 21; // Toggle Edit Land +const U64 GP_LAND_OPTIONS = 0x1 << 22; // Toggle Set Home Point, Fly, Outside Scripts, Create/Edit Objects, Landmark, and Damage checkboxes + +// Parcel Powers +const U64 GP_LAND_ALLOW_EDIT_LAND = 0x1 << 23; // Bypass Edit Land Restriction +const U64 GP_LAND_ALLOW_FLY = 0x1 << 24; // Bypass Fly Restriction +const U64 GP_LAND_ALLOW_CREATE = 0x1 << 25; // Bypass Create/Edit Objects Restriction +const U64 GP_LAND_ALLOW_LANDMARK = 0x1 << 26; // Bypass Landmark Restriction +const U64 GP_LAND_ALLOW_SET_HOME = 0x1 << 28; // Bypass Set Home Point Restriction + +// Parcel Access +const U64 GP_LAND_MANAGE_ALLOWED = 0x1 << 29; // Manage Allowed List +const U64 GP_LAND_MANAGE_BANNED = 0x1 << 30; // Manage Banned List +const U64 GP_LAND_MANAGE_PASSES = 0x1LL << 31; // Change Sell Pass Settings +const U64 GP_LAND_ADMIN = 0x1LL << 32; // Eject and Freeze Users on the land + +// Parcel Content +const U64 GP_LAND_RETURN_GROUP_OWNED= 0x1LL << 48; // Return objects on parcel that are owned by the group +const U64 GP_LAND_RETURN_GROUP_SET = 0x1LL << 33; // Return objects on parcel that are set to group +const U64 GP_LAND_RETURN_NON_GROUP = 0x1LL << 34; // Return objects on parcel that are not set to group +// Select a power-bit based on an object's relationship to a parcel. +const U64 GP_LAND_RETURN = GP_LAND_RETURN_GROUP_OWNED + | GP_LAND_RETURN_GROUP_SET + | GP_LAND_RETURN_NON_GROUP; +const U64 GP_LAND_GARDENING = 0x1LL << 35; // Parcel Gardening - plant and move linden trees + +// Object Management +const U64 GP_OBJECT_DEED = 0x1LL << 36; // Deed Object +// HOLE -- 0x1LL << 37 +const U64 GP_OBJECT_MANIPULATE = 0x1LL << 38; // Manipulate Group Owned Objects (Move, Copy, Mod) +const U64 GP_OBJECT_SET_SALE = 0x1LL << 39; // Set Group Owned Object for Sale + +// Accounting +const U64 GP_ACCOUNTING_ACCOUNTABLE = 0x1LL << 40; // Pay Group Liabilities and Receive Group Dividends + +// Notices +const U64 GP_NOTICES_SEND = 0x1LL << 42; // Send Notices +const U64 GP_NOTICES_RECEIVE = 0x1LL << 43; // Receive Notices and View Notice History + +// Proposals +const U64 GP_PROPOSAL_START = 0x1LL << 44; // Start Proposal +const U64 GP_PROPOSAL_VOTE = 0x1LL << 45; // Vote on Proposal + +const U64 GP_DEFAULT_MEMBER = GP_ACCOUNTING_ACCOUNTABLE + | GP_LAND_ALLOW_SET_HOME + | GP_NOTICES_RECEIVE + | GP_PROPOSAL_START + | GP_PROPOSAL_VOTE + ; + +const U64 GP_DEFAULT_OFFICER = GP_ACCOUNTING_ACCOUNTABLE + | GP_GROUP_CHANGE_IDENTITY + | GP_LAND_ADMIN + | GP_LAND_ALLOW_EDIT_LAND + | GP_LAND_ALLOW_FLY + | GP_LAND_ALLOW_CREATE + | GP_LAND_ALLOW_LANDMARK + | GP_LAND_ALLOW_SET_HOME + | GP_LAND_CHANGE_IDENTITY + | GP_LAND_CHANGE_MEDIA + | GP_LAND_DEED + | GP_LAND_DIVIDE_JOIN + | GP_LAND_EDIT + | GP_LAND_FIND_PLACES + | GP_LAND_GARDENING + | GP_LAND_MANAGE_ALLOWED + | GP_LAND_MANAGE_BANNED + | GP_LAND_MANAGE_PASSES + | GP_LAND_OPTIONS + | GP_LAND_RELEASE + | GP_LAND_RETURN_GROUP_OWNED + | GP_LAND_RETURN_GROUP_SET + | GP_LAND_RETURN_NON_GROUP + | GP_LAND_SET_LANDING_POINT + | GP_LAND_SET_SALE_INFO + | GP_MEMBER_EJECT + | GP_MEMBER_INVITE + | GP_MEMBER_OPTIONS + | GP_MEMBER_VISIBLE_IN_DIR + | GP_NOTICES_RECEIVE + | GP_NOTICES_SEND + | GP_OBJECT_DEED + | GP_OBJECT_MANIPULATE + | GP_OBJECT_SET_SALE + | GP_PROPOSAL_START + | GP_PROPOSAL_VOTE + | GP_ROLE_ASSIGN_MEMBER_LIMITED + | GP_ROLE_PROPERTIES + ; +#endif diff --git a/indra/llcommon/stdenums.h b/indra/llcommon/stdenums.h new file mode 100644 index 0000000000..12fb932a0a --- /dev/null +++ b/indra/llcommon/stdenums.h @@ -0,0 +1,115 @@ +/** + * @file stdenums.h + * @brief Enumerations for indra. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_STDENUMS_H +#define LL_STDENUMS_H + +//---------------------------------------------------------------------------- +// DEPRECATED - create new, more specific files for shared enums/constants +//---------------------------------------------------------------------------- + +// this enum is used by the llview.h (viewer) and the llassetstorage.h (viewer and sim) +enum EDragAndDropType +{ + DAD_NONE = 0, + DAD_TEXTURE = 1, + DAD_SOUND = 2, + DAD_CALLINGCARD = 3, + DAD_LANDMARK = 4, + DAD_SCRIPT = 5, + DAD_CLOTHING = 6, + DAD_OBJECT = 7, + DAD_NOTECARD = 8, + DAD_CATEGORY = 9, + DAD_ROOT_CATEGORY = 10, + DAD_BODYPART = 11, + DAD_ANIMATION = 12, + DAD_GESTURE = 13, + DAD_COUNT = 14, // number of types in this enum +}; + +// Reasons for drags to be denied. +// ordered by priority for multi-drag +enum EAcceptance +{ + ACCEPT_POSTPONED, // we are asynchronously determining acceptance + ACCEPT_NO, // Uninformative, general purpose denial. + ACCEPT_NO_LOCKED, // Operation would be valid, but permissions are set to disallow it. + ACCEPT_YES_COPY_SINGLE, // We'll take a copy of a single item + ACCEPT_YES_SINGLE, // Accepted. OK to drag and drop single item here. + ACCEPT_YES_COPY_MULTI, // We'll take a copy of multiple items + ACCEPT_YES_MULTI // Accepted. OK to drag and drop multiple items here. +}; + +// This is used by the DeRezObject message to determine where to put +// derezed tasks. +enum EDeRezDestination +{ + DRD_SAVE_INTO_AGENT_INVENTORY = 0, + DRD_ACQUIRE_TO_AGENT_INVENTORY = 1, // try to leave copy in world + DRD_SAVE_INTO_TASK_INVENTORY = 2, + DRD_ATTACHMENT = 3, + DRD_TAKE_INTO_AGENT_INVENTORY = 4, // delete from world + DRD_FORCE_TO_GOD_INVENTORY = 5, // force take copy + DRD_TRASH = 6, + DRD_ATTACHMENT_TO_INV = 7, + DRD_ATTACHMENT_EXISTS = 8, + DRD_RETURN_TO_OWNER = 9, // back to owner's inventory + DRD_RETURN_TO_LAST_OWNER = 10, // deeded object back to last owner's inventory + + DRD_COUNT = 11 +}; + + +// This is used by the return to owner code to determine the reason +// that this object is being returned. +enum EReturnReason +{ + RR_GENERIC = 0, + RR_SANDBOX = 1, + RR_PARCEL_OWNER = 2, + RR_PARCEL_AUTO = 3, + RR_PARCEL_FULL = 4, + RR_OFF_WORLD = 5, + + RR_COUNT = 6 +}; + +// This is used for filling in the first byte of the ExtraID field of +// the ObjectProperties message. +enum EObjectPropertiesExtraID +{ + OPEID_NONE = 0, + OPEID_ASSET_ID = 1, + OPEID_FROM_TASK_ID = 2, + + OPEID_COUNT = 3 +}; + +enum EAddPosition +{ + ADD_TOP, + ADD_SORTED, + ADD_BOTTOM +}; + +enum LLGroupChange +{ + GC_PROPERTIES, + GC_MEMBER_DATA, + GC_ROLE_DATA, + GC_ROLE_MEMBER_DATA, + GC_TITLES, + GC_ALL +}; + +//---------------------------------------------------------------------------- +// DEPRECATED - create new, more specific files for shared enums/constants +//---------------------------------------------------------------------------- + +#endif diff --git a/indra/llcommon/stdtypes.h b/indra/llcommon/stdtypes.h new file mode 100644 index 0000000000..b898475683 --- /dev/null +++ b/indra/llcommon/stdtypes.h @@ -0,0 +1,89 @@ +/** + * @file stdtypes.h + * @brief Basic type declarations for cross platform compatibility. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ +#ifndef LL_STDTYPES_H +#define LL_STDTYPES_H + +#include +#include + +typedef signed char S8; +typedef unsigned char U8; +typedef signed short S16; +typedef unsigned short U16; +typedef signed int S32; +typedef unsigned int U32; + +#if LL_WINDOWS +// Windows wchar_t is 16-bit +typedef U32 llwchar; +#else +typedef wchar_t llwchar; +#endif + +#if LL_WINDOWS +typedef signed __int64 S64; +// probably should be 'hyper' or similiar +#define S64L(a) (a) +typedef unsigned __int64 U64; +#define U64L(a) (a) +#else +typedef long long int S64; +typedef long long unsigned int U64; +#if LL_DARWIN || LL_LINUX +#define S64L(a) (a##LL) +#define U64L(a) (a##ULL) +#endif +#endif + +typedef float F32; +typedef double F64; + +typedef S32 BOOL; +typedef U8 KEY; +typedef U32 MASK; +typedef U32 TPACKETID; + +// Use #define instead of consts to avoid conversion headaches +#define S8_MAX (SCHAR_MAX) +#define U8_MAX (UCHAR_MAX) +#define S16_MAX (SHRT_MAX) +#define U16_MAX (USHRT_MAX) +#define S32_MAX (INT_MAX) +#define U32_MAX (UINT_MAX) +#define F32_MAX (FLT_MAX) +#define F64_MAX (DBL_MAX) + +#define S8_MIN (SCHAR_MIN) +#define U8_MIN (0) +#define S16_MIN (SHRT_MIN) +#define U16_MIN (0) +#define S32_MIN (INT_MIN) +#define U32_MIN (0) +#define F32_MIN (FLT_MIN) +#define F64_MIN (DBL_MIN) + + +#ifndef TRUE +#define TRUE (1) +#endif + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef NULL +#define NULL (0) +#endif + +typedef U8 LLPCode; + +#if LL_LINUX && __GNUC__ <= 2 +typedef int intptr_t; +#endif + +#endif diff --git a/indra/llcommon/string_table.h b/indra/llcommon/string_table.h new file mode 100644 index 0000000000..02ea4c34aa --- /dev/null +++ b/indra/llcommon/string_table.h @@ -0,0 +1,8 @@ +/** + * @file string_table.h + * @brief Legacy wrapper header. + * + * Copyright (c) 2000-$CurrentYear$ Linden Research, Inc. + * $License$ + */ +#include "llstringtable.h" diff --git a/indra/llcommon/timer.h b/indra/llcommon/timer.h new file mode 100644 index 0000000000..e4b799ee30 --- /dev/null +++ b/indra/llcommon/timer.h @@ -0,0 +1,8 @@ +/** + * @file timer.h + * @brief Legacy wrapper header. + * + * Copyright (c) 2000-$CurrentYear$ Linden Research, Inc. + * $License$ + */ +#include "lltimer.h" diff --git a/indra/llcommon/timing.cpp b/indra/llcommon/timing.cpp new file mode 100644 index 0000000000..2c8a214b01 --- /dev/null +++ b/indra/llcommon/timing.cpp @@ -0,0 +1,7 @@ +/** + * @file timing.cpp + * @brief This file will be deprecated in the future. + * + * Copyright (c) 2000-$CurrentYear$ Linden Research, Inc. + * $License$ + */ diff --git a/indra/llcommon/timing.h b/indra/llcommon/timing.h new file mode 100644 index 0000000000..f09fb0f92b --- /dev/null +++ b/indra/llcommon/timing.h @@ -0,0 +1,26 @@ +/** + * @file timing.h + * @brief Cross-platform routines for doing timing. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_TIMING_H +#define LL_TIMING_H + +#include + +#if LL_LINUX || LL_DARWIN +# include +#endif + + +const F32 SEC_TO_MICROSEC = 1000000.f; +const U64 SEC_TO_MICROSEC_U64 = 1000000; +const U32 SEC_PER_DAY = 86400; + +// This is just a stub, implementation in lltimer.cpp. This file will be deprecated in the future. +U64 totalTime(); // Returns current system time in microseconds + +#endif diff --git a/indra/llcommon/u64.cpp b/indra/llcommon/u64.cpp new file mode 100644 index 0000000000..c8b8bc4a28 --- /dev/null +++ b/indra/llcommon/u64.cpp @@ -0,0 +1,91 @@ +/** + * @file u64.cpp + * @brief Utilities to deal with U64s. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "u64.h" + + +U64 str_to_U64(const char *str) +{ + U64 result = 0; + char *aptr = strpbrk(str,"0123456789"); + + if (!aptr) + { + llwarns << "str_to_U64: Bad string to U64 conversion attempt: format\n" << llendl; + } + else + { + while ((*aptr >= '0') && (*aptr <= '9')) + { + result = result*10 + (*aptr++ - '0'); + } + } + return (result); +} + + +char* U64_to_str(U64 value, char* result, S32 result_size) +{ + U32 part1,part2,part3; + + part3 = (U32)(value % (U64)10000000); + + value /= 10000000; + part2 = (U32)(value % (U64)10000000); + + value /= 10000000; + part1 = (U32)(value % (U64)10000000); + + // three cases to avoid leading zeroes unless necessary + + if (part1) + { + snprintf( + result, + result_size, + "%u%07u%07u", + part1,part2,part3); /* Flawfinder: ignore */ + } + else if (part2) + { + snprintf( + result, + result_size, + "%u%07u", + part2,part3); /* Flawfinder: ignore */ + } + else + { + snprintf( + result, + result_size, + "%u", + part3); /* Flawfinder: ignore */ + } + return (result); +} + +F64 U64_to_F64(const U64 value) +{ + S64 top_bits = (S64)(value >> 1); + F64 result = (F64)top_bits; + result *= 2.f; + result += (U32)(value & 0x01); + return result; +} + +U64 llstrtou64(const char* str, char** end, S32 base) +{ +#ifdef LL_WINDOWS + return _strtoui64(str,end,base); +#else + return strtoull(str,end,base); +#endif +} diff --git a/indra/llcommon/u64.h b/indra/llcommon/u64.h new file mode 100644 index 0000000000..e659be189f --- /dev/null +++ b/indra/llcommon/u64.h @@ -0,0 +1,19 @@ +/** + * @file u64.h + * @brief Utilities to deal with U64s. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_U64_H +#define LL_U64_H + +U64 str_to_U64(const char* str); +char* U64_to_str(U64 value, char* result, S32 result_size); + +F64 U64_to_F64(const U64 value); + +U64 llstrtou64(const char* str, char** end, S32 base); + +#endif diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp new file mode 100644 index 0000000000..89b4a6d1cc --- /dev/null +++ b/indra/llimage/llimage.cpp @@ -0,0 +1,1772 @@ +/** + * @file llimage.cpp + * @brief Base class for images. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include +#include +#include +#include +#include + +#include "llmath.h" +#include "stdtypes.h" +#include "v4coloru.h" +#include "llmemory.h" + +#include "llimage.h" +#include "llimagebmp.h" +#include "llimagetga.h" +#include "llimagej2c.h" +#if JPEG_SUPPORT +#include "llimagejpeg.h" +#endif +#include "llimagedxt.h" + +//--------------------------------------------------------------------------- +// LLImageBase +//--------------------------------------------------------------------------- + +LLImageBase::LLImageBase() + : mData(NULL), + mDataSize(0), + mWidth(0), + mHeight(0), + mComponents(0), + mMemType(LLMemType::MTYPE_IMAGEBASE) +{ +} + +// virtual +LLImageBase::~LLImageBase() +{ + deleteData(); // virtual +} + +// virtual +void LLImageBase::dump() +{ + llinfos << "LLImageBase mComponents " << mComponents + << " mData " << mData + << " mDataSize " << mDataSize + << " mWidth " << mWidth + << " mHeight " << mHeight + << llendl; +} + +// virtual +void LLImageBase::sanityCheck() +{ + if (mWidth > MAX_IMAGE_SIZE + || mHeight > MAX_IMAGE_SIZE + || mDataSize > (S32)MAX_IMAGE_DATA_SIZE + || mComponents > (S8)MAX_IMAGE_COMPONENTS + ) + { + llerrs << "Failed LLImageBase::sanityCheck " + << "width " << mWidth + << "height " << mHeight + << "datasize " << mDataSize + << "components " << mComponents + << "data " << mData + << llendl; + } +} + +LLString LLImageBase::sLastErrorMessage; +BOOL LLImageBase::sSizeOverride = FALSE; + +BOOL LLImageBase::setLastError(const LLString& message, const LLString& filename) +{ + sLastErrorMessage = message; + if (filename != "") + { + sLastErrorMessage += LLString(" FILE:"); + sLastErrorMessage += filename; + } + llwarns << sLastErrorMessage << llendl; + return FALSE; +} + +// virtual +void LLImageBase::deleteData() +{ + delete[] mData; + mData = NULL; + mDataSize = 0; +} + +// virtual +U8* LLImageBase::allocateData(S32 size) +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + + if (size < 0) + { + size = mWidth * mHeight * mComponents; + if (size <= 0) + { + llerrs << llformat("LLImageBase::allocateData called with bad dimentions: %dx%dx%d",mWidth,mHeight,mComponents) << llendl; + } + } + else if ((size <= 0 || size > 4096*4096*16) && sSizeOverride == FALSE) + { + llerrs << "LLImageBase::allocateData: bad size: " << size << llendl; + } + + resetLastError(); + + if (!mData || size != mDataSize) + { + deleteData(); // virtual + mData = new U8[size]; + if (!mData) + { + llerrs << "allocate image data: " << size << llendl; + } + mDataSize = size; + } + + return mData; +} + +// virtual +U8* LLImageBase::reallocateData(S32 size) +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + U8 *new_datap = new U8[size]; + if (!new_datap) + { + llwarns << "Out of memory in LLImageBase::reallocateData" << llendl; + return 0; + } + if (mData) + { + S32 bytes = llmin(mDataSize, size); + memcpy(new_datap, mData, bytes); + delete[] mData; + } + mData = new_datap; + mDataSize = size; + return mData; +} + + +void LLImageBase::setSize(S32 width, S32 height, S32 ncomponents) +{ + mWidth = width; + mHeight = height; + mComponents = ncomponents; +} + +U8* LLImageBase::allocateDataSize(S32 width, S32 height, S32 ncomponents, S32 size) +{ + setSize(width, height, ncomponents); + return allocateData(size); // virtual +} + +//--------------------------------------------------------------------------- +// LLImageRaw +//--------------------------------------------------------------------------- + +S32 LLImageRaw::sGlobalRawMemory = 0; +S32 LLImageRaw::sRawImageCount = 0; + +LLImageRaw::LLImageRaw() + : LLImageBase() +{ + mMemType = LLMemType::MTYPE_IMAGERAW; + ++sRawImageCount; +} + +LLImageRaw::LLImageRaw(U16 width, U16 height, S8 components) + : LLImageBase() +{ + mMemType = LLMemType::MTYPE_IMAGERAW; + llassert( S32(width) * S32(height) * S32(components) <= MAX_IMAGE_DATA_SIZE ); + allocateDataSize(width, height, components); + ++sRawImageCount; +} + +LLImageRaw::LLImageRaw(U8 *data, U16 width, U16 height, S8 components) + : LLImageBase() +{ + mMemType = LLMemType::MTYPE_IMAGERAW; + copyData(data, width, height, components); + ++sRawImageCount; +} + +LLImageRaw::LLImageRaw(const LLString &filename, bool j2c_lowest_mip_only) + : LLImageBase() +{ + createFromFile(filename, j2c_lowest_mip_only); +} + +LLImageRaw::~LLImageRaw() +{ + // NOTE: ~LLimageBase() call to deleteData() calls LLImageBase::deleteData() + // NOT LLImageRaw::deleteData() + deleteData(); + --sRawImageCount; +} + +// virtual +U8* LLImageRaw::allocateData(S32 size) +{ + U8* res = LLImageBase::allocateData(size); + sGlobalRawMemory += getDataSize(); + return res; +} + +// virtual +U8* LLImageRaw::reallocateData(S32 size) +{ + sGlobalRawMemory -= getDataSize(); + U8* res = LLImageBase::reallocateData(size); + sGlobalRawMemory += getDataSize(); + return res; +} + +// virtual +void LLImageRaw::deleteData() +{ + sGlobalRawMemory -= getDataSize(); + LLImageBase::deleteData(); +} + +BOOL LLImageRaw::copyData(U8 *data, U16 width, U16 height, S8 components) +{ + if (!resize(width, height, components)) + { + return FALSE; + } + memcpy(getData(), data, width*height*components); + return TRUE; +} + +BOOL LLImageRaw::resize(U16 width, U16 height, S8 components) +{ + if ((getWidth() == width) && (getHeight() == height) && (getComponents() == components)) + { + return TRUE; + } + // Reallocate the data buffer. + deleteData(); + + allocateDataSize(width,height,components); + + return TRUE; +} + +U8 * LLImageRaw::getSubImage(U32 x_pos, U32 y_pos, U32 width, U32 height) const +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + U8 *data = new U8[width*height*getComponents()]; + + // Should do some simple bounds checking + + U32 i; + for (i = y_pos; i < y_pos+height; i++) + { + memcpy(data + i*width*getComponents(), + getData() + ((y_pos + i)*getWidth() + x_pos)*getComponents(), getComponents()*width); + } + return data; +} + +BOOL LLImageRaw::setSubImage(U32 x_pos, U32 y_pos, U32 width, U32 height, + const U8 *data, U32 stride, BOOL reverse_y) +{ + if (!getData()) + { + return FALSE; + } + if (!data) + { + return FALSE; + } + + // Should do some simple bounds checking + + U32 i; + U32 to_offset; + U32 from_offset; + if (!reverse_y) + { + for (i = 0; i < height; i++) + { + to_offset = (y_pos + i)*getWidth() + x_pos; + if (stride != 0) + { + from_offset = i*stride; + } + else + { + from_offset = i*width*getComponents(); + } + memcpy(getData() + to_offset*getComponents(), + data + from_offset, getComponents()*width); + } + } + else + { + for (i = 0; i < height; i++) + { + to_offset = (y_pos + i)*getWidth() + x_pos; + if (stride != 0) + { + from_offset = (height - 1 - i)*stride; + } + else + { + from_offset = (height - 1 - i)*width*getComponents(); + } + memcpy(getData() + to_offset*getComponents(), + data + from_offset, getComponents()*width); + } + } + return TRUE; +} + +void LLImageRaw::clear(U8 r, U8 g, U8 b, U8 a) +{ + llassert( getComponents() <= 4 ); + // This is fairly bogus, but it'll do for now. + U8 *pos = getData(); + U32 x, y; + for (x = 0; x < getWidth(); x++) + { + for (y = 0; y < getHeight(); y++) + { + *pos = r; + pos++; + if (getComponents() == 1) + { + continue; + } + *pos = g; + pos++; + if (getComponents() == 2) + { + continue; + } + *pos = b; + pos++; + if (getComponents() == 3) + { + continue; + } + *pos = a; + pos++; + } + } +} + +// Reverses the order of the rows in the image +void LLImageRaw::verticalFlip() +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + S32 row_bytes = getWidth() * getComponents(); + U8* line_buffer = new U8[row_bytes]; + S32 mid_row = getHeight() / 2; + for( S32 row = 0; row < mid_row; row++ ) + { + U8* row_a_data = getData() + row * row_bytes; + U8* row_b_data = getData() + (getHeight() - 1 - row) * row_bytes; + memcpy( line_buffer, row_a_data, row_bytes ); + memcpy( row_a_data, row_b_data, row_bytes ); + memcpy( row_b_data, line_buffer, row_bytes ); + } + delete[] line_buffer; +} + + +void LLImageRaw::expandToPowerOfTwo(S32 max_dim, BOOL scale_image) +{ + // Find new sizes + S32 new_width = MIN_IMAGE_SIZE; + S32 new_height = MIN_IMAGE_SIZE; + + while( (new_width < getWidth()) && (new_width < max_dim) ) + { + new_width <<= 1; + } + + while( (new_height < getHeight()) && (new_height < max_dim) ) + { + new_height <<= 1; + } + + scale( new_width, new_height, scale_image ); +} + +void LLImageRaw::contractToPowerOfTwo(S32 max_dim, BOOL scale_image) +{ + // Find new sizes + S32 new_width = max_dim; + S32 new_height = max_dim; + + while( (new_width > getWidth()) && (new_width > MIN_IMAGE_SIZE) ) + { + new_width >>= 1; + } + + while( (new_height > getHeight()) && (new_height > MIN_IMAGE_SIZE) ) + { + new_height >>= 1; + } + + scale( new_width, new_height, scale_image ); +} + +void LLImageRaw::biasedScaleToPowerOfTwo(S32 max_dim) +{ + // Strong bias towards rounding down (to save bandwidth) + // No bias would mean THRESHOLD == 1.5f; + const F32 THRESHOLD = 1.75f; + + // Find new sizes + S32 larger_w = max_dim; // 2^n >= mWidth + S32 smaller_w = max_dim; // 2^(n-1) <= mWidth + while( (smaller_w > getWidth()) && (smaller_w > MIN_IMAGE_SIZE) ) + { + larger_w = smaller_w; + smaller_w >>= 1; + } + S32 new_width = ( (F32)getWidth() / smaller_w > THRESHOLD ) ? larger_w : smaller_w; + + + S32 larger_h = max_dim; // 2^m >= mHeight + S32 smaller_h = max_dim; // 2^(m-1) <= mHeight + while( (smaller_h > getHeight()) && (smaller_h > MIN_IMAGE_SIZE) ) + { + larger_h = smaller_h; + smaller_h >>= 1; + } + S32 new_height = ( (F32)getHeight() / smaller_h > THRESHOLD ) ? larger_h : smaller_h; + + + scale( new_width, new_height ); +} + + + + +// Calculates (U8)(255*(a/255.f)*(b/255.f) + 0.5f). Thanks, Jim Blinn! +inline U8 LLImageRaw::fastFractionalMult( U8 a, U8 b ) +{ + U32 i = a * b + 128; + return U8((i + (i>>8)) >> 8); +} + + +void LLImageRaw::composite( LLImageRaw* src ) +{ + LLImageRaw* dst = this; // Just for clarity. + + llassert( (3 == src->getComponents()) || (4 == src->getComponents()) ); + llassert( (3 == dst->getComponents()) || (4 == dst->getComponents()) ); + + if( 3 == dst->getComponents() ) + { + if( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) ) + { + // No scaling needed + if( 3 == src->getComponents() ) + { + copyUnscaled( src ); // alpha is one so just copy the data. + } + else + { + compositeUnscaled4onto3( src ); + } + } + else + { + if( 3 == src->getComponents() ) + { + copyScaled( src ); // alpha is one so just copy the data. + } + else + { + compositeScaled4onto3( src ); + } + } + } + else + { + // 4 == dst->mComponents + llassert(0); // not implemented yet. + } +} + +// Src and dst can be any size. Src has 4 components. Dst has 3 components. +void LLImageRaw::compositeScaled4onto3(LLImageRaw* src) +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + llinfos << "compositeScaled4onto3" << llendl; + + LLImageRaw* dst = this; // Just for clarity. + + llassert( (4 == src->getComponents()) && (3 == dst->getComponents()) ); + + // Vertical: scale but no composite + S32 temp_data_size = src->getWidth() * dst->getHeight() * src->getComponents(); + U8* temp_buffer = new U8[ temp_data_size ]; + for( S32 col = 0; col < src->getWidth(); col++ ) + { + copyLineScaled( src->getData() + (src->getComponents() * col), temp_buffer + (src->getComponents() * col), src->getHeight(), dst->getHeight(), src->getWidth(), src->getWidth() ); + } + + // Horizontal: scale and composite + for( S32 row = 0; row < dst->getHeight(); row++ ) + { + compositeRowScaled4onto3( temp_buffer + (src->getComponents() * src->getWidth() * row), dst->getData() + (dst->getComponents() * dst->getWidth() * row), src->getWidth(), dst->getWidth() ); + } + + // Clean up + delete[] temp_buffer; +} + + +// Src and dst are same size. Src has 4 components. Dst has 3 components. +void LLImageRaw::compositeUnscaled4onto3( LLImageRaw* src ) +{ + /* + //test fastFractionalMult() + { + U8 i = 255; + U8 j = 255; + do + { + do + { + llassert( fastFractionalMult(i, j) == (U8)(255*(i/255.f)*(j/255.f) + 0.5f) ); + } while( j-- ); + } while( i-- ); + } + */ + + LLImageRaw* dst = this; // Just for clarity. + + llassert( (3 == src->getComponents()) || (4 == src->getComponents()) ); + llassert( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) ); + + + U8* src_data = src->getData(); + U8* dst_data = dst->getData(); + S32 pixels = getWidth() * getHeight(); + while( pixels-- ) + { + U8 alpha = src_data[3]; + if( alpha ) + { + if( 255 == alpha ) + { + dst_data[0] = src_data[0]; + dst_data[1] = src_data[1]; + dst_data[2] = src_data[2]; + } + else + { + + U8 transparency = 255 - alpha; + dst_data[0] = fastFractionalMult( dst_data[0], transparency ) + fastFractionalMult( src_data[0], alpha ); + dst_data[1] = fastFractionalMult( dst_data[1], transparency ) + fastFractionalMult( src_data[1], alpha ); + dst_data[2] = fastFractionalMult( dst_data[2], transparency ) + fastFractionalMult( src_data[2], alpha ); + } + } + + src_data += 4; + dst_data += 3; + } +} + +// Fill the buffer with a constant color +void LLImageRaw::fill( const LLColor4U& color ) +{ + S32 pixels = getWidth() * getHeight(); + if( 4 == getComponents() ) + { + U32* data = (U32*) getData(); + for( S32 i = 0; i < pixels; i++ ) + { + data[i] = color.mAll; + } + } + else + if( 3 == getComponents() ) + { + U8* data = getData(); + for( S32 i = 0; i < pixels; i++ ) + { + data[0] = color.mV[0]; + data[1] = color.mV[1]; + data[2] = color.mV[2]; + data += 3; + } + } +} + + + + +// Src and dst can be any size. Src and dst can each have 3 or 4 components. +void LLImageRaw::copy(LLImageRaw* src) +{ + LLImageRaw* dst = this; // Just for clarity. + + llassert( (3 == src->getComponents()) || (4 == src->getComponents()) ); + llassert( (3 == dst->getComponents()) || (4 == dst->getComponents()) ); + + if( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) ) + { + // No scaling needed + if( src->getComponents() == dst->getComponents() ) + { + copyUnscaled( src ); + } + else + if( 3 == src->getComponents() ) + { + copyUnscaled3onto4( src ); + } + else + { + // 4 == src->getComponents() + copyUnscaled4onto3( src ); + } + } + else + { + // Scaling needed + // No scaling needed + if( src->getComponents() == dst->getComponents() ) + { + copyScaled( src ); + } + else + if( 3 == src->getComponents() ) + { + copyScaled3onto4( src ); + } + else + { + // 4 == src->getComponents() + copyScaled4onto3( src ); + } + } +} + +// Src and dst are same size. Src and dst have same number of components. +void LLImageRaw::copyUnscaled(LLImageRaw* src) +{ + LLImageRaw* dst = this; // Just for clarity. + + llassert( (3 == src->getComponents()) || (4 == src->getComponents()) ); + llassert( src->getComponents() == dst->getComponents() ); + llassert( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) ); + + memcpy( dst->getData(), src->getData(), getWidth() * getHeight() * getComponents() ); +} + + +// Src and dst can be any size. Src has 3 components. Dst has 4 components. +void LLImageRaw::copyScaled3onto4(LLImageRaw* src) +{ + llassert( (3 == src->getComponents()) && (4 == getComponents()) ); + + // Slow, but simple. Optimize later if needed. + LLImageRaw temp( src->getWidth(), src->getHeight(), 4); + temp.copyUnscaled3onto4( src ); + copyScaled( &temp ); +} + + +// Src and dst can be any size. Src has 4 components. Dst has 3 components. +void LLImageRaw::copyScaled4onto3(LLImageRaw* src) +{ + llassert( (4 == src->getComponents()) && (3 == getComponents()) ); + + // Slow, but simple. Optimize later if needed. + LLImageRaw temp( src->getWidth(), src->getHeight(), 3); + temp.copyUnscaled4onto3( src ); + copyScaled( &temp ); +} + + +// Src and dst are same size. Src has 4 components. Dst has 3 components. +void LLImageRaw::copyUnscaled4onto3( LLImageRaw* src ) +{ + LLImageRaw* dst = this; // Just for clarity. + + llassert( (3 == dst->getComponents()) && (4 == src->getComponents()) ); + llassert( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) ); + + S32 pixels = getWidth() * getHeight(); + U8* src_data = src->getData(); + U8* dst_data = dst->getData(); + for( S32 i=0; igetComponents() ); + llassert( 4 == dst->getComponents() ); + llassert( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) ); + + S32 pixels = getWidth() * getHeight(); + U8* src_data = src->getData(); + U8* dst_data = dst->getData(); + for( S32 i=0; igetComponents()) || (4 == src->getComponents()) ); + llassert( src->getComponents() == dst->getComponents() ); + + if( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) ) + { + memcpy( dst->getData(), src->getData(), getWidth() * getHeight() * getComponents() ); + return; + } + + // Vertical + S32 temp_data_size = src->getWidth() * dst->getHeight() * getComponents(); + U8* temp_buffer = new U8[ temp_data_size ]; + for( S32 col = 0; col < src->getWidth(); col++ ) + { + copyLineScaled( src->getData() + (getComponents() * col), temp_buffer + (getComponents() * col), src->getHeight(), dst->getHeight(), src->getWidth(), src->getWidth() ); + } + + // Horizontal + for( S32 row = 0; row < dst->getHeight(); row++ ) + { + copyLineScaled( temp_buffer + (getComponents() * src->getWidth() * row), dst->getData() + (getComponents() * dst->getWidth() * row), src->getWidth(), dst->getWidth(), 1, 1 ); + } + + // Clean up + delete[] temp_buffer; +} + + +void LLImageRaw::scale( S32 new_width, S32 new_height, BOOL scale_image_data ) +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + llassert( (3 == getComponents()) || (4 == getComponents()) ); + + S32 old_width = getWidth(); + S32 old_height = getHeight(); + + if( (old_width == new_width) && (old_height == new_height) ) + { + return; // Nothing to do. + } + + // Reallocate the data buffer. + + if (scale_image_data) + { + // Vertical + S32 temp_data_size = old_width * new_height * getComponents(); + U8* temp_buffer = new U8[ temp_data_size ]; + for( S32 col = 0; col < old_width; col++ ) + { + copyLineScaled( getData() + (getComponents() * col), temp_buffer + (getComponents() * col), old_height, new_height, old_width, old_width ); + } + + deleteData(); + + U8* new_buffer = allocateDataSize(new_width, new_height, getComponents()); + + // Horizontal + for( S32 row = 0; row < new_height; row++ ) + { + copyLineScaled( temp_buffer + (getComponents() * old_width * row), new_buffer + (getComponents() * new_width * row), old_width, new_width, 1, 1 ); + } + + // Clean up + delete[] temp_buffer; + } + else + { + // copy out existing image data + S32 temp_data_size = old_width * old_height * getComponents(); + U8* temp_buffer = new U8[ temp_data_size ]; + memcpy(temp_buffer, getData(), temp_data_size); + + // allocate new image data, will delete old data + U8* new_buffer = allocateDataSize(new_width, new_height, getComponents()); + + for( S32 row = 0; row < new_height; row++ ) + { + if (row < old_height) + { + memcpy(new_buffer + (new_width * row * getComponents()), temp_buffer + (old_width * row * getComponents()), getComponents() * llmin(old_width, new_width)); + if (old_width < new_width) + { + // pad out rest of row with black + memset(new_buffer + (getComponents() * ((new_width * row) + old_width)), 0, getComponents() * (new_width - old_width)); + } + } + else + { + // pad remaining rows with black + memset(new_buffer + (new_width * row * getComponents()), 0, new_width * getComponents()); + } + } + + // Clean up + delete[] temp_buffer; + } +} + +void LLImageRaw::copyLineScaled( U8* in, U8* out, S32 in_pixel_len, S32 out_pixel_len, S32 in_pixel_step, S32 out_pixel_step ) +{ + const S32 components = getComponents(); + llassert( components >= 1 && components <= 4 ); + + const F32 ratio = F32(in_pixel_len) / out_pixel_len; // ratio of old to new + const F32 norm_factor = 1.f / ratio; + + S32 goff = components >= 2 ? 1 : 0; + S32 boff = components >= 3 ? 2 : 0; + for( S32 x = 0; x < out_pixel_len; x++ ) + { + // Sample input pixels in range from sample0 to sample1. + // Avoid floating point accumulation error... don't just add ratio each time. JC + const F32 sample0 = x * ratio; + const F32 sample1 = (x+1) * ratio; + const S32 index0 = llfloor(sample0); // left integer (floor) + const S32 index1 = llfloor(sample1); // right integer (floor) + const F32 fract0 = 1.f - (sample0 - F32(index0)); // spill over on left + const F32 fract1 = sample1 - F32(index1); // spill-over on right + + if( index0 == index1 ) + { + // Interval is embedded in one input pixel + S32 t0 = x * out_pixel_step * components; + S32 t1 = index0 * in_pixel_step * components; + U8* outp = out + t0; + U8* inp = in + t1; + for (S32 i = 0; i < components; ++i) + { + *outp = *inp; + ++outp; + ++inp; + } + } + else + { + // Left straddle + S32 t1 = index0 * in_pixel_step * components; + F32 r = in[t1 + 0] * fract0; + F32 g = in[t1 + goff] * fract0; + F32 b = in[t1 + boff] * fract0; + F32 a = 0; + if( components == 4) + { + a = in[t1 + 3] * fract0; + } + + // Central interval + if (components < 4) + { + for( S32 u = index0 + 1; u < index1; u++ ) + { + S32 t2 = u * in_pixel_step * components; + r += in[t2 + 0]; + g += in[t2 + goff]; + b += in[t2 + boff]; + } + } + else + { + for( S32 u = index0 + 1; u < index1; u++ ) + { + S32 t2 = u * in_pixel_step * components; + r += in[t2 + 0]; + g += in[t2 + 1]; + b += in[t2 + 2]; + a += in[t2 + 3]; + } + } + + // right straddle + // Watch out for reading off of end of input array. + if( fract1 && index1 < in_pixel_len ) + { + S32 t3 = index1 * in_pixel_step * components; + if (components < 4) + { + U8 in0 = in[t3 + 0]; + U8 in1 = in[t3 + goff]; + U8 in2 = in[t3 + boff]; + r += in0 * fract1; + g += in1 * fract1; + b += in2 * fract1; + } + else + { + U8 in0 = in[t3 + 0]; + U8 in1 = in[t3 + 1]; + U8 in2 = in[t3 + 2]; + U8 in3 = in[t3 + 3]; + r += in0 * fract1; + g += in1 * fract1; + b += in2 * fract1; + a += in3 * fract1; + } + } + + r *= norm_factor; + g *= norm_factor; + b *= norm_factor; + a *= norm_factor; // skip conditional + + S32 t4 = x * out_pixel_step * components; + out[t4 + 0] = U8(llround(r)); + if (components >= 2) + out[t4 + 1] = U8(llround(g)); + if (components >= 3) + out[t4 + 2] = U8(llround(b)); + if( components == 4) + out[t4 + 3] = U8(llround(a)); + } + } +} + +void LLImageRaw::compositeRowScaled4onto3( U8* in, U8* out, S32 in_pixel_len, S32 out_pixel_len ) +{ + llassert( getComponents() == 3 ); + + const S32 IN_COMPONENTS = 4; + const S32 OUT_COMPONENTS = 3; + + const F32 ratio = F32(in_pixel_len) / out_pixel_len; // ratio of old to new + const F32 norm_factor = 1.f / ratio; + + for( S32 x = 0; x < out_pixel_len; x++ ) + { + // Sample input pixels in range from sample0 to sample1. + // Avoid floating point accumulation error... don't just add ratio each time. JC + const F32 sample0 = x * ratio; + const F32 sample1 = (x+1) * ratio; + const S32 index0 = S32(sample0); // left integer (floor) + const S32 index1 = S32(sample1); // right integer (floor) + const F32 fract0 = 1.f - (sample0 - F32(index0)); // spill over on left + const F32 fract1 = sample1 - F32(index1); // spill-over on right + + U8 in_scaled_r; + U8 in_scaled_g; + U8 in_scaled_b; + U8 in_scaled_a; + + if( index0 == index1 ) + { + // Interval is embedded in one input pixel + S32 t1 = index0 * IN_COMPONENTS; + in_scaled_r = in[t1 + 0]; + in_scaled_g = in[t1 + 0]; + in_scaled_b = in[t1 + 0]; + in_scaled_a = in[t1 + 0]; + } + else + { + // Left straddle + S32 t1 = index0 * IN_COMPONENTS; + F32 r = in[t1 + 0] * fract0; + F32 g = in[t1 + 1] * fract0; + F32 b = in[t1 + 2] * fract0; + F32 a = in[t1 + 3] * fract0; + + // Central interval + for( S32 u = index0 + 1; u < index1; u++ ) + { + S32 t2 = u * IN_COMPONENTS; + r += in[t2 + 0]; + g += in[t2 + 1]; + b += in[t2 + 2]; + a += in[t2 + 3]; + } + + // right straddle + // Watch out for reading off of end of input array. + if( fract1 && index1 < in_pixel_len ) + { + S32 t3 = index1 * IN_COMPONENTS; + r += in[t3 + 0] * fract1; + g += in[t3 + 1] * fract1; + b += in[t3 + 2] * fract1; + a += in[t3 + 3] * fract1; + } + + r *= norm_factor; + g *= norm_factor; + b *= norm_factor; + a *= norm_factor; + + in_scaled_r = U8(llround(r)); + in_scaled_g = U8(llround(g)); + in_scaled_b = U8(llround(b)); + in_scaled_a = U8(llround(a)); + } + + if( in_scaled_a ) + { + if( 255 == in_scaled_a ) + { + out[0] = in_scaled_r; + out[1] = in_scaled_g; + out[2] = in_scaled_b; + } + else + { + U8 transparency = 255 - in_scaled_a; + out[0] = fastFractionalMult( out[0], transparency ) + fastFractionalMult( in_scaled_r, in_scaled_a ); + out[1] = fastFractionalMult( out[1], transparency ) + fastFractionalMult( in_scaled_g, in_scaled_a ); + out[2] = fastFractionalMult( out[2], transparency ) + fastFractionalMult( in_scaled_b, in_scaled_a ); + } + } + out += OUT_COMPONENTS; + } +} + + +//---------------------------------------------------------------------------- + +static struct +{ + const char* exten; + S8 codec; +} +file_extensions[] = +{ + { "bmp", IMG_CODEC_BMP }, + { "tga", IMG_CODEC_TGA }, + { "j2c", IMG_CODEC_J2C }, + { "jp2", IMG_CODEC_J2C }, + { "texture", IMG_CODEC_J2C }, + { "jpg", IMG_CODEC_JPEG }, + { "jpeg", IMG_CODEC_JPEG }, + { "mip", IMG_CODEC_DXT }, + { "dxt", IMG_CODEC_DXT } +}; +#define NUM_FILE_EXTENSIONS sizeof(file_extensions)/sizeof(file_extensions[0]) + +static LLString find_file(LLString &name, S8 *codec) +{ + LLString tname; + for (int i=0; i<(int)(NUM_FILE_EXTENSIONS); i++) + { + tname = name + "." + LLString(file_extensions[i].exten); + llifstream ifs(tname.c_str(), llifstream::binary); + if (ifs.is_open()) + { + ifs.close(); + if (codec) + *codec = file_extensions[i].codec; + return LLString(file_extensions[i].exten); + } + } + return LLString(""); +} + +static S8 get_codec(const LLString& exten) +{ + for (int i=0; i<(int)(NUM_FILE_EXTENSIONS); i++) + { + if (exten == file_extensions[i].exten) + return file_extensions[i].codec; + } + return IMG_CODEC_INVALID; +} + +bool LLImageRaw::createFromFile(const LLString &filename, bool j2c_lowest_mip_only) +{ + LLString name = filename; + size_t dotidx = name.rfind('.'); + S8 codec = IMG_CODEC_INVALID; + LLString exten; + + deleteData(); // delete any existing data + + if (dotidx != LLString::npos) + { + exten = name.substr(dotidx+1); + LLString::toLower(exten); + codec = get_codec(exten); + } + else + { + exten = find_file(name, &codec); + name = name + "." + exten; + } + if (codec == IMG_CODEC_INVALID) + { + return false; // format not recognized + } + + llifstream ifs(name.c_str(), llifstream::binary); + if (!ifs.is_open()) + { + // SJB: changed from llinfos to lldebugs to reduce spam + lldebugs << "Unable to open image file: " << name << llendl; + return false; + } + + ifs.seekg (0, std::ios::end); + int length = ifs.tellg(); + if (j2c_lowest_mip_only && length > 2048) + { + length = 2048; + } + ifs.seekg (0, std::ios::beg); + + if (!length) + { + llinfos << "Zero length file file: " << name << llendl; + return false; + } + + LLPointer image; + switch(codec) + { + //case IMG_CODEC_RGB: + case IMG_CODEC_BMP: + image = new LLImageBMP(); + break; + case IMG_CODEC_TGA: + image = new LLImageTGA(); + break; +#if JPEG_SUPPORT + case IMG_CODEC_JPEG: + image = new LLImageJPEG(); + break; +#endif + case IMG_CODEC_J2C: + image = new LLImageJ2C(); + break; + case IMG_CODEC_DXT: + image = new LLImageDXT(); + break; + default: + return false; + } + llassert(image.notNull()); + + U8 *buffer = image->allocateData(length); + ifs.read ((char*)buffer, length); + ifs.close(); + + image->updateData(); + + if (j2c_lowest_mip_only && codec == IMG_CODEC_J2C) + { + S32 width = image->getWidth(); + S32 height = image->getHeight(); + S32 discard_level = 0; + while (width > 1 && height > 1 && discard_level < MAX_DISCARD_LEVEL) + { + width >>= 1; + height >>= 1; + discard_level++; + } + ((LLImageJ2C *)((LLImageFormatted*)image))->setDiscardLevel(discard_level); + } + + BOOL success = image->decode(this, 100000.0f); + image = NULL; // deletes image + + if (!success) + { + deleteData(); + llwarns << "Unable to decode image" << name << llendl; + return false; + } + + return true; +} + +//--------------------------------------------------------------------------- +// LLImageFormatted +//--------------------------------------------------------------------------- + +//static +S32 LLImageFormatted::sGlobalFormattedMemory = 0; + +//static +LLWorkerThread* LLImageFormatted::sWorkerThread = NULL; + +//static +void LLImageFormatted::initClass(bool threaded, bool run_always) +{ + sWorkerThread = new LLWorkerThread(threaded, run_always); +} + +//static +void LLImageFormatted::cleanupClass() +{ + delete sWorkerThread; + sWorkerThread = NULL; +} + + +LLImageFormatted::LLImageFormatted(S8 codec) + : LLImageBase(), LLWorkerClass(sWorkerThread, "ImageFormatted"), + mCodec(codec), + mDecoding(0), + mDecoded(0), + mDiscardLevel(0) +{ + mMemType = LLMemType::MTYPE_IMAGEFORMATTED; +} + +// virtual +LLImageFormatted::~LLImageFormatted() +{ + // NOTE: ~LLimageBase() call to deleteData() calls LLImageBase::deleteData() + // NOT LLImageFormatted::deleteData() + deleteData(); + releaseDecodedData(); +} + +//---------------------------------------------------------------------------- + +//virtual +void LLImageFormatted::startWork(S32 param) +{ + if (mDecoding) llerrs << "WTF?" << llendl; +} + +bool LLImageFormatted::doWork(S32 param) +{ + if (!(isWorking())) llerrs << "WTF?" << llendl; + llassert(mDecodedImage.notNull()); + if (param == 0) + { + // Decode primary channels + mDecoded = decode(mDecodedImage, .001f); // 1ms + } + else + { + // Decode aux channel + mDecoded = decode(mDecodedImage, .001f, param, param); // 1ms + } + if (mDecoded) + { + return true; + } + else + { + return false; + } +} + +void LLImageFormatted::endWork(S32 param, bool aborted) +{ + if (mDecoding) llerrs << "WTF?" << llendl; + if (!mDecoded) llerrs << "WTF?" << llendl; +} + +//---------------------------------------------------------------------------- + +// static +LLImageFormatted* LLImageFormatted::createFromExtension(const LLString& instring) +{ + LLString exten; + size_t dotidx = instring.rfind('.'); + if (dotidx != LLString::npos) + { + exten = instring.substr(dotidx+1); + } + else + { + exten = instring; + } + S8 codec = get_codec(exten); + LLPointer image; + switch(codec) + { + case IMG_CODEC_BMP: + image = new LLImageBMP(); + break; + case IMG_CODEC_TGA: + image = new LLImageTGA(); + break; +#if JPEG_SUPPORT + case IMG_CODEC_JPEG: + image = new LLImageJPEG(); + break; +#endif + case IMG_CODEC_J2C: + image = new LLImageJ2C(); + break; + case IMG_CODEC_DXT: + image = new LLImageDXT(); + break; + default: + break; + } + return image; +} +//---------------------------------------------------------------------------- + +// virtual +void LLImageFormatted::dump() +{ + LLImageBase::dump(); + + llinfos << "LLImageFormatted" + << " mDecoding " << mDecoding + << " mCodec " << S32(mCodec) + << " mDecoded " << mDecoded + << llendl; +} + +//---------------------------------------------------------------------------- + +void LLImageFormatted::readHeader(U8* data, S32 size) +{ + if (size <= 0) + { + size = calcHeaderSize(); + } + copyData(data, size); // calls updateData() +} + +S32 LLImageFormatted::calcDataSize(S32 discard_level) +{ + if (discard_level < 0) + { + discard_level = mDiscardLevel; + } + S32 w = getWidth() >> discard_level; + S32 h = getHeight() >> discard_level; + w = llmax(w, 1); + h = llmax(h, 1); + return w * h * getComponents(); +} + +S32 LLImageFormatted::calcDiscardLevelBytes(S32 bytes) +{ + llassert(bytes >= 0); + S32 discard_level = 0; + while (1) + { + S32 bytes_needed = calcDataSize(discard_level); // virtual + if (bytes_needed <= bytes) + { + break; + } + discard_level++; + if (discard_level > MAX_IMAGE_MIP) + { + return -1; + } + } + return discard_level; +} + + +//---------------------------------------------------------------------------- + +// Subclasses that can handle more than 4 channels should override this function. +BOOL LLImageFormatted::decode(LLImageRaw* raw_image,F32 decode_time, S32 first_channel, S32 max_channel) +{ + llassert( (first_channel == 0) && (max_channel == 4) ); + return decode( raw_image, decode_time ); // Loads first 4 channels by default. +} + +// virtual +BOOL LLImageFormatted::requestDecodedData(LLPointer& raw, S32 discard, F32 decode_time) +{ + llassert(getData() && getDataSize()); + // For most codecs, only mDiscardLevel data is available. (see LLImageDXT for exception) + if (discard >= 0 && discard != mDiscardLevel) + { + llerrs << "Request for invalid discard level" << llendl; + } + if (haveWork()) + { + checkWork(); + } + if (!mDecoded) + { + if (!haveWork()) + { + llassert(!mDecoding); + mDecodedImage = new LLImageRaw(getWidth(), getHeight(), getComponents()); + addWork(0); + } + return FALSE; + } + else + { + llassert(mDecodedImage.notNull()); + llassert(!mDecoding); + raw = mDecodedImage; + return TRUE; + } +} + +BOOL LLImageFormatted::requestDecodedAuxData(LLPointer& raw, S32 channel, + S32 discard, F32 decode_time) +{ + llassert(getData() && getDataSize()); + // For most codecs, only mDiscardLevel data is available. (see LLImageDXT for exception) + if (discard >= 0 && discard != mDiscardLevel) + { + llerrs << "Request for invalid discard level" << llendl; + } + if (haveWork()) + { + checkWork(); + } + if (!mDecoded) + { + if (!haveWork()) + { + llassert(!mDecoding); + mDecodedImage = new LLImageRaw(getWidth(), getHeight(), 1); + addWork(channel); + } + return FALSE; + } + else + { + llassert(mDecodedImage.notNull()); + llassert(!mDecoding); + raw = mDecodedImage; + return TRUE; + } +} + + +// virtual +void LLImageFormatted::releaseDecodedData() +{ + if (mDecoded || mDecoding) + { + mDecodedImage = NULL; // deletes image + mDecoded = FALSE; + mDecoding = FALSE; + } +} + +//---------------------------------------------------------------------------- + +// virtual +U8* LLImageFormatted::allocateData(S32 size) +{ + U8* res = LLImageBase::allocateData(size); // calls deleteData() + sGlobalFormattedMemory += getDataSize(); + return res; +} + +// virtual +U8* LLImageFormatted::reallocateData(S32 size) +{ + sGlobalFormattedMemory -= getDataSize(); + U8* res = LLImageBase::reallocateData(size); + sGlobalFormattedMemory += getDataSize(); + return res; +} + +// virtual +void LLImageFormatted::deleteData() +{ + sGlobalFormattedMemory -= getDataSize(); + LLImageBase::deleteData(); +} + +//---------------------------------------------------------------------------- + +// virtual +void LLImageFormatted::sanityCheck() +{ + LLImageBase::sanityCheck(); + + if (mCodec >= IMG_CODEC_EOF) + { + llerrs << "Failed LLImageFormatted::sanityCheck " + << "decoding " << S32(mDecoding) + << "decoded " << S32(mDecoded) + << "codec " << S32(mCodec) + << llendl; + } +} + +//---------------------------------------------------------------------------- + +BOOL LLImageFormatted::copyData(U8 *data, S32 size) +{ + if (data && data != getData()) + { + deleteData(); + allocateData(size); + memcpy(getData(), data, size); + } + updateData(); // virtual + + return TRUE; +} + +BOOL LLImageFormatted::appendData(U8 *data, S32 size) +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + S32 old_size = getDataSize(); + U8* old_data = getData(); + S32 new_size = old_size + size; + U8* new_data = new U8[new_size]; + // resize the image + setDataAndSize(new_data, new_size); + // copy the old data and delete it + memcpy(new_data, old_data, old_size); + delete old_data; + // if we have new data, copy it and call updateData() + if (data) + { + memcpy(new_data + old_size, data, size); + updateData(); // virtual + } + return TRUE; +} + +BOOL LLImageFormatted::setData(U8 *data, S32 size) +{ + if (data && data != getData()) + { + deleteData(); + setDataAndSize(data, size); // Access private LLImageBase members + sGlobalFormattedMemory += getDataSize(); + } + return updateData(); // virtual +} + +//---------------------------------------------------------------------------- + +BOOL LLImageFormatted::load(const LLString &filename) +{ + resetLastError(); + + S32 file_size = 0; + apr_file_t* apr_file = ll_apr_file_open(filename, LL_APR_RB, &file_size); + if (!apr_file) + { + setLastError("Unable to open file for reading", filename); + return FALSE; + } + if (file_size == 0) + { + setLastError("File is empty",filename); + apr_file_close(apr_file); + return FALSE; + } + + BOOL res; + U8 *data = allocateData(file_size); + apr_size_t bytes_read = file_size; + apr_status_t s = apr_file_read(apr_file, data, &bytes_read); // modifies bytes_read + if (s != APR_SUCCESS || (S32) bytes_read != file_size) + { + deleteData(); + setLastError("Unable to read entire file",filename); + res = FALSE; + } + else + { + res = updateData(); + } + apr_file_close(apr_file); + + return res; +} + +BOOL LLImageFormatted::save(const LLString &filename) +{ + resetLastError(); + + apr_file_t* apr_file = ll_apr_file_open(filename, LL_APR_WB); + if (!apr_file) + { + setLastError("Unable to open file for reading", filename); + return FALSE; + } + + ll_apr_file_write(apr_file, getData(), getDataSize()); + apr_file_close(apr_file); + + return TRUE; +} + +// BOOL LLImageFormatted::save(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type) +// Depricated to remove VFS dependency. +// Use: +// LLVFile::writeFile(image->getData(), image->getDataSize(), vfs, uuid, type); + +//---------------------------------------------------------------------------- + +S8 LLImageFormatted::getCodec() const +{ + return mCodec; +} + +//============================================================================ + +//---------------------------------------------------------------------------- + +static void avg4_colors4(const U8* a, const U8* b, const U8* c, const U8* d, U8* dst) +{ + dst[0] = (U8)(((U32)(a[0]) + b[0] + c[0] + d[0])>>2); + dst[1] = (U8)(((U32)(a[1]) + b[1] + c[1] + d[1])>>2); + dst[2] = (U8)(((U32)(a[2]) + b[2] + c[2] + d[2])>>2); + dst[3] = (U8)(((U32)(a[3]) + b[3] + c[3] + d[3])>>2); +} + +static void avg4_colors3(const U8* a, const U8* b, const U8* c, const U8* d, U8* dst) +{ + dst[0] = (U8)(((U32)(a[0]) + b[0] + c[0] + d[0])>>2); + dst[1] = (U8)(((U32)(a[1]) + b[1] + c[1] + d[1])>>2); + dst[2] = (U8)(((U32)(a[2]) + b[2] + c[2] + d[2])>>2); +} + +static void avg4_colors2(const U8* a, const U8* b, const U8* c, const U8* d, U8* dst) +{ + dst[0] = (U8)(((U32)(a[0]) + b[0] + c[0] + d[0])>>2); + dst[1] = (U8)(((U32)(a[1]) + b[1] + c[1] + d[1])>>2); +} + +//static +void LLImageBase::generateMip(const U8* indata, U8* mipdata, S32 width, S32 height, S32 nchannels) +{ + llassert(width > 0 && height > 0); + U8* data = mipdata; + S32 in_width = width*2; + for (S32 h=0; h>2); + break; + default: + llerrs << "generateMmip called with bad num channels" << llendl; + } + indata += nchannels*2; + data += nchannels; + } + indata += nchannels*in_width; // skip odd lines + } +} + + +//============================================================================ + +//static +F32 LLImageBase::calc_download_priority(F32 virtual_size, F32 visible_pixels, S32 bytes_sent) +{ + F32 w_priority; + + F32 bytes_weight = 1.f; + if (!bytes_sent) + { + bytes_weight = 20.f; + } + else if (bytes_sent < 1000) + { + bytes_weight = 1.f; + } + else if (bytes_sent < 2000) + { + bytes_weight = 1.f/1.5f; + } + else if (bytes_sent < 4000) + { + bytes_weight = 1.f/3.f; + } + else if (bytes_sent < 8000) + { + bytes_weight = 1.f/6.f; + } + else if (bytes_sent < 16000) + { + bytes_weight = 1.f/12.f; + } + else if (bytes_sent < 32000) + { + bytes_weight = 1.f/20.f; + } + else if (bytes_sent < 64000) + { + bytes_weight = 1.f/32.f; + } + else + { + bytes_weight = 1.f/64.f; + } + bytes_weight *= bytes_weight; + + + //llinfos << "VS: " << virtual_size << llendl; + F32 virtual_size_factor = virtual_size / (10.f*10.f); + + // The goal is for weighted priority to be <= 0 when we've reached a point where + // we've sent enough data. + //llinfos << "BytesSent: " << bytes_sent << llendl; + //llinfos << "BytesWeight: " << bytes_weight << llendl; + //llinfos << "PreLog: " << bytes_weight * virtual_size_factor << llendl; + w_priority = (F32)log10(bytes_weight * virtual_size_factor); + + //llinfos << "PreScale: " << w_priority << llendl; + + // We don't want to affect how MANY bytes we send based on the visible pixels, but the order + // in which they're sent. We post-multiply so we don't change the zero point. + if (w_priority > 0.f) + { + F32 pixel_weight = (F32)log10(visible_pixels + 1)*3.0f; + w_priority *= pixel_weight; + } + + return w_priority; +} diff --git a/indra/llimage/llimage.h b/indra/llimage/llimage.h new file mode 100644 index 0000000000..13ea916654 --- /dev/null +++ b/indra/llimage/llimage.h @@ -0,0 +1,298 @@ +/** + * @file llimage.h + * @brief Object for managing images and their textures. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLIMAGE_H +#define LL_LLIMAGE_H + +#include "stdtypes.h" +#include "lluuid.h" +#include "llstring.h" +#include "llmemory.h" +#include "llworkerthread.h" + +const S32 MIN_IMAGE_MIP = 2; // 4x4, only used for expand/contract power of 2 +const S32 MAX_IMAGE_MIP = 11; // 2048x2048 +const S32 MAX_DISCARD_LEVEL = 5; + +const S32 MIN_IMAGE_SIZE = (1<& raw, S32 discard = -1, F32 decode_time=0.0); + virtual BOOL requestDecodedAuxData(LLPointer& raw, S32 channel, + S32 discard = -1, F32 decode_time=0.0); + virtual void releaseDecodedData(); + + virtual BOOL encode(const LLImageRaw* raw_image, F32 encode_time=0.0) = 0; + + S8 getCodec() const; + BOOL isDecoding() const { return mDecoding ? TRUE : FALSE; } + BOOL isDecoded() const { return mDecoded ? TRUE : FALSE; } + void setDiscardLevel(S8 discard_level) { mDiscardLevel = discard_level; } + S8 getDiscardLevel() const { return mDiscardLevel; } + +protected: + S8 mCodec; + S8 mDecoding; + S8 mDecoded; + S8 mDiscardLevel; + + LLPointer mDecodedImage; + +public: + static S32 sGlobalFormattedMemory; + static LLWorkerThread* sWorkerThread; +}; + +#endif diff --git a/indra/llimage/llimagebmp.cpp b/indra/llimage/llimagebmp.cpp new file mode 100644 index 0000000000..94aaa5c19e --- /dev/null +++ b/indra/llimage/llimagebmp.cpp @@ -0,0 +1,624 @@ +/** + * @file llimagebmp.cpp + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llimagebmp.h" +#include "llerror.h" + +#include "llendianswizzle.h" + + +/** + * @struct LLBMPHeader + * + * This struct helps deal with bmp files. + */ +struct LLBMPHeader +{ + S32 mSize; + S32 mWidth; + S32 mHeight; + S16 mPlanes; + S16 mBitsPerPixel; + S16 mCompression; + S16 mAlignmentPadding; // pads out to next word boundary + S32 mImageSize; + S32 mHorzPelsPerMeter; + S32 mVertPelsPerMeter; + S32 mNumColors; + S32 mNumColorsImportant; +}; + +/** + * @struct Win95BmpHeaderExtension + */ +struct Win95BmpHeaderExtension +{ + U32 mReadMask; + U32 mGreenMask; + U32 mBlueMask; + U32 mAlphaMask; + U32 mColorSpaceType; + U16 mRed[3]; // Red CIE endpoint + U16 mGreen[3]; // Green CIE endpoint + U16 mBlue[3]; // Blue CIE endpoint + U32 mGamma[3]; // Gamma scale for r g and b +}; + +/** + * LLImageBMP + */ +LLImageBMP::LLImageBMP() + : + LLImageFormatted(IMG_CODEC_BMP), + mColorPaletteColors( 0 ), + mColorPalette( NULL ), + mBitmapOffset( 0 ), + mBitsPerPixel( 0 ), + mOriginAtTop( FALSE ) +{ + mBitfieldMask[0] = 0; + mBitfieldMask[1] = 0; + mBitfieldMask[2] = 0; + mBitfieldMask[3] = 0; +} + +LLImageBMP::~LLImageBMP() +{ + delete[] mColorPalette; +} + + +BOOL LLImageBMP::updateData() +{ + resetLastError(); + + // Check to make sure that this instance has been initialized with data + U8* mdata = getData(); + if (!mdata || (0 == getDataSize())) + { + setLastError("Uninitialized instance of LLImageBMP"); + return FALSE; + } + + // Read the bitmap headers in order to get all the useful info + // about this image + + //////////////////////////////////////////////////////////////////// + // Part 1: "File Header" + // 14 bytes consisting of + // 2 bytes: either BM or BA + // 4 bytes: file size in bytes + // 4 bytes: reserved (always 0) + // 4 bytes: bitmap offset (starting position of image data in bytes) + const S32 FILE_HEADER_SIZE = 14; + if ((mdata[0] != 'B') || (mdata[1] != 'M')) + { + if ((mdata[0] != 'B') || (mdata[1] != 'A')) + { + setLastError("OS/2 bitmap array BMP files are not supported"); + return FALSE; + } + else + { + setLastError("Does not appear to be a bitmap file"); + return FALSE; + } + } + + mBitmapOffset = mdata[13]; + mBitmapOffset <<= 8; mBitmapOffset += mdata[12]; + mBitmapOffset <<= 8; mBitmapOffset += mdata[11]; + mBitmapOffset <<= 8; mBitmapOffset += mdata[10]; + + + //////////////////////////////////////////////////////////////////// + // Part 2: "Bitmap Header" + const S32 BITMAP_HEADER_SIZE = 40; + LLBMPHeader header; + llassert( sizeof( header ) == BITMAP_HEADER_SIZE ); + + memcpy((void *)&header, mdata + FILE_HEADER_SIZE, BITMAP_HEADER_SIZE); + + // convert BMP header from little endian (no-op on little endian builds) + llendianswizzleone(header.mSize); + llendianswizzleone(header.mWidth); + llendianswizzleone(header.mHeight); + llendianswizzleone(header.mPlanes); + llendianswizzleone(header.mBitsPerPixel); + llendianswizzleone(header.mCompression); + llendianswizzleone(header.mAlignmentPadding); + llendianswizzleone(header.mImageSize); + llendianswizzleone(header.mHorzPelsPerMeter); + llendianswizzleone(header.mVertPelsPerMeter); + llendianswizzleone(header.mNumColors); + llendianswizzleone(header.mNumColorsImportant); + + BOOL windows_nt_version = FALSE; + BOOL windows_95_version = FALSE; + if( 12 == header.mSize ) + { + setLastError("Windows 2.x and OS/2 1.x BMP files are not supported"); + return FALSE; + } + else + if( 40 == header.mSize ) + { + if( 3 == header.mCompression ) + { + // Windows NT + windows_nt_version = TRUE; + } + else + { + // Windows 3.x + } + } + else + if( 12 <= header.mSize && 64 <= header.mSize ) + { + setLastError("OS/2 2.x BMP files are not supported"); + return FALSE; + } + else + if( 108 == header.mSize ) + { + // BITMAPV4HEADER + windows_95_version = TRUE; + } + else + if( 108 < header.mSize ) + { + // BITMAPV5HEADER or greater + // Should work as long at Microsoft maintained backwards compatibility (which they did in V4 and V5) + windows_95_version = TRUE; + } + + S32 width = header.mWidth; + S32 height = header.mHeight; + if (height < 0) + { + mOriginAtTop = TRUE; + height = -height; + } + else + { + mOriginAtTop = FALSE; + } + + mBitsPerPixel = header.mBitsPerPixel; + S32 components; + switch( mBitsPerPixel ) + { + case 8: + components = 1; + break; + case 24: + case 32: + components = 3; + break; + case 1: + case 4: + case 16: // Started work on 16, but doesn't work yet + // These are legal, but we don't support them yet. + setLastError("Unsupported bit depth"); + return FALSE; + default: + setLastError("Unrecognized bit depth"); + return FALSE; + } + + setSize(width, height, components); + + switch( header.mCompression ) + { + case 0: + // Uncompressed + break; + + case 1: + setLastError("8 bit RLE compression not supported."); + return FALSE; + + case 2: + setLastError("4 bit RLE compression not supported."); + return FALSE; + + case 3: + // Windows NT or Windows 95 + break; + + default: + setLastError("Unsupported compression format."); + return FALSE; + } + + //////////////////////////////////////////////////////////////////// + // Part 3: Bitfield Masks and other color data + S32 extension_size = 0; + if( windows_nt_version ) + { + if( (16 != header.mBitsPerPixel) && (32 != header.mBitsPerPixel) ) + { + setLastError("Bitfield encoding requires 16 or 32 bits per pixel."); + return FALSE; + } + + if( 0 != header.mNumColors ) + { + setLastError("Bitfield encoding is not compatible with a color table."); + return FALSE; + } + + + extension_size = 4 * 3; + memcpy( mBitfieldMask, mdata + FILE_HEADER_SIZE + BITMAP_HEADER_SIZE, extension_size); + } + else + if( windows_95_version ) + { + Win95BmpHeaderExtension win_95_extension; + extension_size = sizeof( win_95_extension ); + + llassert( sizeof( win_95_extension ) + BITMAP_HEADER_SIZE == 108 ); + memcpy( &win_95_extension, mdata + FILE_HEADER_SIZE + BITMAP_HEADER_SIZE, sizeof( win_95_extension ) ); + + if( 3 == header.mCompression ) + { + memcpy( mBitfieldMask, mdata + FILE_HEADER_SIZE + BITMAP_HEADER_SIZE, 4 * 4); + } + + // Color correction ignored for now + } + + + //////////////////////////////////////////////////////////////////// + // Part 4: Color Palette (optional) + // Note: There's no color palette if there are 16 or more bits per pixel + S32 color_palette_size = 0; + mColorPaletteColors = 0; + if( header.mBitsPerPixel < 16 ) + { + if( 0 == header.mNumColors ) + { + mColorPaletteColors = (1 << header.mBitsPerPixel); + } + else + { + mColorPaletteColors = header.mNumColors; + } + } + color_palette_size = mColorPaletteColors * 4; + + if( 0 != mColorPaletteColors ) + { + mColorPalette = new U8[color_palette_size]; + memcpy( mColorPalette, mdata + FILE_HEADER_SIZE + BITMAP_HEADER_SIZE + extension_size, color_palette_size ); + } + + return TRUE; +} + +BOOL LLImageBMP::decode(LLImageRaw* raw_image, F32 decode_time) +{ + llassert_always(raw_image); + + resetLastError(); + + // Check to make sure that this instance has been initialized with data + U8* mdata = getData(); + if (!mdata || (0 == getDataSize())) + { + setLastError("llimagebmp trying to decode an image with no data!"); + return FALSE; + } + + raw_image->resize(getWidth(), getHeight(), 3); + + U8* src = mdata + mBitmapOffset; + U8* dst = raw_image->getData(); + + BOOL success = FALSE; + + switch( mBitsPerPixel ) + { + case 8: + if( mColorPaletteColors >= 256 ) + { + success = decodeColorTable8( dst, src ); + } + break; + + case 16: + success = decodeColorMask16( dst, src ); + break; + + case 24: + success = decodeTruecolor24( dst, src ); + break; + + case 32: + success = decodeColorMask32( dst, src ); + break; + } + + if( success && mOriginAtTop ) + { + raw_image->verticalFlip(); + } + + return success; +} + +U32 LLImageBMP::countTrailingZeros( U32 m ) +{ + U32 shift_count = 0; + while( !(m & 1) ) + { + shift_count++; + m >>= 1; + } + return shift_count; +} + + +BOOL LLImageBMP::decodeColorMask16( U8* dst, U8* src ) +{ + llassert( 16 == mBitsPerPixel ); + + if( !mBitfieldMask[0] && !mBitfieldMask[1] && !mBitfieldMask[2] ) + { + // Use default values + mBitfieldMask[0] = 0x00007C00; + mBitfieldMask[1] = 0x000003E0; + mBitfieldMask[2] = 0x0000001F; + } + + S32 src_row_span = getWidth() * 2; + S32 alignment_bytes = (3 * src_row_span) % 4; // round up to nearest multiple of 4 + + U32 r_shift = countTrailingZeros( mBitfieldMask[2] ); + U32 g_shift = countTrailingZeros( mBitfieldMask[1] ); + U32 b_shift = countTrailingZeros( mBitfieldMask[0] ); + + for( S32 row = 0; row < getHeight(); row++ ) + { + for( S32 col = 0; col < getWidth(); col++ ) + { + U32 value = *((U16*)src); + dst[0] = U8((value & mBitfieldMask[2]) >> r_shift); // Red + dst[1] = U8((value & mBitfieldMask[1]) >> g_shift); // Green + dst[2] = U8((value & mBitfieldMask[0]) >> b_shift); // Blue + src += 2; + dst += 3; + } + src += alignment_bytes; + } + + return TRUE; +} + +BOOL LLImageBMP::decodeColorMask32( U8* dst, U8* src ) +{ + // Note: alpha is not supported + + llassert( 32 == mBitsPerPixel ); + + if( !mBitfieldMask[0] && !mBitfieldMask[1] && !mBitfieldMask[2] ) + { + // Use default values + mBitfieldMask[0] = 0x00FF0000; + mBitfieldMask[1] = 0x0000FF00; + mBitfieldMask[2] = 0x000000FF; + } + + + S32 src_row_span = getWidth() * 4; + S32 alignment_bytes = (3 * src_row_span) % 4; // round up to nearest multiple of 4 + + U32 r_shift = countTrailingZeros( mBitfieldMask[0] ); + U32 g_shift = countTrailingZeros( mBitfieldMask[1] ); + U32 b_shift = countTrailingZeros( mBitfieldMask[2] ); + + for( S32 row = 0; row < getHeight(); row++ ) + { + for( S32 col = 0; col < getWidth(); col++ ) + { + U32 value = *((U32*)src); + dst[0] = U8((value & mBitfieldMask[0]) >> r_shift); // Red + dst[1] = U8((value & mBitfieldMask[1]) >> g_shift); // Green + dst[2] = U8((value & mBitfieldMask[2]) >> b_shift); // Blue + src += 4; + dst += 3; + } + src += alignment_bytes; + } + + return TRUE; +} + + +BOOL LLImageBMP::decodeColorTable8( U8* dst, U8* src ) +{ + llassert( (8 == mBitsPerPixel) && (mColorPaletteColors >= 256) ); + + S32 src_row_span = getWidth() * 1; + S32 alignment_bytes = (3 * src_row_span) % 4; // round up to nearest multiple of 4 + + for( S32 row = 0; row < getHeight(); row++ ) + { + for( S32 col = 0; col < getWidth(); col++ ) + { + S32 index = 4 * src[0]; + dst[0] = mColorPalette[index + 2]; // Red + dst[1] = mColorPalette[index + 1]; // Green + dst[2] = mColorPalette[index + 0]; // Blue + src++; + dst += 3; + } + src += alignment_bytes; + } + + return TRUE; +} + + +BOOL LLImageBMP::decodeTruecolor24( U8* dst, U8* src ) +{ + llassert( 24 == mBitsPerPixel ); + llassert( 3 == getComponents() ); + S32 src_row_span = getWidth() * 3; + S32 alignment_bytes = (3 * src_row_span) % 4; // round up to nearest multiple of 4 + + for( S32 row = 0; row < getHeight(); row++ ) + { + for( S32 col = 0; col < getWidth(); col++ ) + { + dst[0] = src[2]; // Red + dst[1] = src[1]; // Green + dst[2] = src[0]; // Blue + src += 3; + dst += 3; + } + src += alignment_bytes; + } + + return TRUE; +} + +BOOL LLImageBMP::encode(const LLImageRaw* raw_image, F32 encode_time) +{ + llassert_always(raw_image); + + resetLastError(); + + S32 src_components = raw_image->getComponents(); + S32 dst_components = ( src_components < 3 ) ? 1 : 3; + + if( (2 == src_components) || (4 == src_components) ) + { + llinfos << "Dropping alpha information during BMP encoding" << llendl; + } + + setSize(raw_image->getWidth(), raw_image->getHeight(), dst_components); + + U8 magic[14]; + LLBMPHeader header; + int header_bytes = 14+sizeof(header); + llassert(header_bytes == 54); + if (getComponents() == 1) + { + header_bytes += 1024; // Need colour LUT. + } + int line_bytes = getComponents() * getWidth(); + int alignment_bytes = (3 * line_bytes) % 4; + line_bytes += alignment_bytes; + int file_bytes = line_bytes*getHeight() + header_bytes; + + // Allocate the new buffer for the data. + allocateData(file_bytes); + + magic[0] = 'B'; magic[1] = 'M'; + magic[2] = (U8) file_bytes; + magic[3] = (U8)(file_bytes>>8); + magic[4] = (U8)(file_bytes>>16); + magic[5] = (U8)(file_bytes>>24); + magic[6] = magic[7] = magic[8] = magic[9] = 0; + magic[10] = (U8) header_bytes; + magic[11] = (U8)(header_bytes>>8); + magic[12] = (U8)(header_bytes>>16); + magic[13] = (U8)(header_bytes>>24); + header.mSize = 40; + header.mWidth = getWidth(); + header.mHeight = getHeight(); + header.mPlanes = 1; + header.mBitsPerPixel = (getComponents()==1)?8:24; + header.mCompression = 0; + header.mAlignmentPadding = 0; + header.mImageSize = 0; +#if LL_DARWIN + header.mHorzPelsPerMeter = header.mVertPelsPerMeter = 2834; // 72dpi +#else + header.mHorzPelsPerMeter = header.mVertPelsPerMeter = 0; +#endif + header.mNumColors = header.mNumColorsImportant = 0; + + // convert BMP header to little endian (no-op on little endian builds) + llendianswizzleone(header.mSize); + llendianswizzleone(header.mWidth); + llendianswizzleone(header.mHeight); + llendianswizzleone(header.mPlanes); + llendianswizzleone(header.mBitsPerPixel); + llendianswizzleone(header.mCompression); + llendianswizzleone(header.mAlignmentPadding); + llendianswizzleone(header.mImageSize); + llendianswizzleone(header.mHorzPelsPerMeter); + llendianswizzleone(header.mVertPelsPerMeter); + llendianswizzleone(header.mNumColors); + llendianswizzleone(header.mNumColorsImportant); + + U8* mdata = getData(); + + // Output magic, then header, then the palette table, then the data. + U32 cur_pos = 0; + memcpy(mdata, magic, 14); + cur_pos += 14; + memcpy(mdata+cur_pos, &header, 40); + cur_pos += 40; + if (getComponents() == 1) + { + S32 n; + for (n=0; n < 256; n++) + { + mdata[cur_pos++] = (U8)n; + mdata[cur_pos++] = (U8)n; + mdata[cur_pos++] = (U8)n; + mdata[cur_pos++] = 0; + } + } + + // Need to iterate through, because we need to flip the RGB. + const U8* src = raw_image->getData(); + U8* dst = mdata + cur_pos; + + for( S32 row = 0; row < getHeight(); row++ ) + { + for( S32 col = 0; col < getWidth(); col++ ) + { + switch( src_components ) + { + case 1: + *dst++ = *src++; + break; + case 2: + { + U32 lum = src[0]; + U32 alpha = src[1]; + *dst++ = (U8)(lum * alpha / 255); + src += 2; + break; + } + case 3: + case 4: + dst[0] = src[2]; + dst[1] = src[1]; + dst[2] = src[0]; + src += src_components; + dst += 3; + break; + } + + for( S32 i = 0; i < alignment_bytes; i++ ) + { + *dst++ = 0; + } + } + } + + return TRUE; +} diff --git a/indra/llimage/llimagebmp.h b/indra/llimage/llimagebmp.h new file mode 100644 index 0000000000..0c0e51e2e7 --- /dev/null +++ b/indra/llimage/llimagebmp.h @@ -0,0 +1,45 @@ +/** + * @file llimagebmp.h + * @brief Image implementation for BMP. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLIMAGEBMP_H +#define LL_LLIMAGEBMP_H + +#include "llimage.h" + +// This class compresses and decompressed BMP files + +class LLImageBMP : public LLImageFormatted +{ +protected: + virtual ~LLImageBMP(); + +public: + LLImageBMP(); + + /*virtual*/ BOOL updateData(); + /*virtual*/ BOOL decode(LLImageRaw* raw_image, F32 time=0.0); + /*virtual*/ BOOL encode(const LLImageRaw* raw_image, F32 time=0.0); + +protected: + BOOL decodeColorTable8( U8* dst, U8* src ); + BOOL decodeColorMask16( U8* dst, U8* src ); + BOOL decodeTruecolor24( U8* dst, U8* src ); + BOOL decodeColorMask32( U8* dst, U8* src ); + + U32 countTrailingZeros( U32 m ); + +protected: + S32 mColorPaletteColors; + U8* mColorPalette; + S32 mBitmapOffset; + S32 mBitsPerPixel; + U32 mBitfieldMask[4]; // rgba + BOOL mOriginAtTop; +}; + +#endif diff --git a/indra/llimage/llimagedxt.cpp b/indra/llimage/llimagedxt.cpp new file mode 100644 index 0000000000..9ddd044007 --- /dev/null +++ b/indra/llimage/llimagedxt.cpp @@ -0,0 +1,475 @@ +/** + * @file llimagedxt.cpp + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llimagedxt.h" + +//static +void LLImageDXT::checkMinWidthHeight(EFileFormat format, S32& width, S32& height) +{ + S32 mindim = (format >= FORMAT_DXT1 && format <= FORMAT_DXR5) ? 4 : 1; + width = llmax(width, mindim); + height = llmax(height, mindim); +} + +//static +S32 LLImageDXT::formatBits(EFileFormat format) +{ + switch (format) + { + case FORMAT_DXT1: return 4; + case FORMAT_DXR1: return 4; + case FORMAT_I8: return 8; + case FORMAT_A8: return 8; + case FORMAT_DXT3: return 8; + case FORMAT_DXR3: return 8; + case FORMAT_DXR5: return 8; + case FORMAT_DXT5: return 8; + case FORMAT_RGB8: return 24; + case FORMAT_RGBA8: return 32; + default: + llerrs << "LLImageDXT::Unknown format: " << format << llendl; + return 0; + } +}; + +//static +S32 LLImageDXT::formatBytes(EFileFormat format, S32 width, S32 height) +{ + checkMinWidthHeight(format, width, height); + S32 bytes = ((width*height*formatBits(format)+7)>>3); + S32 aligned = (bytes+3)&~3; + return aligned; +} + +//static +S32 LLImageDXT::formatComponents(EFileFormat format) +{ + switch (format) + { + case FORMAT_DXT1: return 3; + case FORMAT_DXR1: return 3; + case FORMAT_I8: return 1; + case FORMAT_A8: return 1; + case FORMAT_DXT3: return 4; + case FORMAT_DXR3: return 4; + case FORMAT_DXT5: return 4; + case FORMAT_DXR5: return 4; + case FORMAT_RGB8: return 3; + case FORMAT_RGBA8: return 4; + default: + llerrs << "LLImageDXT::Unknown format: " << format << llendl; + return 0; + } +}; + +// static +LLImageDXT::EFileFormat LLImageDXT::getFormat(S32 fourcc) +{ + switch(fourcc) + { + case 0x20203849: return FORMAT_I8; + case 0x20203841: return FORMAT_A8; + case 0x20424752: return FORMAT_RGB8; + case 0x41424752: return FORMAT_RGBA8; + case 0x31525844: return FORMAT_DXR1; + case 0x32525844: return FORMAT_DXR2; + case 0x33525844: return FORMAT_DXR3; + case 0x34525844: return FORMAT_DXR4; + case 0x35525844: return FORMAT_DXR5; + case 0x31545844: return FORMAT_DXT1; + case 0x32545844: return FORMAT_DXT2; + case 0x33545844: return FORMAT_DXT3; + case 0x34545844: return FORMAT_DXT4; + case 0x35545844: return FORMAT_DXT5; + default: return FORMAT_UNKNOWN; + } +} + +//static +S32 LLImageDXT::getFourCC(EFileFormat format) +{ + switch(format) + { + case FORMAT_I8: return 0x20203849; + case FORMAT_A8: return 0x20203841; + case FORMAT_RGB8: return 0x20424752; + case FORMAT_RGBA8: return 0x41424752; + case FORMAT_DXR1: return 0x31525844; + case FORMAT_DXR2: return 0x32525844; + case FORMAT_DXR3: return 0x33525844; + case FORMAT_DXR4: return 0x34525844; + case FORMAT_DXR5: return 0x35525844; + case FORMAT_DXT1: return 0x31545844; + case FORMAT_DXT2: return 0x32545844; + case FORMAT_DXT3: return 0x33545844; + case FORMAT_DXT4: return 0x34545844; + case FORMAT_DXT5: return 0x35545844; + default: return 0x00000000; + } +} + +//static +void LLImageDXT::calcDiscardWidthHeight(S32 discard_level, EFileFormat format, S32& width, S32& height) +{ + while (discard_level > 0 && width > 1 && height > 1) + { + discard_level--; + width >>= 1; + height >>= 1; + } + checkMinWidthHeight(format, width, height); +} + +//static +S32 LLImageDXT::calcNumMips(S32 width, S32 height) +{ + S32 nmips = 0; + while (width > 0 && height > 0) + { + width >>= 1; + height >>= 1; + nmips++; + } + return nmips; +} + +//============================================================================ + +LLImageDXT::LLImageDXT() + : LLImageFormatted(IMG_CODEC_DXT), + mFileFormat(FORMAT_UNKNOWN), + mHeaderSize(0) +{ +} + +LLImageDXT::~LLImageDXT() +{ +} + +// virtual +BOOL LLImageDXT::updateData() +{ + resetLastError(); + + U8* data = getData(); + S32 data_size = getDataSize(); + + if (!data || !data_size) + { + setLastError("LLImageDXT uninitialized"); + return FALSE; + } + + S32 width, height, miplevelmax; + dxtfile_header_t* header = (dxtfile_header_t*)data; + if (header->fourcc != 0x20534444) + { + dxtfile_header_old_t* oldheader = (dxtfile_header_old_t*)header; + mHeaderSize = sizeof(dxtfile_header_old_t); + mFileFormat = EFileFormat(oldheader->format); + miplevelmax = llmin(oldheader->maxlevel,MAX_IMAGE_MIP); + width = oldheader->maxwidth; + height = oldheader->maxheight; + } + else + { + mHeaderSize = sizeof(dxtfile_header_t); + mFileFormat = getFormat(header->pixel_fmt.fourcc); + miplevelmax = llmin(header->num_mips-1,MAX_IMAGE_MIP); + width = header->maxwidth; + height = header->maxheight; + } + + if (data_size < mHeaderSize) + { + llerrs << "LLImageDXT: not enough data" << llendl; + } + S32 ncomponents = formatComponents(mFileFormat); + setSize(width, height, ncomponents); + + S32 discard = calcDiscardLevelBytes(data_size); + discard = llmin(discard, miplevelmax); + setDiscardLevel(discard); + + return TRUE; +} + +// discard: 0 = largest (last) mip +S32 LLImageDXT::getMipOffset(S32 discard) +{ + if (mFileFormat >= FORMAT_DXT1 && mFileFormat <= FORMAT_DXT5) + { + llerrs << "getMipOffset called with old (unsupported) format" << llendl; + } + S32 width = getWidth(), height = getHeight(); + S32 num_mips = calcNumMips(width, height); + discard = llclamp(discard, 0, num_mips-1); + S32 last_mip = num_mips-1-discard; + llassert(mHeaderSize > 0); + S32 offset = mHeaderSize; + for (S32 mipidx = num_mips-1; mipidx >= 0; mipidx--) + { + if (mipidx < last_mip) + { + offset += formatBytes(mFileFormat, width, height); + } + width >>= 1; + height >>= 1; + } + return offset; +} + +void LLImageDXT::setFormat() +{ + S32 ncomponents = getComponents(); + switch (ncomponents) + { + case 3: mFileFormat = FORMAT_DXR1; break; + case 4: mFileFormat = FORMAT_DXR3; break; + default: llerrs << "LLImageDXT::setFormat called with ncomponents = " << ncomponents << llendl; + } + mHeaderSize = calcHeaderSize(); +} + +// virtual +BOOL LLImageDXT::decode(LLImageRaw* raw_image, F32 time) +{ + llassert_always(raw_image); + + if (mFileFormat >= FORMAT_DXT1 && mFileFormat <= FORMAT_DXR5) + { + llwarns << "Attempt to decode compressed LLImageDXT to Raw (unsupported)" << llendl; + return FALSE; + } + + S32 width = getWidth(), height = getHeight(); + S32 ncomponents = getComponents(); + S32 image_size = formatBytes(mFileFormat, width, height); + U8* data = getData() + getMipOffset(0); + + if ((!getData()) || (data + image_size > getData() + getDataSize())) + { + setLastError("LLImageDXT trying to decode an image with not enough data!"); + return FALSE; + } + + raw_image->resize(width, height, ncomponents); + memcpy(raw_image->getData(), data, image_size); + + return TRUE; +} + +// virtual +BOOL LLImageDXT::requestDecodedData(LLPointer& raw, S32 discard, F32 decode_time) +{ + if (discard < 0) + { + discard = mDiscardLevel; + } + else if (discard < mDiscardLevel) + { + llerrs << "Request for invalid discard level" << llendl; + } + U8* data = getData() + getMipOffset(discard); + S32 width, height; + calcDiscardWidthHeight(discard, mFileFormat, width, height); + raw = new LLImageRaw(data, width, height, getComponents()); + return TRUE; +} + +void LLImageDXT::releaseDecodedData() +{ + // nothing to do +} + +BOOL LLImageDXT::encode(const LLImageRaw* raw_image, F32 time, bool explicit_mips) +{ + llassert_always(raw_image); + + S32 ncomponents = raw_image->getComponents(); + EFileFormat format; + switch (ncomponents) + { + case 1: + format = FORMAT_A8; + break; + case 3: + format = FORMAT_RGB8; + break; + case 4: + format = FORMAT_RGBA8; + break; + default: + llerrs << "LLImageDXT::encode: Unhandled channel number: " << ncomponents << llendl; + return 0; + } + + S32 width = raw_image->getWidth(); + S32 height = raw_image->getHeight(); + + if (explicit_mips) + { + height = (height/3)*2; + } + + setSize(width, height, ncomponents); + mHeaderSize = sizeof(dxtfile_header_t); + mFileFormat = format; + + S32 nmips = calcNumMips(width, height); + S32 w = width; + S32 h = height; + + S32 totbytes = mHeaderSize; + for (S32 mip=0; mip>= 1; + h >>= 1; + } + + allocateData(totbytes); + + U8* data = getData(); + dxtfile_header_t* header = (dxtfile_header_t*)data; + llassert(mHeaderSize > 0); + memset(header, 0, mHeaderSize); + header->fourcc = 0x20534444; + header->pixel_fmt.fourcc = getFourCC(format); + header->num_mips = nmips; + header->maxwidth = width; + header->maxheight = height; + + U8* prev_mipdata = 0; + w = width, h = height; + for (S32 mip=0; mipgetData(), bytes); + } + else if (explicit_mips) + { + extractMip(raw_image->getData(), mipdata, width, height, w, h, format); + } + else + { + generateMip(prev_mipdata, mipdata, w, h, ncomponents); + } + w >>= 1; + h >>= 1; + checkMinWidthHeight(format, w, h); + prev_mipdata = mipdata; + } + + return TRUE; +} + +// virtual +BOOL LLImageDXT::encode(const LLImageRaw* raw_image, F32 time) +{ + return encode(raw_image, time, false); +} + +// virtual +bool LLImageDXT::convertToDXR() +{ + EFileFormat newformat = FORMAT_UNKNOWN; + switch (mFileFormat) + { + case FORMAT_DXR1: + case FORMAT_DXR2: + case FORMAT_DXR3: + case FORMAT_DXR4: + case FORMAT_DXR5: + return false; // nothing to do + case FORMAT_DXT1: newformat = FORMAT_DXR1; break; + case FORMAT_DXT2: newformat = FORMAT_DXR2; break; + case FORMAT_DXT3: newformat = FORMAT_DXR3; break; + case FORMAT_DXT4: newformat = FORMAT_DXR4; break; + case FORMAT_DXT5: newformat = FORMAT_DXR5; break; + default: + llwarns << "convertToDXR: can not convert format: " << llformat("0x%08x",getFourCC(mFileFormat)) << llendl; + return false; + } + mFileFormat = newformat; + S32 width = getWidth(), height = getHeight(); + S32 nmips = calcNumMips(width,height); + S32 total_bytes = getDataSize(); + U8* olddata = getData(); + U8* newdata = new U8[total_bytes]; + llassert(total_bytes > 0); + memset(newdata, 0, total_bytes); + memcpy(newdata, olddata, mHeaderSize); + for (S32 mip=0; mip>= 1; + height >>= 1; + } + dxtfile_header_t* header = (dxtfile_header_t*)newdata; + header->pixel_fmt.fourcc = getFourCC(newformat); + setData(newdata, total_bytes); + return true; +} + +// virtual +S32 LLImageDXT::calcHeaderSize() +{ + return llmax(sizeof(dxtfile_header_old_t), sizeof(dxtfile_header_t)); +} + +// virtual +S32 LLImageDXT::calcDataSize(S32 discard_level) +{ + if (mFileFormat == FORMAT_UNKNOWN) + { + llerrs << "calcDataSize called with unloaded LLImageDXT" << llendl; + return 0; + } + if (discard_level < 0) + { + discard_level = mDiscardLevel; + } + S32 bytes = getMipOffset(discard_level); // size of header + previous mips + S32 w = getWidth() >> discard_level; + S32 h = getHeight() >> discard_level; + bytes += formatBytes(mFileFormat,w,h); + return bytes; +} + +//============================================================================ + +//static +void LLImageDXT::extractMip(const U8 *indata, U8* mipdata, int width, int height, + int mip_width, int mip_height, EFileFormat format) +{ + int initial_offset = formatBytes(format, width, height); + int line_width = formatBytes(format, width, 1); + int mip_line_width = formatBytes(format, mip_width, 1); + int line_offset = 0; + + for (int ww=width>>1; ww>mip_width; ww>>=1) + { + line_offset += formatBytes(format, ww, 1); + } + + for (int h=0;h& raw, S32 discard=-1, F32 decode_time=0.0); + /*virtual*/ void releaseDecodedData(); + + /*virtual*/ S32 calcHeaderSize(); + /*virtual*/ S32 calcDataSize(S32 discard_level = 0); + + void setFormat(); + S32 getMipOffset(S32 discard); + + EFileFormat getFileFormat() { return mFileFormat; } + bool isCompressed() { return (mFileFormat >= FORMAT_DXT1 && mFileFormat <= FORMAT_DXR5); } + + bool convertToDXR(); // convert from DXT to DXR + + static void checkMinWidthHeight(EFileFormat format, S32& width, S32& height); + static S32 formatBits(EFileFormat format); + static S32 formatBytes(EFileFormat format, S32 width, S32 height); + static S32 formatOffset(EFileFormat format, S32 width, S32 height, S32 max_width, S32 max_height); + static S32 formatComponents(EFileFormat format); + + static EFileFormat getFormat(S32 fourcc); + static S32 getFourCC(EFileFormat format); + + static void calcDiscardWidthHeight(S32 discard_level, EFileFormat format, S32& width, S32& height); + static S32 calcNumMips(S32 width, S32 height); + +private: + static void extractMip(const U8 *indata, U8* mipdata, int width, int height, + int mip_width, int mip_height, EFileFormat format); + +private: + EFileFormat mFileFormat; + S32 mHeaderSize; +}; + +#endif diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp new file mode 100644 index 0000000000..ad07700a37 --- /dev/null +++ b/indra/llimage/llimagej2c.cpp @@ -0,0 +1,249 @@ +/** + * @file llimagej2c.cpp + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#ifndef LL_USE_KDU +#define LL_USE_KDU 1 +#endif // LL_USE_KDU + +#include "llimagej2c.h" +#include "llmemory.h" +#if LL_USE_KDU +#include "llimagej2ckdu.h" +#endif + +#include "llimagej2coj.h" + + +LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C), + mMaxBytes(0), + mRawDiscardLevel(-1), + mRate(0.0f) +{ +#if LL_USE_KDU + mImpl = new LLImageJ2CKDU(); +#else + mImpl = new LLImageJ2COJ(); +#endif +} + +// virtual +LLImageJ2C::~LLImageJ2C() +{ + delete mImpl; +} + +// virtual +S8 LLImageJ2C::getRawDiscardLevel() +{ + return mRawDiscardLevel; +} + +BOOL LLImageJ2C::updateData() +{ + resetLastError(); + + // Check to make sure that this instance has been initialized with data + if (!getData() || (getDataSize() < 16)) + { + setLastError("LLImageJ2C uninitialized"); + return FALSE; + } + + if (!mImpl->getMetadata(*this)) + { + return FALSE; + } + // SJB: override discard based on mMaxBytes elsewhere + S32 max_bytes = getDataSize(); // mMaxBytes ? mMaxBytes : getDataSize(); + S32 discard = calcDiscardLevelBytes(max_bytes); + setDiscardLevel(discard); + + return TRUE; +} + + +BOOL LLImageJ2C::decode(LLImageRaw *raw_imagep, F32 decode_time) +{ + return decode(raw_imagep, decode_time, 0, 4); +} + + +BOOL LLImageJ2C::decode(LLImageRaw *raw_imagep, F32 decode_time, S32 first_channel, S32 max_channel_count ) +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + + resetLastError(); + + // Check to make sure that this instance has been initialized with data + if (!getData() || (getDataSize() < 16)) + { + setLastError("LLImageJ2C uninitialized"); + return FALSE; + } + + // Update the raw discard level + updateRawDiscardLevel(); + + return mImpl->decodeImpl(*this, *raw_imagep, decode_time, 0, 4); +} + + +BOOL LLImageJ2C::encode(const LLImageRaw *raw_imagep, F32 encode_time) +{ + return encode(raw_imagep, NULL, encode_time); +} + + +BOOL LLImageJ2C::encode(const LLImageRaw *raw_imagep, const char* comment_text, F32 encode_time) +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + return mImpl->encodeImpl(*this, *raw_imagep, comment_text, encode_time); +} + +//static +S32 LLImageJ2C::calcHeaderSizeJ2C() +{ + return 600; //2048; // ??? hack... just needs to be >= actual header size... +} + +//static +S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) +{ + if (rate <= 0.f) rate = .125f; + while (discard_level > 0) + { + if (w < 1 || h < 1) + break; + w >>= 1; + h >>= 1; + discard_level--; + } + S32 bytes = (S32)((F32)(w*h*comp)*rate); + bytes = llmax(bytes, calcHeaderSizeJ2C()); + return bytes; +} + +S32 LLImageJ2C::calcHeaderSize() +{ + return calcHeaderSizeJ2C(); +} + +S32 LLImageJ2C::calcDataSize(S32 discard_level) +{ + return calcDataSizeJ2C(getWidth(), getHeight(), getComponents(), discard_level, mRate); +} + +S32 LLImageJ2C::calcDiscardLevelBytes(S32 bytes) +{ + llassert(bytes >= 0); + S32 discard_level = 0; + if (bytes == 0) + { + return MAX_DISCARD_LEVEL; + } + while (1) + { + S32 bytes_needed = calcDataSize(discard_level); // virtual + if (bytes >= bytes_needed - (bytes_needed>>2)) // For J2c, up the res at 75% of the optimal number of bytes + { + break; + } + discard_level++; + if (discard_level >= MAX_DISCARD_LEVEL) + { + break; + } + } + return discard_level; +} + +void LLImageJ2C::setRate(F32 rate) +{ + mRate = rate; +} + +void LLImageJ2C::setMaxBytes(S32 max_bytes) +{ + mMaxBytes = max_bytes; +} +// NOT USED +// void LLImageJ2C::setReversible(const BOOL reversible) +// { +// mReversible = reversible; +// } + + +BOOL LLImageJ2C::loadAndValidate(const LLString &filename) +{ + resetLastError(); + + S32 file_size = 0; + apr_file_t* apr_file = ll_apr_file_open(filename, LL_APR_RB, &file_size); + if (!apr_file) + { + setLastError("Unable to open file for reading", filename); + return FALSE; + } + if (file_size == 0) + { + setLastError("File is empty",filename); + apr_file_close(apr_file); + return FALSE; + } + + U8 *data = new U8[file_size]; + apr_size_t bytes_read = file_size; + apr_status_t s = apr_file_read(apr_file, data, &bytes_read); // modifies bytes_read + if (s != APR_SUCCESS || bytes_read != file_size) + { + delete[] data; + setLastError("Unable to read entire file"); + return FALSE; + } + apr_file_close(apr_file); + + return validate(data, file_size); +} + + +BOOL LLImageJ2C::validate(U8 *data, U32 file_size) +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + // Taken from setData() + + BOOL res = LLImageFormatted::setData(data, file_size); + if ( !res ) + { + return FALSE; + } + + // Check to make sure that this instance has been initialized with data + if (!getData() || (0 == getDataSize())) + { + setLastError("LLImageJ2C uninitialized"); + return FALSE; + } + + return mImpl->getMetadata(*this); +} + +void LLImageJ2C::setDecodingDone(BOOL complete) +{ + mDecoding = FALSE; + mDecoded = complete; +} + +void LLImageJ2C::updateRawDiscardLevel() +{ + mRawDiscardLevel = mMaxBytes ? calcDiscardLevelBytes(mMaxBytes) : mDiscardLevel; +} + +LLImageJ2CImpl::~LLImageJ2CImpl() +{ +} diff --git a/indra/llimage/llimagej2c.h b/indra/llimage/llimagej2c.h new file mode 100644 index 0000000000..ee73612bc7 --- /dev/null +++ b/indra/llimage/llimagej2c.h @@ -0,0 +1,77 @@ +/** + * @file llimagej2c.h + * @brief Image implmenation for jpeg2000. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLIMAGEJ2C_H +#define LL_LLIMAGEJ2C_H + +#include "llimage.h" +#include "llassettype.h" + +class LLImageJ2CImpl; + +class LLImageJ2C : public LLImageFormatted +{ +protected: + virtual ~LLImageJ2C(); + +public: + LLImageJ2C(); + + // Base class overrides + /*virtual*/ BOOL updateData(); + /*virtual*/ BOOL decode(LLImageRaw *raw_imagep, F32 decode_time=0.0); + /*virtual*/ BOOL decode(LLImageRaw *raw_imagep, F32 decode_time, S32 first_channel, S32 max_channel_count); + /*virtual*/ BOOL encode(const LLImageRaw *raw_imagep, F32 encode_time=0.0); + /*virtual*/ S32 calcHeaderSize(); + /*virtual*/ S32 calcDataSize(S32 discard_level = 0); + /*virtual*/ S32 calcDiscardLevelBytes(S32 bytes); + /*virtual*/ S8 getRawDiscardLevel(); + + // Encode with comment text + BOOL encode(const LLImageRaw *raw_imagep, const char* comment_text, F32 encode_time=0.0); + + BOOL validate(U8 *data, U32 file_size); + BOOL loadAndValidate(const LLString &filename); + + // Encode accessors + void setReversible(const BOOL reversible); // Use non-lossy? + void setRate(F32 rate); + void setMaxBytes(S32 max_bytes); + S32 getMaxBytes() const { return mMaxBytes; } + + static S32 calcHeaderSizeJ2C(); + static S32 calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate = 0.f); + +protected: + friend class LLImageJ2CImpl; + friend class LLImageJ2COJ; + friend class LLImageJ2CKDU; + void setDecodingDone(BOOL complete = TRUE); + void updateRawDiscardLevel(); + + S32 mMaxBytes; // Maximum number of bytes of data to use... + S8 mRawDiscardLevel; + F32 mRate; + LLImageJ2CImpl *mImpl; +}; + +// Derive from this class to implement JPEG2000 decoding +class LLImageJ2CImpl +{ +protected: + virtual ~LLImageJ2CImpl(); + virtual BOOL getMetadata(LLImageJ2C &base) = 0; + virtual BOOL decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, S32 first_channel, S32 max_channel_count) = 0; + virtual BOOL encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text, F32 encode_time=0.0) = 0; + + friend class LLImageJ2C; +}; + +#define LINDEN_J2C_COMMENT_PREFIX "LL_" + +#endif diff --git a/indra/llimage/llimagejpeg.cpp b/indra/llimage/llimagejpeg.cpp new file mode 100644 index 0000000000..c75e449db5 --- /dev/null +++ b/indra/llimage/llimagejpeg.cpp @@ -0,0 +1,602 @@ +/** + * @file llimagejpeg.cpp + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "stdtypes.h" + +#include "llimagejpeg.h" + +#include "llerror.h" + +LLImageJPEG::LLImageJPEG() + : + LLImageFormatted(IMG_CODEC_JPEG), + mOutputBuffer( NULL ), + mOutputBufferSize( 0 ), + mEncodeQuality( 75 ) // on a scale from 1 to 100 +{ +} + +LLImageJPEG::~LLImageJPEG() +{ + llassert( !mOutputBuffer ); // Should already be deleted at end of encode. + delete[] mOutputBuffer; +} + +BOOL LLImageJPEG::updateData() +{ + resetLastError(); + + // Check to make sure that this instance has been initialized with data + if (!getData() || (0 == getDataSize())) + { + setLastError("Uninitialized instance of LLImageJPEG"); + return FALSE; + } + + //////////////////////////////////////// + // Step 1: allocate and initialize JPEG decompression object + + // This struct contains the JPEG decompression parameters and pointers to + // working space (which is allocated as needed by the JPEG library). + struct jpeg_decompress_struct cinfo; + cinfo.client_data = this; + + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + + // Customize with our own callbacks + jerr.error_exit = &LLImageJPEG::errorExit; // Error exit handler: does not return to caller + jerr.emit_message = &LLImageJPEG::errorEmitMessage; // Conditionally emit a trace or warning message + jerr.output_message = &LLImageJPEG::errorOutputMessage; // Routine that actually outputs a trace or error message + + try + { + // Now we can initialize the JPEG decompression object. + jpeg_create_decompress(&cinfo); + + //////////////////////////////////////// + // Step 2: specify data source + // (Code is modified version of jpeg_stdio_src(); + if (cinfo.src == NULL) + { + cinfo.src = (struct jpeg_source_mgr *) + (*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT, + sizeof(struct jpeg_source_mgr)); + } + cinfo.src->init_source = &LLImageJPEG::decodeInitSource; + cinfo.src->fill_input_buffer = &LLImageJPEG::decodeFillInputBuffer; + cinfo.src->skip_input_data = &LLImageJPEG::decodeSkipInputData; + cinfo.src->resync_to_restart = jpeg_resync_to_restart; // For now, use default method, but we should be able to do better. + cinfo.src->term_source = &LLImageJPEG::decodeTermSource; + + cinfo.src->bytes_in_buffer = getDataSize(); + cinfo.src->next_input_byte = getData(); + + //////////////////////////////////////// + // Step 3: read file parameters with jpeg_read_header() + jpeg_read_header( &cinfo, TRUE ); + + // Data set by jpeg_read_header + setSize(cinfo.image_width, cinfo.image_height, 3); // Force to 3 components (RGB) + + /* + // More data set by jpeg_read_header + cinfo.num_components; + cinfo.jpeg_color_space; // Colorspace of image + cinfo.saw_JFIF_marker; // TRUE if a JFIF APP0 marker was seen + cinfo.JFIF_major_version; // Version information from JFIF marker + cinfo.JFIF_minor_version; // + cinfo.density_unit; // Resolution data from JFIF marker + cinfo.X_density; + cinfo.Y_density; + cinfo.saw_Adobe_marker; // TRUE if an Adobe APP14 marker was seen + cinfo.Adobe_transform; // Color transform code from Adobe marker + */ + } + catch (int) + { + jpeg_destroy_decompress(&cinfo); + + return FALSE; + } + //////////////////////////////////////// + // Step 4: Release JPEG decompression object + jpeg_destroy_decompress(&cinfo); + + return TRUE; +} + +// Initialize source --- called by jpeg_read_header +// before any data is actually read. +void LLImageJPEG::decodeInitSource( j_decompress_ptr cinfo ) +{ + // no work necessary here +} + +// Fill the input buffer --- called whenever buffer is emptied. +boolean LLImageJPEG::decodeFillInputBuffer( j_decompress_ptr cinfo ) +{ +// jpeg_source_mgr* src = cinfo->src; +// LLImageJPEG* self = (LLImageJPEG*) cinfo->client_data; + + // Should never get here, since we provide the entire buffer up front. + ERREXIT(cinfo, JERR_INPUT_EMPTY); + + return TRUE; +} + +// Skip data --- used to skip over a potentially large amount of +// uninteresting data (such as an APPn marker). +// +// Writers of suspendable-input applications must note that skip_input_data +// is not granted the right to give a suspension return. If the skip extends +// beyond the data currently in the buffer, the buffer can be marked empty so +// that the next read will cause a fill_input_buffer call that can suspend. +// Arranging for additional bytes to be discarded before reloading the input +// buffer is the application writer's problem. +void LLImageJPEG::decodeSkipInputData (j_decompress_ptr cinfo, long num_bytes) +{ + jpeg_source_mgr* src = cinfo->src; +// LLImageJPEG* self = (LLImageJPEG*) cinfo->client_data; + + src->next_input_byte += (size_t) num_bytes; + src->bytes_in_buffer -= (size_t) num_bytes; +} + +void LLImageJPEG::decodeTermSource (j_decompress_ptr cinfo) +{ + // no work necessary here +} + + +BOOL LLImageJPEG::decode(LLImageRaw* raw_image, F32 decode_time) +{ + llassert_always(raw_image); + + resetLastError(); + + // Check to make sure that this instance has been initialized with data + if (!getData() || (0 == getDataSize())) + { + setLastError("LLImageJPEG trying to decode an image with no data!"); + return FALSE; + } + + S32 row_stride = 0; + U8* raw_image_data = NULL; + + //////////////////////////////////////// + // Step 1: allocate and initialize JPEG decompression object + + // This struct contains the JPEG decompression parameters and pointers to + // working space (which is allocated as needed by the JPEG library). + struct jpeg_decompress_struct cinfo; + + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + + // Customize with our own callbacks + jerr.error_exit = &LLImageJPEG::errorExit; // Error exit handler: does not return to caller + jerr.emit_message = &LLImageJPEG::errorEmitMessage; // Conditionally emit a trace or warning message + jerr.output_message = &LLImageJPEG::errorOutputMessage; // Routine that actually outputs a trace or error message + + + try + { + // Now we can initialize the JPEG decompression object. + jpeg_create_decompress(&cinfo); + + //////////////////////////////////////// + // Step 2: specify data source + // (Code is modified version of jpeg_stdio_src(); + if (cinfo.src == NULL) + { + cinfo.src = (struct jpeg_source_mgr *) + (*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT, + sizeof(struct jpeg_source_mgr)); + } + cinfo.src->init_source = &LLImageJPEG::decodeInitSource; + cinfo.src->fill_input_buffer = &LLImageJPEG::decodeFillInputBuffer; + cinfo.src->skip_input_data = &LLImageJPEG::decodeSkipInputData; + cinfo.src->resync_to_restart = jpeg_resync_to_restart; // For now, use default method, but we should be able to do better. + cinfo.src->term_source = &LLImageJPEG::decodeTermSource; + cinfo.src->bytes_in_buffer = getDataSize(); + cinfo.src->next_input_byte = getData(); + + //////////////////////////////////////// + // Step 3: read file parameters with jpeg_read_header() + + jpeg_read_header(&cinfo, TRUE); + + // We can ignore the return value from jpeg_read_header since + // (a) suspension is not possible with our data source, and + // (b) we passed TRUE to reject a tables-only JPEG file as an error. + // See libjpeg.doc for more info. + + setSize(cinfo.image_width, cinfo.image_height, 3); // Force to 3 components (RGB) + + raw_image->resize(getWidth(), getHeight(), getComponents()); + raw_image_data = raw_image->getData(); + + + //////////////////////////////////////// + // Step 4: set parameters for decompression + cinfo.out_color_components = 3; + cinfo.out_color_space = JCS_RGB; + + + //////////////////////////////////////// + // Step 5: Start decompressor + + jpeg_start_decompress(&cinfo); + // We can ignore the return value since suspension is not possible + // with our data source. + + // We may need to do some setup of our own at this point before reading + // the data. After jpeg_start_decompress() we have the correct scaled + // output image dimensions available, as well as the output colormap + // if we asked for color quantization. + // In this example, we need to make an output work buffer of the right size. + + // JSAMPLEs per row in output buffer + row_stride = cinfo.output_width * cinfo.output_components; + + //////////////////////////////////////// + // Step 6: while (scan lines remain to be read) + // jpeg_read_scanlines(...); + + // Here we use the library's state variable cinfo.output_scanline as the + // loop counter, so that we don't have to keep track ourselves. + + // Move pointer to last line + raw_image_data += row_stride * (cinfo.output_height - 1); + + while (cinfo.output_scanline < cinfo.output_height) + { + // jpeg_read_scanlines expects an array of pointers to scanlines. + // Here the array is only one element long, but you could ask for + // more than one scanline at a time if that's more convenient. + + jpeg_read_scanlines(&cinfo, &raw_image_data, 1); + raw_image_data -= row_stride; // move pointer up a line + } + + //////////////////////////////////////// + // Step 7: Finish decompression + jpeg_finish_decompress(&cinfo); + + //////////////////////////////////////// + // Step 8: Release JPEG decompression object + jpeg_destroy_decompress(&cinfo); + } + + catch (int) + { + jpeg_destroy_decompress(&cinfo); + return FALSE; + } + + // Check to see whether any corrupt-data warnings occurred + if( jerr.num_warnings != 0 ) + { + // TODO: extract the warning to find out what went wrong. + setLastError( "Unable to decode JPEG image."); + return FALSE; + } + + return TRUE; +} + + +// Initialize destination --- called by jpeg_start_compress before any data is actually written. +// static +void LLImageJPEG::encodeInitDestination ( j_compress_ptr cinfo ) +{ + LLImageJPEG* self = (LLImageJPEG*) cinfo->client_data; + + cinfo->dest->next_output_byte = self->mOutputBuffer; + cinfo->dest->free_in_buffer = self->mOutputBufferSize; +} + + +// Empty the output buffer --- called whenever buffer fills up. +// +// In typical applications, this should write the entire output buffer +// (ignoring the current state of next_output_byte & free_in_buffer), +// reset the pointer & count to the start of the buffer, and return TRUE +// indicating that the buffer has been dumped. +// +// In applications that need to be able to suspend compression due to output +// overrun, a FALSE return indicates that the buffer cannot be emptied now. +// In this situation, the compressor will return to its caller (possibly with +// an indication that it has not accepted all the supplied scanlines). The +// application should resume compression after it has made more room in the +// output buffer. Note that there are substantial restrictions on the use of +// suspension --- see the documentation. +// +// When suspending, the compressor will back up to a convenient restart point +// (typically the start of the current MCU). next_output_byte & free_in_buffer +// indicate where the restart point will be if the current call returns FALSE. +// Data beyond this point will be regenerated after resumption, so do not +// write it out when emptying the buffer externally. + +boolean LLImageJPEG::encodeEmptyOutputBuffer( j_compress_ptr cinfo ) +{ + LLImageJPEG* self = (LLImageJPEG*) cinfo->client_data; + + // Should very rarely happen, since our output buffer is + // as large as the input to start out with. + + // Double the buffer size; + S32 new_buffer_size = self->mOutputBufferSize * 2; + U8* new_buffer = new U8[ new_buffer_size ]; + memcpy( new_buffer, self->mOutputBuffer, self->mOutputBufferSize ); + delete[] self->mOutputBuffer; + self->mOutputBuffer = new_buffer; + + cinfo->dest->next_output_byte = self->mOutputBuffer + self->mOutputBufferSize; + cinfo->dest->free_in_buffer = self->mOutputBufferSize; + self->mOutputBufferSize = new_buffer_size; + + return TRUE; +} + +// Terminate destination --- called by jpeg_finish_compress +// after all data has been written. Usually needs to flush buffer. +// +// NB: *not* called by jpeg_abort or jpeg_destroy; surrounding +// application must deal with any cleanup that should happen even +// for error exit. +void LLImageJPEG::encodeTermDestination( j_compress_ptr cinfo ) +{ + LLImageJPEG* self = (LLImageJPEG*) cinfo->client_data; + + S32 file_bytes = (S32)(self->mOutputBufferSize - cinfo->dest->free_in_buffer); + self->allocateData(file_bytes); + + memcpy( self->getData(), self->mOutputBuffer, file_bytes ); +} + +// static +void LLImageJPEG::errorExit( j_common_ptr cinfo ) +{ + //LLImageJPEG* self = (LLImageJPEG*) cinfo->client_data; + + // Always display the message + (*cinfo->err->output_message)(cinfo); + + // Let the memory manager delete any temp files + jpeg_destroy(cinfo); + + // Return control to the setjmp point + throw 1; +} + +// Decide whether to emit a trace or warning message. +// msg_level is one of: +// -1: recoverable corrupt-data warning, may want to abort. +// 0: important advisory messages (always display to user). +// 1: first level of tracing detail. +// 2,3,...: successively more detailed tracing messages. +// An application might override this method if it wanted to abort on warnings +// or change the policy about which messages to display. +// static +void LLImageJPEG::errorEmitMessage( j_common_ptr cinfo, int msg_level ) +{ + struct jpeg_error_mgr * err = cinfo->err; + + if (msg_level < 0) + { + // It's a warning message. Since corrupt files may generate many warnings, + // the policy implemented here is to show only the first warning, + // unless trace_level >= 3. + if (err->num_warnings == 0 || err->trace_level >= 3) + { + (*err->output_message) (cinfo); + } + // Always count warnings in num_warnings. + err->num_warnings++; + } + else + { + // It's a trace message. Show it if trace_level >= msg_level. + if (err->trace_level >= msg_level) + { + (*err->output_message) (cinfo); + } + } +} + +// static +void LLImageJPEG::errorOutputMessage( j_common_ptr cinfo ) +{ + // Create the message + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message) (cinfo, buffer); + + ((LLImageJPEG*) cinfo->client_data)->setLastError( buffer ); + + BOOL is_decode = (cinfo->is_decompressor != 0); + llwarns << "LLImageJPEG " << (is_decode ? "decode " : "encode ") << " failed: " << buffer << llendl; +} + +BOOL LLImageJPEG::encode( const LLImageRaw* raw_image, F32 encode_time ) +{ + llassert_always(raw_image); + + resetLastError(); + + switch( raw_image->getComponents() ) + { + case 1: + case 3: + break; + default: + setLastError("Unable to encode a JPEG image that doesn't have 1 or 3 components."); + return FALSE; + } + + setSize(raw_image->getWidth(), raw_image->getHeight(), raw_image->getComponents()); + + // Allocate a temporary buffer big enough to hold the entire compressed image (and then some) + // (Note: we make it bigger in emptyOutputBuffer() if we need to) + delete[] mOutputBuffer; + mOutputBufferSize = getWidth() * getHeight() * getComponents() + 1024; + mOutputBuffer = new U8[ mOutputBufferSize ]; + + const U8* raw_image_data = NULL; + S32 row_stride = 0; + + //////////////////////////////////////// + // Step 1: allocate and initialize JPEG compression object + + // This struct contains the JPEG compression parameters and pointers to + // working space (which is allocated as needed by the JPEG library). + struct jpeg_compress_struct cinfo; + cinfo.client_data = this; + + // We have to set up the error handler first, in case the initialization + // step fails. (Unlikely, but it could happen if you are out of memory.) + // This routine fills in the contents of struct jerr, and returns jerr's + // address which we place into the link field in cinfo. + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + + // Customize with our own callbacks + jerr.error_exit = &LLImageJPEG::errorExit; // Error exit handler: does not return to caller + jerr.emit_message = &LLImageJPEG::errorEmitMessage; // Conditionally emit a trace or warning message + jerr.output_message = &LLImageJPEG::errorOutputMessage; // Routine that actually outputs a trace or error message + + // Establish the setjmp return context mSetjmpBuffer. Used by library to abort. + if( setjmp(mSetjmpBuffer) ) + { + // If we get here, the JPEG code has signaled an error. + // We need to clean up the JPEG object, close the input file, and return. + jpeg_destroy_compress(&cinfo); + delete[] mOutputBuffer; + mOutputBuffer = NULL; + mOutputBufferSize = 0; + return FALSE; + } + + try + { + + // Now we can initialize the JPEG compression object. + jpeg_create_compress(&cinfo); + + //////////////////////////////////////// + // Step 2: specify data destination + // (code is a modified form of jpeg_stdio_dest() ) + if( cinfo.dest == NULL) + { + cinfo.dest = (struct jpeg_destination_mgr *) + (*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT, + sizeof(struct jpeg_destination_mgr)); + } + cinfo.dest->next_output_byte = mOutputBuffer; // => next byte to write in buffer + cinfo.dest->free_in_buffer = mOutputBufferSize; // # of byte spaces remaining in buffer + cinfo.dest->init_destination = &LLImageJPEG::encodeInitDestination; + cinfo.dest->empty_output_buffer = &LLImageJPEG::encodeEmptyOutputBuffer; + cinfo.dest->term_destination = &LLImageJPEG::encodeTermDestination; + + //////////////////////////////////////// + // Step 3: set parameters for compression + // + // First we supply a description of the input image. + // Four fields of the cinfo struct must be filled in: + + cinfo.image_width = getWidth(); // image width and height, in pixels + cinfo.image_height = getHeight(); + + switch( getComponents() ) + { + case 1: + cinfo.input_components = 1; // # of color components per pixel + cinfo.in_color_space = JCS_GRAYSCALE; // colorspace of input image + break; + case 3: + cinfo.input_components = 3; // # of color components per pixel + cinfo.in_color_space = JCS_RGB; // colorspace of input image + break; + default: + setLastError("Unable to encode a JPEG image that doesn't have 1 or 3 components."); + return FALSE; + } + + // Now use the library's routine to set default compression parameters. + // (You must set at least cinfo.in_color_space before calling this, + // since the defaults depend on the source color space.) + jpeg_set_defaults(&cinfo); + + // Now you can set any non-default parameters you wish to. + jpeg_set_quality(&cinfo, mEncodeQuality, TRUE ); // limit to baseline-JPEG values + + //////////////////////////////////////// + // Step 4: Start compressor + // + // TRUE ensures that we will write a complete interchange-JPEG file. + // Pass TRUE unless you are very sure of what you're doing. + + jpeg_start_compress(&cinfo, TRUE); + + //////////////////////////////////////// + // Step 5: while (scan lines remain to be written) + // jpeg_write_scanlines(...); + + // Here we use the library's state variable cinfo.next_scanline as the + // loop counter, so that we don't have to keep track ourselves. + // To keep things simple, we pass one scanline per call; you can pass + // more if you wish, though. + + row_stride = getWidth() * getComponents(); // JSAMPLEs per row in image_buffer + + // NOTE: For compatibility with LLImage, we need to invert the rows. + raw_image_data = raw_image->getData(); + + const U8* last_row_data = raw_image_data + (getHeight()-1) * row_stride; + + JSAMPROW row_pointer[1]; // pointer to JSAMPLE row[s] + while (cinfo.next_scanline < cinfo.image_height) + { + // jpeg_write_scanlines expects an array of pointers to scanlines. + // Here the array is only one element long, but you could pass + // more than one scanline at a time if that's more convenient. + + //Ugly const uncast here (jpeg_write_scanlines should take a const* but doesn't) + //row_pointer[0] = (JSAMPROW)(raw_image_data + (cinfo.next_scanline * row_stride)); + row_pointer[0] = (JSAMPROW)(last_row_data - (cinfo.next_scanline * row_stride)); + + jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + + //////////////////////////////////////// + // Step 6: Finish compression + jpeg_finish_compress(&cinfo); + + // After finish_compress, we can release the temp output buffer. + delete[] mOutputBuffer; + mOutputBuffer = NULL; + mOutputBufferSize = 0; + + //////////////////////////////////////// + // Step 7: release JPEG compression object + jpeg_destroy_compress(&cinfo); + } + + catch(int) + { + jpeg_destroy_compress(&cinfo); + delete[] mOutputBuffer; + mOutputBuffer = NULL; + mOutputBufferSize = 0; + return FALSE; + } + + return TRUE; +} diff --git a/indra/llimage/llimagejpeg.h b/indra/llimage/llimagejpeg.h new file mode 100644 index 0000000000..5684e3720f --- /dev/null +++ b/indra/llimage/llimagejpeg.h @@ -0,0 +1,63 @@ +/** + * @file llimagejpeg.h + * @brief This class compresses and decompresses JPEG files + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLIMAGEJPEG_H +#define LL_LLIMAGEJPEG_H + +#include + +#include "llimage.h" + +extern "C" { +#include "jpeglib/jinclude.h" +#include "jpeglib/jpeglib.h" +#include "jpeglib/jerror.h" +} + +class LLImageJPEG : public LLImageFormatted +{ +protected: + virtual ~LLImageJPEG(); + +public: + LLImageJPEG(); + + /*virtual*/ BOOL updateData(); + /*virtual*/ BOOL decode(LLImageRaw* raw_image, F32 time=0.0); + /*virtual*/ BOOL encode(const LLImageRaw* raw_image, F32 time=0.0); + + void setEncodeQuality( S32 q ) { mEncodeQuality = q; } // on a scale from 1 to 100 + S32 getEncodeQuality() { return mEncodeQuality; } + + // Callbacks registered with jpeglib + static void encodeInitDestination ( j_compress_ptr cinfo ); + static boolean encodeEmptyOutputBuffer(j_compress_ptr cinfo); + static void encodeTermDestination(j_compress_ptr cinfo); + + static void decodeInitSource(j_decompress_ptr cinfo); + static boolean decodeFillInputBuffer(j_decompress_ptr cinfo); + static void decodeSkipInputData(j_decompress_ptr cinfo, long num_bytes); + static void decodeTermSource(j_decompress_ptr cinfo); + + + static void errorExit(j_common_ptr cinfo); + static void errorEmitMessage(j_common_ptr cinfo, int msg_level); + static void errorOutputMessage(j_common_ptr cinfo); + + static BOOL decompress(LLImageJPEG* imagep); + +protected: + U8* mOutputBuffer; // temp buffer used during encoding + S32 mOutputBufferSize; // bytes in mOuputBuffer + + S32 mEncodeQuality; // on a scale from 1 to 100 + + jmp_buf mSetjmpBuffer; // To allow the library to abort. +}; + +#endif // LL_LLIMAGEJPEG_H diff --git a/indra/llimage/llimagetga.cpp b/indra/llimage/llimagetga.cpp new file mode 100644 index 0000000000..1007f8e2bb --- /dev/null +++ b/indra/llimage/llimagetga.cpp @@ -0,0 +1,1090 @@ +/** + * @file llimagetga.cpp + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llimagetga.h" +#include "llerror.h" +#include "llmath.h" + +// For expanding 5-bit pixel values to 8-bit with best rounding +// static +const U8 LLImageTGA::s5to8bits[32] = + { + 0, 8, 16, 25, 33, 41, 49, 58, + 66, 74, 82, 90, 99, 107, 115, 123, + 132, 140, 148, 156, 165, 173, 181, 189, + 197, 206, 214, 222, 230, 239, 247, 255 + }; + +inline void LLImageTGA::decodeTruecolorPixel15( U8* dst, const U8* src ) +{ + // We expand 5 bit data to 8 bit sample width. + // The format of the 16-bit (LSB first) input word is + // xRRRRRGGGGGBBBBB + U32 t = U32(src[0]) + (U32(src[1]) << 8); + dst[2] = s5to8bits[t & 0x1F]; // blue + t >>= 5; + dst[1] = s5to8bits[t & 0x1F]; // green + t >>= 5; + dst[0] = s5to8bits[t & 0x1F]; // red +} + +LLImageTGA::LLImageTGA() + : LLImageFormatted(IMG_CODEC_TGA), + mColorMap( NULL ), + mColorMapStart( 0 ), + mColorMapLength( 0 ), + mColorMapBytesPerEntry( 0 ), + mIs15Bit( FALSE ) +{ +} + +LLImageTGA::LLImageTGA(const LLString& file_name) + : LLImageFormatted(IMG_CODEC_TGA), + mColorMap( NULL ), + mColorMapStart( 0 ), + mColorMapLength( 0 ), + mColorMapBytesPerEntry( 0 ), + mIs15Bit( FALSE ) +{ + loadFile(file_name); +} + +LLImageTGA::~LLImageTGA() +{ + delete mColorMap; +} + +BOOL LLImageTGA::updateData() +{ + resetLastError(); + + // Check to make sure that this instance has been initialized with data + if (!getData() || (0 == getDataSize())) + { + setLastError("LLImageTGA uninitialized"); + return FALSE; + } + + // Pull image information from the header... + U8 flags; + U8 junk[256]; + + /**************************************************************************** + ** + ** For more information about the original Truevision TGA(tm) file format, + ** or for additional information about the new extensions to the + ** Truevision TGA file, refer to the "Truevision TGA File Format + ** Specification Version 2.0" available from Truevision or your + ** Truevision dealer. + ** + ** FILE STRUCTURE FOR THE ORIGINAL TRUEVISION TGA FILE + ** FIELD 1 : NUMBER OF CHARACTERS IN ID FIELD (1 BYTES) + ** FIELD 2 : COLOR MAP TYPE (1 BYTES) + ** FIELD 3 : IMAGE TYPE CODE (1 BYTES) + ** = 0 NO IMAGE DATA INCLUDED + ** = 1 UNCOMPRESSED, COLOR-MAPPED IMAGE + ** = 2 UNCOMPRESSED, TRUE-COLOR IMAGE + ** = 3 UNCOMPRESSED, BLACK AND WHITE IMAGE + ** = 9 RUN-LENGTH ENCODED COLOR-MAPPED IMAGE + ** = 10 RUN-LENGTH ENCODED TRUE-COLOR IMAGE + ** = 11 RUN-LENGTH ENCODED BLACK AND WHITE IMAGE + ** FIELD 4 : COLOR MAP SPECIFICATION (5 BYTES) + ** 4.1 : COLOR MAP ORIGIN (2 BYTES) + ** 4.2 : COLOR MAP LENGTH (2 BYTES) + ** 4.3 : COLOR MAP ENTRY SIZE (2 BYTES) + ** FIELD 5 : IMAGE SPECIFICATION (10 BYTES) + ** 5.1 : X-ORIGIN OF IMAGE (2 BYTES) + ** 5.2 : Y-ORIGIN OF IMAGE (2 BYTES) + ** 5.3 : WIDTH OF IMAGE (2 BYTES) + ** 5.4 : HEIGHT OF IMAGE (2 BYTES) + ** 5.5 : IMAGE PIXEL SIZE (1 BYTE) + ** 5.6 : IMAGE DESCRIPTOR BYTE (1 BYTE) + ** FIELD 6 : IMAGE ID FIELD (LENGTH SPECIFIED BY FIELD 1) + ** FIELD 7 : COLOR MAP DATA (BIT WIDTH SPECIFIED BY FIELD 4.3 AND + ** NUMBER OF COLOR MAP ENTRIES SPECIFIED IN FIELD 4.2) + ** FIELD 8 : IMAGE DATA FIELD (WIDTH AND HEIGHT SPECIFIED IN + ** FIELD 5.3 AND 5.4) + ****************************************************************************/ + + mDataOffset = 0; + mIDLength = *(getData()+mDataOffset++); + mColorMapType = *(getData()+mDataOffset++); + mImageType = *(getData()+mDataOffset++); + mColorMapIndexLo = *(getData()+mDataOffset++); + mColorMapIndexHi = *(getData()+mDataOffset++); + mColorMapLengthLo = *(getData()+mDataOffset++); + mColorMapLengthHi = *(getData()+mDataOffset++); + mColorMapDepth = *(getData()+mDataOffset++); + mXOffsetLo = *(getData()+mDataOffset++); + mXOffsetHi = *(getData()+mDataOffset++); + mYOffsetLo = *(getData()+mDataOffset++); + mYOffsetHi = *(getData()+mDataOffset++); + mWidthLo = *(getData()+mDataOffset++); + mWidthHi = *(getData()+mDataOffset++); + mHeightLo = *(getData()+mDataOffset++); + mHeightHi = *(getData()+mDataOffset++); + mPixelSize = *(getData()+mDataOffset++); + flags = *(getData()+mDataOffset++); + mAttributeBits = flags & 0xf; + mOriginRightBit = (flags & 0x10) >> 4; + mOriginTopBit = (flags & 0x20) >> 5; + mInterleave = (flags & 0xc0) >> 6; + + switch( mImageType ) + { + case 0: + // No image data included in file + setLastError("Unable to load file. TGA file contains no image data."); + return FALSE; + case 1: + // Colormapped uncompressed + if( 8 != mPixelSize ) + { + setLastError("Unable to load file. Colormapped images must have 8 bits per pixel."); + return FALSE; + } + break; + case 2: + // Truecolor uncompressed + break; + case 3: + // Monochrome uncompressed + if( 8 != mPixelSize ) + { + setLastError("Unable to load file. Monochrome images must have 8 bits per pixel."); + return FALSE; + } + break; + case 9: + // Colormapped, RLE + break; + case 10: + // Truecolor, RLE + break; + case 11: + // Monochrome, RLE + if( 8 != mPixelSize ) + { + setLastError("Unable to load file. Monochrome images must have 8 bits per pixel."); + return FALSE; + } + break; + default: + setLastError("Unable to load file. Unrecoginzed TGA image type."); + return FALSE; + } + + // discard the ID field, if any + if (mIDLength) + { + memcpy(junk, getData()+mDataOffset, mIDLength); + mDataOffset += mIDLength; + } + + // check to see if there's a colormap since even rgb files can have them + S32 color_map_bytes = 0; + if( (1 == mColorMapType) && (mColorMapDepth > 0) ) + { + mColorMapStart = (S32(mColorMapIndexHi) << 8) + mColorMapIndexLo; + mColorMapLength = (S32(mColorMapLengthHi) << 8) + mColorMapLengthLo; + + if( mColorMapDepth > 24 ) + { + mColorMapBytesPerEntry = 4; + } + else + if( mColorMapDepth > 16 ) + { + mColorMapBytesPerEntry = 3; + } + else + if( mColorMapDepth > 8 ) + { + mColorMapBytesPerEntry = 2; + } + else + { + mColorMapBytesPerEntry = 1; + } + color_map_bytes = mColorMapLength * mColorMapBytesPerEntry; + + // Note: although it's legal for TGA files to have color maps and not use them + // (some programs actually do this and use the color map for other ends), we'll + // only allocate memory for one if _we_ intend to use it. + if ( (1 == mImageType) || (9 == mImageType) ) + { + mColorMap = new U8[ color_map_bytes ]; + memcpy( mColorMap, getData() + mDataOffset, color_map_bytes ); + } + + mDataOffset += color_map_bytes; + } + + // heights are read as bytes to prevent endian problems + S32 height = (S32(mHeightHi) << 8) + mHeightLo; + S32 width = (S32(mWidthHi) << 8) + mWidthLo; + + // make sure that it's a pixel format that we understand + S32 bits_per_pixel; + if( mColorMap ) + { + bits_per_pixel = mColorMapDepth; + } + else + { + bits_per_pixel = mPixelSize; + } + + S32 components; + switch(bits_per_pixel) + { + case 24: + components = 3; + break; + case 32: + components = 4; +// Don't enforce this. ACDSee doesn't bother to set the attributes bits correctly. Arrgh! +// if( mAttributeBits != 8 ) +// { +// setLastError("Unable to load file. 32 bit TGA image does not have 8 bits of alpha."); +// return FALSE; +// } + mAttributeBits = 8; + break; + case 15: + case 16: + components = 3; + mIs15Bit = TRUE; // 16th bit is used for Targa hardware interupts and is ignored. + break; + case 8: + components = 1; + break; + default: + setLastError("Unable to load file. Unknown pixel size."); + return FALSE; + } + setSize(width, height, components); + + return TRUE; +} + +BOOL LLImageTGA::decode(LLImageRaw* raw_image, F32 decode_time) +{ + llassert_always(raw_image); + + // Check to make sure that this instance has been initialized with data + if (!getData() || (0 == getDataSize())) + { + setLastError("LLImageTGA trying to decode an image with no data!"); + return FALSE; + } + + // Copy everything after the header. + + raw_image->resize(getWidth(), getHeight(), getComponents()); + + if( (getComponents() != 1) && + (getComponents() != 3) && + (getComponents() != 4) ) + { + setLastError("TGA images with a number of components other than 1, 3, and 4 are not supported."); + return FALSE; + } + + + if( mOriginRightBit ) + { + setLastError("TGA images with origin on right side are not supported."); + return FALSE; + } + + BOOL flipped = (mOriginTopBit != 0); + BOOL rle_compressed = ((mImageType & 0x08) != 0); + + if( mColorMap ) + { + return decodeColorMap( raw_image, rle_compressed, flipped ); + } + else + { + return decodeTruecolor( raw_image, rle_compressed, flipped ); + } +} + +BOOL LLImageTGA::decodeTruecolor( LLImageRaw* raw_image, BOOL rle, BOOL flipped ) +{ + BOOL success = FALSE; + BOOL alpha_opaque = FALSE; + if( rle ) + { + + switch( getComponents() ) + { + case 1: + success = decodeTruecolorRle8( raw_image ); + break; + case 3: + if( mIs15Bit ) + { + success = decodeTruecolorRle15( raw_image ); + } + else + { + success = decodeTruecolorRle24( raw_image ); + } + break; + case 4: + success = decodeTruecolorRle32( raw_image, alpha_opaque ); + if (alpha_opaque) + { + // alpha was entirely opaque + // convert to 24 bit image + LLPointer compacted_image = new LLImageRaw(raw_image->getWidth(), raw_image->getHeight(), 3); + compacted_image->copy(raw_image); + raw_image->resize(raw_image->getWidth(), raw_image->getHeight(), 3); + raw_image->copy(compacted_image); + } + break; + } + } + else + { + BOOL alpha_opaque; + success = decodeTruecolorNonRle( raw_image, alpha_opaque ); + if (alpha_opaque && raw_image->getComponents() == 4) + { + // alpha was entirely opaque + // convert to 24 bit image + LLPointer compacted_image = new LLImageRaw(raw_image->getWidth(), raw_image->getHeight(), 3); + compacted_image->copy(raw_image); + raw_image->resize(raw_image->getWidth(), raw_image->getHeight(), 3); + raw_image->copy(compacted_image); + } + } + + if( success && flipped ) + { + // This works because the Targa definition requires that RLE blocks never + // encode pixels from more than one scanline. + // (On the other hand, it's not as fast as writing separate flipped versions as + // we did with TruecolorNonRle.) + raw_image->verticalFlip(); + } + + return success; +} + + +BOOL LLImageTGA::decodeTruecolorNonRle( LLImageRaw* raw_image, BOOL &alpha_opaque ) +{ + alpha_opaque = TRUE; + + // Origin is the bottom left + U8* dst = raw_image->getData(); + U8* src = getData() + mDataOffset; + S32 pixels = getWidth() * getHeight(); + + if (getComponents() == 4) + { + while( pixels-- ) + { + // Our data is stored in RGBA. TGA stores them as BGRA (little-endian ARGB) + dst[0] = src[2]; // Red + dst[1] = src[1]; // Green + dst[2] = src[0]; // Blue + dst[3] = src[3]; // Alpha + if (dst[3] != 255) + { + alpha_opaque = FALSE; + } + dst += 4; + src += 4; + } + } + else if (getComponents() == 3) + { + if( mIs15Bit ) + { + while( pixels-- ) + { + decodeTruecolorPixel15( dst, src ); + dst += 3; + src += 2; + } + } + else + { + while( pixels-- ) + { + dst[0] = src[2]; // Red + dst[1] = src[1]; // Green + dst[2] = src[0]; // Blue + dst += 3; + src += 3; + } + } + } + else if (getComponents() == 1) + { + memcpy(dst, src, pixels); + } + + return TRUE; +} + +void LLImageTGA::decodeColorMapPixel8( U8* dst, const U8* src ) +{ + S32 index = llclamp( *src - mColorMapStart, 0, mColorMapLength - 1 ); + dst[0] = mColorMap[ index ]; +} + +void LLImageTGA::decodeColorMapPixel15( U8* dst, const U8* src ) +{ + S32 index = llclamp( *src - mColorMapStart, 0, mColorMapLength - 1 ); + decodeTruecolorPixel15( dst, mColorMap + 2 * index ); +} + +void LLImageTGA::decodeColorMapPixel24( U8* dst, const U8* src ) +{ + S32 index = 3 * llclamp( *src - mColorMapStart, 0, mColorMapLength - 1 ); + dst[0] = mColorMap[ index + 2 ]; // Red + dst[1] = mColorMap[ index + 1 ]; // Green + dst[2] = mColorMap[ index + 0 ]; // Blue +} + +void LLImageTGA::decodeColorMapPixel32( U8* dst, const U8* src ) +{ + S32 index = 4 * llclamp( *src - mColorMapStart, 0, mColorMapLength - 1 ); + dst[0] = mColorMap[ index + 2 ]; // Red + dst[1] = mColorMap[ index + 1 ]; // Green + dst[2] = mColorMap[ index + 0 ]; // Blue + dst[3] = mColorMap[ index + 3 ]; // Alpha +} + + +BOOL LLImageTGA::decodeColorMap( LLImageRaw* raw_image, BOOL rle, BOOL flipped ) +{ + // If flipped, origin is the top left. Need to reverse the order of the rows. + // Otherwise the origin is the bottom left. + + if( 8 != mPixelSize ) + { + return FALSE; + } + + U8* src = getData() + mDataOffset; + U8* dst = raw_image->getData(); // start from the top + + void (LLImageTGA::*pixel_decoder)( U8*, const U8* ); + + switch( mColorMapBytesPerEntry ) + { + case 1: pixel_decoder = &LLImageTGA::decodeColorMapPixel8; break; + case 2: pixel_decoder = &LLImageTGA::decodeColorMapPixel15; break; + case 3: pixel_decoder = &LLImageTGA::decodeColorMapPixel24; break; + case 4: pixel_decoder = &LLImageTGA::decodeColorMapPixel32; break; + default: llassert(0); return FALSE; + } + + if( rle ) + { + U8* last_dst = dst + getComponents() * (getHeight() * getWidth() - 1); + while( dst <= last_dst ) + { + // Read RLE block header + U8 block_header_byte = *src; + src++; + + U8 block_pixel_count = (block_header_byte & 0x7F) + 1; + if( block_header_byte & 0x80 ) + { + // Encoded (duplicate-pixel) block + do + { + (this->*pixel_decoder)( dst, src ); + dst += getComponents(); + block_pixel_count--; + } + while( block_pixel_count > 0 ); + src++; + } + else + { + // Unencoded block + do + { + (this->*pixel_decoder)( dst, src ); + dst += getComponents(); + src++; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + } + } + + raw_image->verticalFlip(); + } + else + { + S32 src_row_bytes = getWidth(); + S32 dst_row_bytes = getWidth() * getComponents(); + + if( flipped ) + { + U8* src_last_row_start = src + (getHeight() - 1) * src_row_bytes; + src = src_last_row_start; // start from the bottom + src_row_bytes *= -1; + } + + + S32 i; + S32 j; + + for( S32 row = 0; row < getHeight(); row++ ) + { + for( i = 0, j = 0; j < getWidth(); i += getComponents(), j++ ) + { + (this->*pixel_decoder)( dst + i, src + j ); + } + + dst += dst_row_bytes; + src += src_row_bytes; + } + } + + return TRUE; +} + + + +BOOL LLImageTGA::encode(const LLImageRaw* raw_image, F32 encode_time) +{ + llassert_always(raw_image); + + deleteData(); + + setSize(raw_image->getWidth(), raw_image->getHeight(), raw_image->getComponents()); + + // Data from header + mIDLength = 0; // Length of identifier string + mColorMapType = 0; // 0 = No Map + + // Supported: 2 = Uncompressed true color, 3 = uncompressed monochrome without colormap + switch( getComponents() ) + { + case 1: + mImageType = 3; + break; + case 2: // Interpret as intensity plus alpha + case 3: + case 4: + mImageType = 2; + break; + default: + return FALSE; + } + + // Color map stuff (unsupported) + mColorMapIndexLo = 0; // First color map entry (low order byte) + mColorMapIndexHi = 0; // First color map entry (high order byte) + mColorMapLengthLo = 0; // Color map length (low order byte) + mColorMapLengthHi = 0; // Color map length (high order byte) + mColorMapDepth = 0; // Size of color map entry (15, 16, 24, or 32 bits) + + // Image offset relative to origin. + mXOffsetLo = 0; // X offset from origin (low order byte) + mXOffsetHi = 0; // X offset from origin (hi order byte) + mYOffsetLo = 0; // Y offset from origin (low order byte) + mYOffsetHi = 0; // Y offset from origin (hi order byte) + + // Height and width + mWidthLo = U8(getWidth() & 0xFF); // Width (low order byte) + mWidthHi = U8((getWidth() >> 8) & 0xFF); // Width (hi order byte) + mHeightLo = U8(getHeight() & 0xFF); // Height (low order byte) + mHeightHi = U8((getHeight() >> 8) & 0xFF); // Height (hi order byte) + + S32 bytes_per_pixel; + switch( getComponents() ) + { + case 1: + bytes_per_pixel = 1; + break; + case 3: + bytes_per_pixel = 3; + break; + case 2: // Interpret as intensity plus alpha. Store as RGBA. + case 4: + bytes_per_pixel = 4; + break; + default: + return FALSE; + } + mPixelSize = U8(bytes_per_pixel * 8); // 8, 16, 24, 32 bits per pixel + + mAttributeBits = (4 == bytes_per_pixel) ? 8 : 0; // 4 bits: number of attribute bits (alpha) per pixel + mOriginRightBit = 0; // 1 bit: origin, 0 = left, 1 = right + mOriginTopBit = 0; // 1 bit: origin, 0 = bottom, 1 = top + mInterleave = 0; // 2 bits: interleaved flag, 0 = none, 1 = interleaved 2, 2 = interleaved 4 + + + const S32 TGA_HEADER_SIZE = 18; + const S32 COLOR_MAP_SIZE = 0; + mDataOffset = TGA_HEADER_SIZE + mIDLength + COLOR_MAP_SIZE; // Offset from start of data to the actual header. + + S32 pixels = getWidth() * getHeight(); + S32 datasize = mDataOffset + bytes_per_pixel * pixels; + U8* dst = allocateData(datasize); + + // Write header + *(dst++) = mIDLength; + *(dst++) = mColorMapType; + *(dst++) = mImageType; + *(dst++) = mColorMapIndexLo; + *(dst++) = mColorMapIndexHi; + *(dst++) = mColorMapLengthLo; + *(dst++) = mColorMapLengthHi; + *(dst++) = mColorMapDepth; + *(dst++) = mXOffsetLo; + *(dst++) = mXOffsetHi; + *(dst++) = mYOffsetLo; + *(dst++) = mYOffsetHi; + *(dst++) = mWidthLo; + *(dst++) = mWidthHi; + *(dst++) = mHeightLo; + *(dst++) = mHeightHi; + *(dst++) = mPixelSize; + *(dst++) = + ((mInterleave & 3) << 5) | + ((mOriginTopBit & 1) << 4) | + ((mOriginRightBit & 1) << 3) | + ((mAttributeBits & 0xF) << 0); + + // Write pixels + const U8* src = raw_image->getData(); + llassert( dst == getData() + mDataOffset ); + S32 i = 0; + S32 j = 0; + switch( getComponents() ) + { + case 1: + memcpy( dst, src, bytes_per_pixel * pixels ); + break; + + case 2: + while( pixels-- ) + { + dst[i + 0] = src[j + 0]; // intensity + dst[i + 1] = src[j + 0]; // intensity + dst[i + 2] = src[j + 0]; // intensity + dst[i + 3] = src[j + 1]; // alpha + i += 4; + j += 2; + } + break; + + case 3: + while( pixels-- ) + { + dst[i + 0] = src[i + 2]; // blue + dst[i + 1] = src[i + 1]; // green + dst[i + 2] = src[i + 0]; // red + i += 3; + } + break; + + case 4: + while( pixels-- ) + { + dst[i + 0] = src[i + 2]; // blue + dst[i + 1] = src[i + 1]; // green + dst[i + 2] = src[i + 0]; // red + dst[i + 3] = src[i + 3]; // alpha + i += 4; + } + break; + } + + return TRUE; +} + +BOOL LLImageTGA::decodeTruecolorRle32( LLImageRaw* raw_image, BOOL &alpha_opaque ) +{ + llassert( getComponents() == 4 ); + alpha_opaque = TRUE; + + U8* dst = raw_image->getData(); + U32* dst_pixels = (U32*) dst; + + U8* src = getData() + mDataOffset; + + U32 rgba; + U8* rgba_byte_p = (U8*) &rgba; + + U32* last_dst_pixel = dst_pixels + getHeight() * getWidth() - 1; + while( dst_pixels <= last_dst_pixel ) + { + // Read RLE block header + U8 block_header_byte = *src; + src++; + + U32 block_pixel_count = (block_header_byte & 0x7F) + 1; + if( block_header_byte & 0x80 ) + { + // Encoded (duplicate-pixel) block + rgba_byte_p[0] = src[2]; + rgba_byte_p[1] = src[1]; + rgba_byte_p[2] = src[0]; + rgba_byte_p[3] = src[3]; + if (rgba_byte_p[3] != 255) + { + alpha_opaque = FALSE; + } + + src += 4; + register U32 value = rgba; + do + { + *dst_pixels = value; + dst_pixels++; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + } + else + { + // Unencoded block + do + { + ((U8*)dst_pixels)[0] = src[2]; + ((U8*)dst_pixels)[1] = src[1]; + ((U8*)dst_pixels)[2] = src[0]; + ((U8*)dst_pixels)[3] = src[3]; + if (src[3] != 255) + { + alpha_opaque = FALSE; + } + src += 4; + dst_pixels++; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + } + } + + return TRUE; +} + +BOOL LLImageTGA::decodeTruecolorRle15( LLImageRaw* raw_image ) +{ + llassert( getComponents() == 3 ); + llassert( mIs15Bit ); + + U8* dst = raw_image->getData(); + U8* src = getData() + mDataOffset; + + U8* last_dst = dst + getComponents() * (getHeight() * getWidth() - 1); + while( dst <= last_dst ) + { + // Read RLE block header + U8 block_header_byte = *src; + src++; + + U8 block_pixel_count = (block_header_byte & 0x7F) + 1; + if( block_header_byte & 0x80 ) + { + // Encoded (duplicate-pixel) block + do + { + decodeTruecolorPixel15( dst, src ); // slow + dst += 3; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + src += 2; + } + else + { + // Unencoded block + do + { + decodeTruecolorPixel15( dst, src ); + dst += 3; + src += 2; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + } + } + + return TRUE; +} + + + +BOOL LLImageTGA::decodeTruecolorRle24( LLImageRaw* raw_image ) +{ + llassert( getComponents() == 3 ); + + U8* dst = raw_image->getData(); + U8* src = getData() + mDataOffset; + + U8* last_dst = dst + getComponents() * (getHeight() * getWidth() - 1); + while( dst <= last_dst ) + { + // Read RLE block header + U8 block_header_byte = *src; + src++; + + U8 block_pixel_count = (block_header_byte & 0x7F) + 1; + if( block_header_byte & 0x80 ) + { + // Encoded (duplicate-pixel) block + do + { + dst[0] = src[2]; + dst[1] = src[1]; + dst[2] = src[0]; + dst += 3; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + src += 3; + } + else + { + // Unencoded block + do + { + dst[0] = src[2]; + dst[1] = src[1]; + dst[2] = src[0]; + dst += 3; + src += 3; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + } + } + + return TRUE; +} + + +BOOL LLImageTGA::decodeTruecolorRle8( LLImageRaw* raw_image ) +{ + llassert( getComponents() == 1 ); + + U8* dst = raw_image->getData(); + U8* src = getData() + mDataOffset; + + U8* last_dst = dst + getHeight() * getWidth() - 1; + while( dst <= last_dst ) + { + // Read RLE block header + U8 block_header_byte = *src; + src++; + + U8 block_pixel_count = (block_header_byte & 0x7F) + 1; + if( block_header_byte & 0x80 ) + { + // Encoded (duplicate-pixel) block + memset( dst, *src, block_pixel_count ); + dst += block_pixel_count; + src++; + } + else + { + // Unencoded block + do + { + *dst = *src; + dst++; + src++; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + } + } + + return TRUE; +} + + +// Decoded and process the image for use in avatar gradient masks. +// Processing happens during the decode for speed. +BOOL LLImageTGA::decodeAndProcess( LLImageRaw* raw_image, F32 domain, F32 weight ) +{ + llassert_always(raw_image); + + // "Domain" isn't really the right word. It refers to the width of the + // ramp portion of the function that relates input and output pixel values. + // A domain of 0 gives a step function. + // + // | /---------------- + // O| / | + // u| / | + // t| / | + // p|------------------/ | + // u| | | + // t|<---------------->|<-->| + // | "offset" "domain" + // | + // --+---Input-------------------------------- + // | + + if (!getData() || (0 == getDataSize())) + { + setLastError("LLImageTGA trying to decode an image with no data!"); + return FALSE; + } + + // Only works for unflipped monochrome RLE images + if( (getComponents() != 1) || (mImageType != 11) || mOriginTopBit || mOriginRightBit ) + { + llerrs << "LLImageTGA trying to alpha-gradient process an image that's not a standard RLE, one component image" << llendl; + return FALSE; + } + + raw_image->resize(getWidth(), getHeight(), getComponents()); + + U8* dst = raw_image->getData(); + U8* src = getData() + mDataOffset; + U8* last_dst = dst + getHeight() * getWidth() - 1; + + if( domain > 0 ) + { + // Process using a look-up table (lut) + const S32 LUT_LEN = 256; + U8 lut[LUT_LEN]; + S32 i; + + F32 scale = 1.f / domain; + F32 offset = (1.f - domain) * llclampf( 1.f - weight ); + F32 bias = -(scale * offset); + + for( i = 0; i < LUT_LEN; i++ ) + { + lut[i] = (U8)llclampb( 255.f * ( i/255.f * scale + bias ) ); + } + + while( dst <= last_dst ) + { + // Read RLE block header + U8 block_header_byte = *src; + src++; + + U8 block_pixel_count = (block_header_byte & 0x7F) + 1; + if( block_header_byte & 0x80 ) + { + // Encoded (duplicate-pixel) block + memset( dst, lut[ *src ], block_pixel_count ); + dst += block_pixel_count; + src++; + } + else + { + // Unencoded block + do + { + *dst = lut[ *src ]; + dst++; + src++; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + } + } + } + else + { + // Process using a simple comparison agains a threshold + const U8 threshold = (U8)(0xFF * llclampf( 1.f - weight )); + + while( dst <= last_dst ) + { + // Read RLE block header + U8 block_header_byte = *src; + src++; + + U8 block_pixel_count = (block_header_byte & 0x7F) + 1; + if( block_header_byte & 0x80 ) + { + // Encoded (duplicate-pixel) block + memset( dst, ((*src >= threshold) ? 0xFF : 0), block_pixel_count ); + dst += block_pixel_count; + src++; + } + else + { + // Unencoded block + do + { + *dst = (*src >= threshold) ? 0xFF : 0; + dst++; + src++; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + } + } + } + return TRUE; +} + +// Reads a .tga file and creates an LLImageTGA with its data. +bool LLImageTGA::loadFile( const LLString& path ) +{ + S32 len = path.size(); + if( len < 5 ) + { + return false; + } + + LLString extension = path.substr( len - 4, 4 ); + LLString::toLower(extension); + if( ".tga" != extension ) + { + return false; + } + + FILE *file = LLFile::fopen(path.c_str(), "rb"); + if( !file ) + { + llwarns << "Couldn't open file " << path << llendl; + return false; + } + + S32 file_size = 0; + if (!fseek(file, 0, SEEK_END)) + { + file_size = ftell(file); + fseek(file, 0, SEEK_SET); + } + + U8* buffer = allocateData(file_size); + S32 bytes_read = fread(buffer, 1, file_size, file); + if( bytes_read != file_size ) + { + deleteData(); + llwarns << "Couldn't read file " << path << llendl; + return false; + } + + fclose( file ); + + if( !updateData() ) + { + llwarns << "Couldn't decode file " << path << llendl; + deleteData(); + return false; + } + return true; +} + + diff --git a/indra/llimage/llimagetga.h b/indra/llimage/llimagetga.h new file mode 100644 index 0000000000..376d6dc269 --- /dev/null +++ b/indra/llimage/llimagetga.h @@ -0,0 +1,89 @@ +/** + * @file llimagetga.h + * @brief Image implementation to compresses and decompressed TGA files. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLIMAGETGA_H +#define LL_LLIMAGETGA_H + +#include "llimage.h" + +// This class compresses and decompressed TGA (targa) files + +class LLImageTGA : public LLImageFormatted +{ +protected: + virtual ~LLImageTGA(); + +public: + LLImageTGA(); + LLImageTGA(const LLString& file_name); + + /*virtual*/ BOOL updateData(); + /*virtual*/ BOOL decode(LLImageRaw* raw_image, F32 decode_time=0.0); + /*virtual*/ BOOL encode(const LLImageRaw* raw_image, F32 encode_time=0.0); + + BOOL decodeAndProcess(LLImageRaw* raw_image, F32 domain, F32 weight); + +private: + BOOL decodeTruecolor( LLImageRaw* raw_image, BOOL rle, BOOL flipped ); + + BOOL decodeTruecolorRle8( LLImageRaw* raw_image ); + BOOL decodeTruecolorRle15( LLImageRaw* raw_image ); + BOOL decodeTruecolorRle24( LLImageRaw* raw_image ); + BOOL decodeTruecolorRle32( LLImageRaw* raw_image, BOOL &alpha_opaque ); + + void decodeTruecolorPixel15( U8* dst, const U8* src ); + + BOOL decodeTruecolorNonRle( LLImageRaw* raw_image, BOOL &alpha_opaque ); + + BOOL decodeColorMap( LLImageRaw* raw_image, BOOL rle, BOOL flipped ); + + void decodeColorMapPixel8(U8* dst, const U8* src); + void decodeColorMapPixel15(U8* dst, const U8* src); + void decodeColorMapPixel24(U8* dst, const U8* src); + void decodeColorMapPixel32(U8* dst, const U8* src); + + bool loadFile(const LLString& file_name); + +private: + // Class specific data + U32 mDataOffset; // Offset from start of data to the actual header. + + // Data from header + U8 mIDLength; // Length of identifier string + U8 mColorMapType; // 0 = No Map + U8 mImageType; // Supported: 2 = Uncompressed true color, 3 = uncompressed monochrome without colormap + U8 mColorMapIndexLo; // First color map entry (low order byte) + U8 mColorMapIndexHi; // First color map entry (high order byte) + U8 mColorMapLengthLo; // Color map length (low order byte) + U8 mColorMapLengthHi; // Color map length (high order byte) + U8 mColorMapDepth; // Size of color map entry (15, 16, 24, or 32 bits) + U8 mXOffsetLo; // X offset of image (low order byte) + U8 mXOffsetHi; // X offset of image (hi order byte) + U8 mYOffsetLo; // Y offset of image (low order byte) + U8 mYOffsetHi; // Y offset of image (hi order byte) + U8 mWidthLo; // Width (low order byte) + U8 mWidthHi; // Width (hi order byte) + U8 mHeightLo; // Height (low order byte) + U8 mHeightHi; // Height (hi order byte) + U8 mPixelSize; // 8, 16, 24, 32 bits per pixel + U8 mAttributeBits; // 4 bits: number of attributes per pixel + U8 mOriginRightBit; // 1 bit: origin, 0 = left, 1 = right + U8 mOriginTopBit; // 1 bit: origin, 0 = bottom, 1 = top + U8 mInterleave; // 2 bits: interleaved flag, 0 = none, 1 = interleaved 2, 2 = interleaved 4 + + U8* mColorMap; + S32 mColorMapStart; + S32 mColorMapLength; + S32 mColorMapBytesPerEntry; + + BOOL mIs15Bit; + + static const U8 s5to8bits[32]; +}; + +#endif diff --git a/indra/llimage/llmapimagetype.h b/indra/llimage/llmapimagetype.h new file mode 100644 index 0000000000..6b66506a28 --- /dev/null +++ b/indra/llimage/llmapimagetype.h @@ -0,0 +1,22 @@ +/** + * @file llmapimagetype.h + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMAPIMAGETYPE_H +#define LL_LLMAPIMAGETYPE_H + +typedef enum e_map_image_type +{ + MIT_TERRAIN = 0, + MIT_POPULAR = 1, + MIT_OBJECTS = 2, + MIT_OBJECTS_FOR_SALE = 3, + MIT_LAND_TO_BUY = 4, + MIT_OBJECT_NEW = 5, + MIT_EOF = 6 +} EMapImageType; + +#endif diff --git a/indra/llinventory/llcategory.cpp b/indra/llinventory/llcategory.cpp new file mode 100644 index 0000000000..7a7bbf530a --- /dev/null +++ b/indra/llinventory/llcategory.cpp @@ -0,0 +1,161 @@ +/** + * @file llcategory.cpp + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llcategory.h" + +#include "message.h" + +const LLCategory LLCategory::none; + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- + +// This is the storage of the category names. It's loosely based on a +// heap-like structure with indices into it for faster searching and +// so that we don't have to maintain a balanced heap. It's *VITALLY* +// important that the CATEGORY_INDEX and CATEGORY_NAME tables are kept +// in synch. + +// CATEGORY_INDEX indexes into CATEGORY_NAME at the first occurance of +// a child. Thus, the first child of root is "Object" which is located +// in CATEGORY_NAME[1]. +const S32 CATEGORY_INDEX[] = +{ + 1, // ROOT + 6, // object + 7, // clothing + 7, // texture + 7, // sound + 7, // landmark + 7, // object|component + 7, // off the end (required for child count calculations) +}; + +// The heap of names +const char* CATEGORY_NAME[] = +{ + "(none)", + "Object", // (none) + "Clothing", + "Texture", + "Sound", + "Landmark", + "Component", // object + NULL +}; + +///---------------------------------------------------------------------------- +/// Class llcategory +///---------------------------------------------------------------------------- + +LLCategory::LLCategory() +{ + // this is used as a simple compile time assertion. If this code + // fails to compile, the depth has been changed, and we need to + // clean up some of the code that relies on the depth, such as the + // default constructor. If CATEGORY_DEPTH != 4, this code will + // attempt to construct a zero length array - which the compiler + // should balk at. +// static const char CATEGORY_DEPTH_CHECK[(CATEGORY_DEPTH == 4)?1:0] = {' '}; // unused + + // actually initialize the object. + mData[0] = 0; + mData[1] = 0; + mData[2] = 0; + mData[3] = 0; +} + +void LLCategory::init(U32 value) +{ + U8 v; + for(S32 i = 0; i < CATEGORY_DEPTH; i++) + { + v = (U8)((0x000000ff) & value); + mData[CATEGORY_DEPTH - 1 - i] = v; + value >>= 8; + } +} + +U32 LLCategory::getU32() const +{ + U32 rv = 0; + rv |= mData[0]; + rv <<= 8; + rv |= mData[1]; + rv <<= 8; + rv |= mData[2]; + rv <<= 8; + rv |= mData[3]; + return rv; +} + +S32 LLCategory::getSubCategoryCount() const +{ + S32 rv = CATEGORY_INDEX[mData[0] + 1] - CATEGORY_INDEX[mData[0]]; + return rv; +} + +// This method will return a category that is the nth subcategory. If +// you're already at the bottom of the hierarchy, then the method will +// return a copy of this. +LLCategory LLCategory::getSubCategory(U8 n) const +{ + LLCategory rv(*this); + for(S32 i = 0; i < (CATEGORY_DEPTH - 1); i++) + { + if(rv.mData[i] == 0) + { + rv.mData[i] = n + 1; + break; + } + } + return rv; +} + +// This method will return the name of the leaf category type +const char* LLCategory::lookupName() const +{ + S32 i = 0; + S32 index = mData[i++]; + while((i < CATEGORY_DEPTH) && (mData[i] != 0)) + { + index = CATEGORY_INDEX[index]; + ++i; + } + return CATEGORY_NAME[index]; +} + +// message serialization +void LLCategory::packMessage(LLMessageSystem* msg) const +{ + U32 data = getU32(); + msg->addU32Fast(_PREHASH_Category, data); +} + +// message serialization +void LLCategory::unpackMessage(LLMessageSystem* msg, const char* block) +{ + U32 data; + msg->getU32Fast(block, _PREHASH_Category, data); + init(data); +} + +// message serialization +void LLCategory::unpackMultiMessage(LLMessageSystem* msg, const char* block, + S32 block_num) +{ + U32 data; + msg->getU32Fast(block, _PREHASH_Category, data, block_num); + init(data); +} + +///---------------------------------------------------------------------------- +/// Local function definitions +///---------------------------------------------------------------------------- diff --git a/indra/llinventory/llcategory.h b/indra/llinventory/llcategory.h new file mode 100644 index 0000000000..a28b707d93 --- /dev/null +++ b/indra/llinventory/llcategory.h @@ -0,0 +1,80 @@ +/** + * @file llcategory.h + * @brief LLCategory class header file. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLCATEGORY_H +#define LL_LLCATEGORY_H + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLCategory +// +// An instance of the LLCategory class represents a particular +// category in a hierarchical classification system. For now, it is 4 +// levels deep with 255 (minus 1) possible values at each level. If a +// non zero value is found at level 4, that is the leaf category, +// otherwise, it is the first level that has a 0 in the next depth +// level. +// +// To output the names of all top level categories, you could do the +// following: +// +// S32 count = LLCategory::none.getSubCategoryCount(); +// for(S32 i = 0; i < count; i++) +// { +// llinfos << none.getSubCategory(i).lookupNmae() << llendl; +// } +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMessageSystem; + +class LLCategory +{ +public: + // Nice default static const. + static const LLCategory none; + + // construction. Since this is really a POD type, destruction, + // copy, and assignment are handled by the compiler. + LLCategory(); + explicit LLCategory(U32 value) { init(value); } + + // methods + void init(U32 value); + U32 getU32() const; + S32 getSubCategoryCount() const; + + // This method will return a category that is the nth + // subcategory. If you're already at the bottom of the hierarchy, + // then the method will return a copy of this. + LLCategory getSubCategory(U8 n) const; + + // This method will return the name of the leaf category type + const char* lookupName() const; + + // This method will return the full hierarchy name in an easily + // interpreted (TOP)|(SUB1)|(SUB2) format. *NOTE: not implemented + // because we don't have anything but top level categories at the + // moment. + //const char* lookupFullName() const; + + // message serialization + void packMessage(LLMessageSystem* msg) const; + void unpackMessage(LLMessageSystem* msg, const char* block); + void unpackMultiMessage(LLMessageSystem* msg, const char* block, + S32 block_num); +protected: + enum + { + CATEGORY_TOP = 0, + CATEGORY_DEPTH = 4, + }; + + U8 mData[CATEGORY_DEPTH]; +}; + + +#endif // LL_LLCATEGORY_H diff --git a/indra/llinventory/lleconomy.cpp b/indra/llinventory/lleconomy.cpp new file mode 100644 index 0000000000..378ab8ced1 --- /dev/null +++ b/indra/llinventory/lleconomy.cpp @@ -0,0 +1,227 @@ +/** + * @file lleconomy.cpp + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lleconomy.h" +#include "llerror.h" +#include "message.h" +#include "v3math.h" + +LLGlobalEconomy::LLGlobalEconomy() +: mObjectCount( -1 ), + mObjectCapacity( -1 ), + mPriceObjectClaim( -1 ), + mPricePublicObjectDecay( -1 ), + mPricePublicObjectDelete( -1 ), + mPriceEnergyUnit( -1 ), + mPriceUpload( -1 ), + mPriceRentLight( -1 ), + mTeleportMinPrice( -1 ), + mTeleportPriceExponent( -1 ), + mPriceGroupCreate( -1 ) +{ } + +LLGlobalEconomy::~LLGlobalEconomy() +{ } + +// static +void LLGlobalEconomy::processEconomyData(LLMessageSystem *msg, void** user_data) +{ + S32 i; + F32 f; + + LLGlobalEconomy *this_ptr = (LLGlobalEconomy*)user_data; + + msg->getS32Fast(_PREHASH_Info, _PREHASH_ObjectCapacity, i); + this_ptr->setObjectCapacity(i); + msg->getS32Fast(_PREHASH_Info, _PREHASH_ObjectCount, i); + this_ptr->setObjectCount(i); + msg->getS32Fast(_PREHASH_Info, _PREHASH_PriceEnergyUnit, i); + this_ptr->setPriceEnergyUnit(i); + msg->getS32Fast(_PREHASH_Info, _PREHASH_PriceObjectClaim, i); + this_ptr->setPriceObjectClaim(i); + msg->getS32Fast(_PREHASH_Info, _PREHASH_PricePublicObjectDecay, i); + this_ptr->setPricePublicObjectDecay(i); + msg->getS32Fast(_PREHASH_Info, _PREHASH_PricePublicObjectDelete, i); + this_ptr->setPricePublicObjectDelete(i); + msg->getS32Fast(_PREHASH_Info, _PREHASH_PriceUpload, i); + this_ptr->setPriceUpload(i); + msg->getS32Fast(_PREHASH_Info, _PREHASH_PriceRentLight, i); + this_ptr->setPriceRentLight(i); + msg->getS32Fast(_PREHASH_Info, _PREHASH_TeleportMinPrice, i); + this_ptr->setTeleportMinPrice(i); + msg->getF32Fast(_PREHASH_Info, _PREHASH_TeleportPriceExponent, f); + this_ptr->setTeleportPriceExponent(f); + msg->getS32Fast(_PREHASH_Info, _PREHASH_PriceGroupCreate, i); + this_ptr->setPriceGroupCreate(i); +} + +S32 LLGlobalEconomy::calculateTeleportCost(F32 distance) const +{ + S32 min_cost = getTeleportMinPrice(); + F32 exponent = getTeleportPriceExponent(); + F32 divisor = 100.f * pow(3.f, exponent); + S32 cost = (U32)(distance * pow(log10(distance), exponent) / divisor); + if (cost < 0) + { + cost = 0; + } + else if (cost < min_cost) + { + cost = min_cost; + } + + return cost; +} + +S32 LLGlobalEconomy::calculateLightRent(const LLVector3& object_size) const +{ + F32 intensity_mod = llmax(object_size.magVec(), 1.f); + return (S32)(intensity_mod * getPriceRentLight()); +} + +void LLGlobalEconomy::print() +{ + llinfos << "Global Economy Settings: " << llendl; + llinfos << "Object Capacity: " << mObjectCapacity << llendl; + llinfos << "Object Count: " << mObjectCount << llendl; + llinfos << "Claim Price Per Object: " << mPriceObjectClaim << llendl; + llinfos << "Claim Price Per Public Object: " << mPricePublicObjectDecay << llendl; + llinfos << "Delete Price Per Public Object: " << mPricePublicObjectDelete << llendl; + llinfos << "Release Price Per Public Object: " << getPricePublicObjectRelease() << llendl; + llinfos << "Price Per Energy Unit: " << mPriceEnergyUnit << llendl; + llinfos << "Price Per Upload: " << mPriceUpload << llendl; + llinfos << "Light Base Price: " << mPriceRentLight << llendl; + llinfos << "Teleport Min Price: " << mTeleportMinPrice << llendl; + llinfos << "Teleport Price Exponent: " << mTeleportPriceExponent << llendl; + llinfos << "Price for group creation: " << mPriceGroupCreate << llendl; +} + +LLRegionEconomy::LLRegionEconomy() +: LLGlobalEconomy(), + mPriceObjectRent( -1.f ), + mPriceObjectScaleFactor( -1.f ), + mEnergyEfficiency( -1.f ), + mBasePriceParcelClaimDefault(-1), + mBasePriceParcelClaimActual(-1), + mPriceParcelClaimFactor(-1.f), + mBasePriceParcelRent(-1), + mAreaOwned(-1.f), + mAreaTotal(-1.f) +{ } + +LLRegionEconomy::~LLRegionEconomy() +{ } + +BOOL LLRegionEconomy::hasData() const +{ + return (mBasePriceParcelRent != -1); +} + +// static +void LLRegionEconomy::processEconomyData(LLMessageSystem *msg, void** user_data) +{ + S32 i; + F32 f; + + LLGlobalEconomy::processEconomyData(msg, user_data); + + LLRegionEconomy *this_ptr = (LLRegionEconomy*)user_data; + + msg->getS32Fast(_PREHASH_Info, _PREHASH_PriceParcelClaim, i); + this_ptr->setBasePriceParcelClaimDefault(i); + msg->getF32(_PREHASH_Info, _PREHASH_PriceParcelClaimFactor, f); + this_ptr->setPriceParcelClaimFactor(f); + msg->getF32Fast(_PREHASH_Info, _PREHASH_EnergyEfficiency, f); + this_ptr->setEnergyEfficiency(f); + msg->getF32Fast(_PREHASH_Info, _PREHASH_PriceObjectRent, f); + this_ptr->setPriceObjectRent(f); + msg->getF32Fast(_PREHASH_Info, _PREHASH_PriceObjectScaleFactor, f); + this_ptr->setPriceObjectScaleFactor(f); + msg->getS32Fast(_PREHASH_Info, _PREHASH_PriceParcelRent, i); + this_ptr->setBasePriceParcelRent(i); +} + +// static +void LLRegionEconomy::processEconomyDataRequest(LLMessageSystem *msg, void **user_data) +{ + LLRegionEconomy *this_ptr = (LLRegionEconomy*)user_data; + + msg->newMessageFast(_PREHASH_EconomyData); + msg->nextBlockFast(_PREHASH_Info); + msg->addS32Fast(_PREHASH_ObjectCapacity, this_ptr->getObjectCapacity()); + msg->addS32Fast(_PREHASH_ObjectCount, this_ptr->getObjectCount()); + msg->addS32Fast(_PREHASH_PriceEnergyUnit, this_ptr->getPriceEnergyUnit()); + msg->addS32Fast(_PREHASH_PriceObjectClaim, this_ptr->getPriceObjectClaim()); + msg->addS32Fast(_PREHASH_PricePublicObjectDecay, this_ptr->getPricePublicObjectDecay()); + msg->addS32Fast(_PREHASH_PricePublicObjectDelete, this_ptr->getPricePublicObjectDelete()); + msg->addS32Fast(_PREHASH_PriceParcelClaim, this_ptr->mBasePriceParcelClaimActual); + msg->addF32Fast(_PREHASH_PriceParcelClaimFactor, this_ptr->mPriceParcelClaimFactor); + msg->addS32Fast(_PREHASH_PriceUpload, this_ptr->getPriceUpload()); + msg->addS32Fast(_PREHASH_PriceRentLight, this_ptr->getPriceRentLight()); + msg->addS32Fast(_PREHASH_TeleportMinPrice, this_ptr->getTeleportMinPrice()); + msg->addF32Fast(_PREHASH_TeleportPriceExponent, this_ptr->getTeleportPriceExponent()); + + msg->addF32Fast(_PREHASH_EnergyEfficiency, this_ptr->getEnergyEfficiency()); + msg->addF32Fast(_PREHASH_PriceObjectRent, this_ptr->getPriceObjectRent()); + msg->addF32Fast(_PREHASH_PriceObjectScaleFactor, this_ptr->getPriceObjectScaleFactor()); + msg->addS32Fast(_PREHASH_PriceParcelRent, this_ptr->getPriceParcelRent()); + msg->addS32Fast(_PREHASH_PriceGroupCreate, this_ptr->getPriceGroupCreate()); + + msg->sendReliable(msg->getSender()); +} + + +S32 LLRegionEconomy::getPriceParcelClaim() const +{ + //return (S32)((F32)mBasePriceParcelClaim * (mAreaTotal / (mAreaTotal - mAreaOwned))); + return (S32)((F32)mBasePriceParcelClaimActual * mPriceParcelClaimFactor); +} + +S32 LLRegionEconomy::getPriceParcelRent() const +{ + return mBasePriceParcelRent; +} + + +void LLRegionEconomy::print() +{ + this->LLGlobalEconomy::print(); + + llinfos << "Region Economy Settings: " << llendl; + llinfos << "Land (square meters): " << mAreaTotal << llendl; + llinfos << "Owned Land (square meters): " << mAreaOwned << llendl; + llinfos << "Daily Object Rent: " << mPriceObjectRent << llendl; + llinfos << "Daily Land Rent (per meter): " << getPriceParcelRent() << llendl; + llinfos << "Energey Efficiency: " << mEnergyEfficiency << llendl; +} + + +void LLRegionEconomy::setBasePriceParcelClaimDefault(S32 val) +{ + mBasePriceParcelClaimDefault = val; + if(mBasePriceParcelClaimActual == -1) + { + mBasePriceParcelClaimActual = val; + } +} + +void LLRegionEconomy::setBasePriceParcelClaimActual(S32 val) +{ + mBasePriceParcelClaimActual = val; +} + +void LLRegionEconomy::setPriceParcelClaimFactor(F32 val) +{ + mPriceParcelClaimFactor = val; +} + +void LLRegionEconomy::setBasePriceParcelRent(S32 val) +{ + mBasePriceParcelRent = val; +} diff --git a/indra/llinventory/lleconomy.h b/indra/llinventory/lleconomy.h new file mode 100644 index 0000000000..c71fc9e6d0 --- /dev/null +++ b/indra/llinventory/lleconomy.h @@ -0,0 +1,116 @@ +/** + * @file lleconomy.h + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLECONOMY_H +#define LL_LLECONOMY_H + +class LLMessageSystem; +class LLVector3; + +class LLGlobalEconomy +{ +public: + LLGlobalEconomy(); + virtual ~LLGlobalEconomy(); + + virtual void print(); + + static void processEconomyData(LLMessageSystem *msg, void **user_data); + + S32 calculateTeleportCost(F32 distance) const; + S32 calculateLightRent(const LLVector3& object_size) const; + + S32 getObjectCount() const { return mObjectCount; } + S32 getObjectCapacity() const { return mObjectCapacity; } + S32 getPriceObjectClaim() const { return mPriceObjectClaim; } + S32 getPricePublicObjectDecay() const { return mPricePublicObjectDecay; } + S32 getPricePublicObjectDelete() const { return mPricePublicObjectDelete; } + S32 getPricePublicObjectRelease() const { return mPriceObjectClaim - mPricePublicObjectDelete; } + S32 getPriceEnergyUnit() const { return mPriceEnergyUnit; } + S32 getPriceUpload() const { return mPriceUpload; } + S32 getPriceRentLight() const { return mPriceRentLight; } + S32 getTeleportMinPrice() const { return mTeleportMinPrice; } + F32 getTeleportPriceExponent() const { return mTeleportPriceExponent; } + S32 getPriceGroupCreate() const { return mPriceGroupCreate; } + + + void setObjectCount(S32 val) { mObjectCount = val; } + void setObjectCapacity(S32 val) { mObjectCapacity = val; } + void setPriceObjectClaim(S32 val) { mPriceObjectClaim = val; } + void setPricePublicObjectDecay(S32 val) { mPricePublicObjectDecay = val; } + void setPricePublicObjectDelete(S32 val) { mPricePublicObjectDelete = val; } + void setPriceEnergyUnit(S32 val) { mPriceEnergyUnit = val; } + void setPriceUpload(S32 val) { mPriceUpload = val; } + void setPriceRentLight(S32 val) { mPriceRentLight = val; } + void setTeleportMinPrice(S32 val) { mTeleportMinPrice = val; } + void setTeleportPriceExponent(F32 val) { mTeleportPriceExponent = val; } + void setPriceGroupCreate(S32 val) { mPriceGroupCreate = val; } + +private: + S32 mObjectCount; + S32 mObjectCapacity; + S32 mPriceObjectClaim; // per primitive + S32 mPricePublicObjectDecay; // per primitive + S32 mPricePublicObjectDelete; // per primitive + S32 mPriceEnergyUnit; + S32 mPriceUpload; + S32 mPriceRentLight; + S32 mTeleportMinPrice; + F32 mTeleportPriceExponent; + S32 mPriceGroupCreate; +}; + + +class LLRegionEconomy : public LLGlobalEconomy +{ +public: + LLRegionEconomy(); + ~LLRegionEconomy(); + + static void processEconomyData(LLMessageSystem *msg, void **user_data); + static void processEconomyDataRequest(LLMessageSystem *msg, void **user_data); + + void print(); + + BOOL hasData() const; + F32 getPriceObjectRent() const { return mPriceObjectRent; } + F32 getPriceObjectScaleFactor() const {return mPriceObjectScaleFactor;} + F32 getEnergyEfficiency() const { return mEnergyEfficiency; } + S32 getPriceParcelClaim() const; + S32 getPriceParcelRent() const; + F32 getAreaOwned() const { return mAreaOwned; } + F32 getAreaTotal() const { return mAreaTotal; } + S32 getBasePriceParcelClaimActual() const { return mBasePriceParcelClaimActual; } + + void setPriceObjectRent(F32 val) { mPriceObjectRent = val; } + void setPriceObjectScaleFactor(F32 val) { mPriceObjectScaleFactor = val; } + void setEnergyEfficiency(F32 val) { mEnergyEfficiency = val; } + + void setBasePriceParcelClaimDefault(S32 val); + void setBasePriceParcelClaimActual(S32 val); + void setPriceParcelClaimFactor(F32 val); + void setBasePriceParcelRent(S32 val); + + void setAreaOwned(F32 val) { mAreaOwned = val; } + void setAreaTotal(F32 val) { mAreaTotal = val; } + +private: + F32 mPriceObjectRent; + F32 mPriceObjectScaleFactor; + F32 mEnergyEfficiency; + + S32 mBasePriceParcelClaimDefault; + S32 mBasePriceParcelClaimActual; + F32 mPriceParcelClaimFactor; + S32 mBasePriceParcelRent; + + F32 mAreaOwned; + F32 mAreaTotal; + +}; + +#endif diff --git a/indra/llinventory/llinventory.cpp b/indra/llinventory/llinventory.cpp new file mode 100644 index 0000000000..bffc4df281 --- /dev/null +++ b/indra/llinventory/llinventory.cpp @@ -0,0 +1,1773 @@ +/** + * @file llinventory.cpp + * @brief Implementation of the inventory system. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include + +#include "llinventory.h" + +#include "lldbstrings.h" +#include "llcrypto.h" +#include "llsd.h" +#include "message.h" +#include + +#include "llsdutil.h" + +#include "llsdutil.h" + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- + +const U8 TASK_INVENTORY_ITEM_KEY = 0; +const U8 TASK_INVENTORY_ASSET_KEY = 1; + +const LLUUID MAGIC_ID("3c115e51-04f4-523c-9fa6-98aff1034730"); + +// helper function which returns true if inventory type and asset type +// are potentially compatible. For example, an attachment must be an +// object, but a wearable can be a bodypart or clothing asset. +bool inventory_and_asset_types_match( + LLInventoryType::EType inventory_type, + LLAssetType::EType asset_type); + + +///---------------------------------------------------------------------------- +/// Class LLInventoryType +///---------------------------------------------------------------------------- + +// Unlike asset type names, not limited to 8 characters. +// Need not match asset type names. +static const char* INVENTORY_TYPE_NAMES[LLInventoryType::IT_COUNT] = +{ + "texture", // 0 + "sound", + "callcard", + "landmark", + NULL, + NULL, // 5 + "object", + "notecard", + "category", + "root", + "script", // 10 + NULL, + NULL, + NULL, + NULL, + "snapshot", // 15 + NULL, + "attach", + "wearable", + "animation", + "gesture", // 20 +}; + +// This table is meant for decoding to human readable form. Put any +// and as many printable characters you want in each one. +// See also LLAssetType::mAssetTypeHumanNames +static const char* INVENTORY_TYPE_HUMAN_NAMES[LLInventoryType::IT_COUNT] = +{ + "texture", // 0 + "sound", + "calling card", + "landmark", + NULL, + NULL, // 5 + "object", + "note card", + "folder", + "root", + "script", // 10 + NULL, + NULL, + NULL, + NULL, + "snapshot", // 15 + NULL, + "attachment", + "wearable", + "animation", + "gesture", // 20 +}; + +// Maps asset types to the default inventory type for that kind of asset. +// Thus, "Lost and Found" is a "Category" +static const LLInventoryType::EType +DEFAULT_ASSET_FOR_INV_TYPE[LLAssetType::AT_COUNT] = +{ + LLInventoryType::IT_TEXTURE, // AT_TEXTURE + LLInventoryType::IT_SOUND, // AT_SOUND + LLInventoryType::IT_CALLINGCARD, // AT_CALLINGCARD + LLInventoryType::IT_LANDMARK, // AT_LANDMARK + LLInventoryType::IT_LSL, // AT_SCRIPT + LLInventoryType::IT_WEARABLE, // AT_CLOTHING + LLInventoryType::IT_OBJECT, // AT_OBJECT + LLInventoryType::IT_NOTECARD, // AT_NOTECARD + LLInventoryType::IT_CATEGORY, // AT_CATEGORY + LLInventoryType::IT_ROOT_CATEGORY, // AT_ROOT_CATEGORY + LLInventoryType::IT_LSL, // AT_LSL_TEXT + LLInventoryType::IT_LSL, // AT_LSL_BYTECODE + LLInventoryType::IT_TEXTURE, // AT_TEXTURE_TGA + LLInventoryType::IT_WEARABLE, // AT_BODYPART + LLInventoryType::IT_CATEGORY, // AT_TRASH + LLInventoryType::IT_CATEGORY, // AT_SNAPSHOT_CATEGORY + LLInventoryType::IT_CATEGORY, // AT_LOST_AND_FOUND + LLInventoryType::IT_SOUND, // AT_SOUND_WAV + LLInventoryType::IT_NONE, // AT_IMAGE_TGA + LLInventoryType::IT_NONE, // AT_IMAGE_JPEG + LLInventoryType::IT_ANIMATION, // AT_ANIMATION + LLInventoryType::IT_GESTURE, // AT_GESTURE +}; + +static const int MAX_POSSIBLE_ASSET_TYPES = 2; +static const LLAssetType::EType +INVENTORY_TO_ASSET_TYPE[LLInventoryType::IT_COUNT][MAX_POSSIBLE_ASSET_TYPES] = +{ + { LLAssetType::AT_TEXTURE, LLAssetType::AT_NONE }, // IT_TEXTURE + { LLAssetType::AT_SOUND, LLAssetType::AT_NONE }, // IT_SOUND + { LLAssetType::AT_CALLINGCARD, LLAssetType::AT_NONE }, // IT_CALLINGCARD + { LLAssetType::AT_LANDMARK, LLAssetType::AT_NONE }, // IT_LANDMARK + { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, + { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, + { LLAssetType::AT_OBJECT, LLAssetType::AT_NONE }, // IT_OBJECT + { LLAssetType::AT_NOTECARD, LLAssetType::AT_NONE }, // IT_NOTECARD + { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, // IT_CATEGORY + { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, // IT_ROOT_CATEGORY + { LLAssetType::AT_LSL_TEXT, LLAssetType::AT_LSL_BYTECODE }, // IT_LSL + { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, + { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, + { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, + { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, + { LLAssetType::AT_TEXTURE, LLAssetType::AT_NONE }, // IT_SNAPSHOT + { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, + { LLAssetType::AT_OBJECT, LLAssetType::AT_NONE }, // IT_ATTACHMENT + { LLAssetType::AT_CLOTHING, LLAssetType::AT_BODYPART }, // IT_WEARABLE + { LLAssetType::AT_ANIMATION, LLAssetType::AT_NONE }, // IT_ANIMATION + { LLAssetType::AT_GESTURE, LLAssetType::AT_NONE }, // IT_GESTURE +}; + +// static +const char* LLInventoryType::lookup(EType type) +{ + if((type >= 0) && (type < IT_COUNT)) + { + return INVENTORY_TYPE_NAMES[S32(type)]; + } + else + { + return NULL; + } +} + +// static +LLInventoryType::EType LLInventoryType::lookup(const char* name) +{ + for(S32 i = 0; i < IT_COUNT; ++i) + { + if((INVENTORY_TYPE_NAMES[i]) + && (0 == strcmp(name, INVENTORY_TYPE_NAMES[i]))) + { + // match + return (EType)i; + } + } + return IT_NONE; +} + +// XUI:translate +// translation from a type to a human readable form. +// static +const char* LLInventoryType::lookupHumanReadable(EType type) +{ + if((type >= 0) && (type < IT_COUNT)) + { + return INVENTORY_TYPE_HUMAN_NAMES[S32(type)]; + } + else + { + return NULL; + } +} + +// return the default inventory for the given asset type. +// static +LLInventoryType::EType LLInventoryType::defaultForAssetType(LLAssetType::EType asset_type) +{ + if((asset_type >= 0) && (asset_type < LLAssetType::AT_COUNT)) + { + return DEFAULT_ASSET_FOR_INV_TYPE[S32(asset_type)]; + } + else + { + return IT_NONE; + } +} + +///---------------------------------------------------------------------------- +/// Class LLInventoryObject +///---------------------------------------------------------------------------- + +LLInventoryObject::LLInventoryObject( + const LLUUID& uuid, + const LLUUID& parent_uuid, + LLAssetType::EType type, + const LLString& name) : + mUUID(uuid), + mParentUUID(parent_uuid), + mType(type), + mName(name) +{ + LLString::replaceNonstandardASCII(mName, ' '); + LLString::replaceChar(mName, '|', ' '); + LLString::trim(mName); + LLString::truncate(mName, DB_INV_ITEM_NAME_STR_LEN); +} + +LLInventoryObject::LLInventoryObject() : + mType(LLAssetType::AT_NONE) +{ +} + +LLInventoryObject::~LLInventoryObject( void ) +{ +} + +void LLInventoryObject::copy(const LLInventoryObject* other) +{ + mUUID = other->mUUID; + mParentUUID = other->mParentUUID; + mType = other->mType; + mName = other->mName; +} + +const LLUUID& LLInventoryObject::getUUID() const +{ + return mUUID; +} + +const LLUUID& LLInventoryObject::getParentUUID() const +{ + return mParentUUID; +} + +const LLString& LLInventoryObject::getName() const +{ + return mName; +} + +LLAssetType::EType LLInventoryObject::getType() const +{ + return mType; +} + +void LLInventoryObject::setUUID(const LLUUID& new_uuid) +{ + mUUID = new_uuid; +} + +void LLInventoryObject::rename(const LLString& n) +{ + LLString new_name(n); + LLString::replaceNonstandardASCII(new_name, ' '); + LLString::replaceChar(new_name, '|', ' '); + LLString::trim(new_name); + LLString::truncate(new_name, DB_INV_ITEM_NAME_STR_LEN); + + if( new_name != mName ) + { + mName = new_name; + } +} + +void LLInventoryObject::setParent(const LLUUID& new_parent) +{ + mParentUUID = new_parent; +} + +void LLInventoryObject::setType(LLAssetType::EType type) +{ + mType = type; +} + + +// virtual +BOOL LLInventoryObject::importLegacyStream(std::istream& input_stream) +{ + char buffer[MAX_STRING]; + char keyword[MAX_STRING]; + char valuestr[MAX_STRING]; + + keyword[0] = '\0'; + valuestr[0] = '\0'; + while(input_stream.good()) + { + input_stream.getline(buffer, MAX_STRING); + sscanf(buffer, " %254s %254s", keyword, valuestr); + if(!keyword) + { + continue; + } + if(0 == strcmp("{",keyword)) + { + continue; + } + if(0 == strcmp("}", keyword)) + { + break; + } + else if(0 == strcmp("obj_id", keyword)) + { + mUUID.set(valuestr); + } + else if(0 == strcmp("parent_id", keyword)) + { + mParentUUID.set(valuestr); + } + else if(0 == strcmp("type", keyword)) + { + mType = LLAssetType::lookup(valuestr); + } + else if(0 == strcmp("name", keyword)) + { + //strcpy(valuestr, buffer + strlen(keyword) + 3); + // *NOTE: Not ANSI C, but widely supported. + sscanf(buffer, " %254s %[^|]", keyword, valuestr); + mName.assign(valuestr); + LLString::replaceNonstandardASCII(mName, ' '); + LLString::replaceChar(mName, '|', ' '); + LLString::trim(mName); + LLString::truncate(mName, DB_INV_ITEM_NAME_STR_LEN); + } + else + { + llwarns << "unknown keyword '" << keyword + << "' in LLInventoryObject::importLegacyStream() for object " << mUUID << llendl; + } + } + return TRUE; +} + +// exportFile should be replaced with exportLegacyStream +// not sure whether exportLegacyStream(llofstream(fp)) would work, fp may need to get icramented... +BOOL LLInventoryObject::exportFile(FILE* fp, BOOL) const +{ + char uuid_str[UUID_STR_LENGTH]; + fprintf(fp, "\tinv_object\t0\n\t{\n"); + mUUID.toString(uuid_str); + fprintf(fp, "\t\tobj_id\t%s\n", uuid_str); + mParentUUID.toString(uuid_str); + fprintf(fp, "\t\tparent_id\t%s\n", uuid_str); + fprintf(fp, "\t\ttype\t%s\n", LLAssetType::lookup(mType)); + fprintf(fp, "\t\tname\t%s|\n", mName.c_str()); + fprintf(fp,"\t}\n"); + return TRUE; +} + +BOOL LLInventoryObject::exportLegacyStream(std::ostream& output_stream, BOOL) const +{ + char uuid_str[UUID_STR_LENGTH]; + output_stream << "\tinv_object\t0\n\t{\n"; + mUUID.toString(uuid_str); + output_stream << "\t\tobj_id\t" << uuid_str << "\n"; + mParentUUID.toString(uuid_str); + output_stream << "\t\tparent_id\t" << uuid_str << "\n"; + output_stream << "\t\ttype\t" << LLAssetType::lookup(mType) << "\n"; + output_stream << "\t\tname\t" << mName.c_str() << "|\n"; + output_stream << "\t}\n"; + return TRUE; +} + + +void LLInventoryObject::removeFromServer() +{ + // don't do nothin' + llwarns << "LLInventoryObject::removeFromServer() called. Doesn't do anything." << llendl; +} + +void LLInventoryObject::updateParentOnServer(BOOL) const +{ + // don't do nothin' + llwarns << "LLInventoryObject::updateParentOnServer() called. Doesn't do anything." << llendl; +} + +void LLInventoryObject::updateServer(BOOL) const +{ + // don't do nothin' + llwarns << "LLInventoryObject::updateServer() called. Doesn't do anything." << llendl; +} + + +///---------------------------------------------------------------------------- +/// Class LLInventoryItem +///---------------------------------------------------------------------------- + +LLInventoryItem::LLInventoryItem( + const LLUUID& uuid, + const LLUUID& parent_uuid, + const LLPermissions& permissions, + const LLUUID& asset_uuid, + LLAssetType::EType type, + LLInventoryType::EType inv_type, + const LLString& name, + const LLString& desc, + const LLSaleInfo& sale_info, + U32 flags, + S32 creation_date_utc) : + LLInventoryObject(uuid, parent_uuid, type, name), + mPermissions(permissions), + mAssetUUID(asset_uuid), + mDescription(desc), + mSaleInfo(sale_info), + mInventoryType(inv_type), + mFlags(flags), + mCreationDate(creation_date_utc) +{ + LLString::replaceNonstandardASCII(mDescription, ' '); + LLString::replaceChar(mDescription, '|', ' '); +} + +LLInventoryItem::LLInventoryItem() : + LLInventoryObject(), + mPermissions(), + mAssetUUID(), + mDescription(), + mSaleInfo(), + mInventoryType(LLInventoryType::IT_NONE), + mFlags(0), + mCreationDate(0) +{ +} + +LLInventoryItem::LLInventoryItem(const LLInventoryItem* other) : + LLInventoryObject() +{ + copy(other); +} + +LLInventoryItem::~LLInventoryItem() +{ +} + +// virtual +void LLInventoryItem::copy(const LLInventoryItem* other) +{ + LLInventoryObject::copy(other); + mPermissions = other->mPermissions; + mAssetUUID = other->mAssetUUID; + mDescription = other->mDescription; + mSaleInfo = other->mSaleInfo; + mInventoryType = other->mInventoryType; + mFlags = other->mFlags; + mCreationDate = other->mCreationDate; +} + +// As a constructor alternative, the clone() method works like a +// copy constructor, but gens a new UUID. +void LLInventoryItem::clone(LLPointer& newitem) const +{ + newitem = new LLInventoryItem; + newitem->copy(this); + newitem->mUUID.generate(); +} + +const LLPermissions& LLInventoryItem::getPermissions() const +{ + return mPermissions; +} + +const LLUUID& LLInventoryItem::getCreatorUUID() const +{ + return mPermissions.getCreator(); +} + +const LLUUID& LLInventoryItem::getAssetUUID() const +{ + return mAssetUUID; +} + +void LLInventoryItem::setAssetUUID(const LLUUID& asset_id) +{ + mAssetUUID = asset_id; +} + + +const LLString& LLInventoryItem::getDescription() const +{ + return mDescription; +} + +S32 LLInventoryItem::getCreationDate() const +{ + return mCreationDate; +} + +U32 LLInventoryItem::getCRC32() const +{ + // *FIX: Not a real crc - more of a checksum. + // *NOTE: We currently do not validate the name or description, + // but if they change in transit, it's no big deal. + U32 crc = mUUID.getCRC32(); + //lldebugs << "1 crc: " << std::hex << crc << std::dec << llendl; + crc += mParentUUID.getCRC32(); + //lldebugs << "2 crc: " << std::hex << crc << std::dec << llendl; + crc += mPermissions.getCRC32(); + //lldebugs << "3 crc: " << std::hex << crc << std::dec << llendl; + crc += mAssetUUID.getCRC32(); + //lldebugs << "4 crc: " << std::hex << crc << std::dec << llendl; + crc += mType; + //lldebugs << "5 crc: " << std::hex << crc << std::dec << llendl; + crc += mInventoryType; + //lldebugs << "6 crc: " << std::hex << crc << std::dec << llendl; + crc += mFlags; + //lldebugs << "7 crc: " << std::hex << crc << std::dec << llendl; + crc += mSaleInfo.getCRC32(); + //lldebugs << "8 crc: " << std::hex << crc << std::dec << llendl; + crc += mCreationDate; + //lldebugs << "9 crc: " << std::hex << crc << std::dec << llendl; + return crc; +} + + +void LLInventoryItem::setDescription(const LLString& d) +{ + LLString new_desc(d); + LLString::replaceNonstandardASCII(new_desc, ' '); + LLString::replaceChar(new_desc, '|', ' '); + if( new_desc != mDescription ) + { + mDescription = new_desc; + } +} + +void LLInventoryItem::setPermissions(const LLPermissions& perm) +{ + mPermissions = perm; +} + +void LLInventoryItem::setInventoryType(LLInventoryType::EType inv_type) +{ + mInventoryType = inv_type; +} + +void LLInventoryItem::setFlags(U32 flags) +{ + mFlags = flags; +} + +void LLInventoryItem::setCreationDate(S32 creation_date_utc) +{ + mCreationDate = creation_date_utc; +} + + +const LLSaleInfo& LLInventoryItem::getSaleInfo() const +{ + return mSaleInfo; +} + +void LLInventoryItem::setSaleInfo(const LLSaleInfo& sale_info) +{ + mSaleInfo = sale_info; +} + +LLInventoryType::EType LLInventoryItem::getInventoryType() const +{ + return mInventoryType; +} + +U32 LLInventoryItem::getFlags() const +{ + return mFlags; +} + +// virtual +void LLInventoryItem::packMessage(LLMessageSystem* msg) const +{ + msg->addUUIDFast(_PREHASH_ItemID, mUUID); + msg->addUUIDFast(_PREHASH_FolderID, mParentUUID); + mPermissions.packMessage(msg); + msg->addUUIDFast(_PREHASH_AssetID, mAssetUUID); + S8 type = static_cast(mType); + msg->addS8Fast(_PREHASH_Type, type); + type = static_cast(mInventoryType); + msg->addS8Fast(_PREHASH_InvType, type); + msg->addU32Fast(_PREHASH_Flags, mFlags); + mSaleInfo.packMessage(msg); + msg->addStringFast(_PREHASH_Name, mName); + msg->addStringFast(_PREHASH_Description, mDescription); + msg->addS32Fast(_PREHASH_CreationDate, mCreationDate); + U32 crc = getCRC32(); + msg->addU32Fast(_PREHASH_CRC, crc); +} + +// virtual +BOOL LLInventoryItem::unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num) +{ + msg->getUUIDFast(block, _PREHASH_ItemID, mUUID, block_num); + msg->getUUIDFast(block, _PREHASH_FolderID, mParentUUID, block_num); + mPermissions.unpackMessage(msg, block, block_num); + msg->getUUIDFast(block, _PREHASH_AssetID, mAssetUUID, block_num); + + S8 type; + msg->getS8Fast(block, _PREHASH_Type, type, block_num); + mType = static_cast(type); + msg->getS8(block, "InvType", type, block_num); + mInventoryType = static_cast(type); + + msg->getU32Fast(block, _PREHASH_Flags, mFlags, block_num); + + mSaleInfo.unpackMultiMessage(msg, block, block_num); + + char name[DB_INV_ITEM_NAME_BUF_SIZE]; + msg->getStringFast(block, _PREHASH_Name, DB_INV_ITEM_NAME_BUF_SIZE, name, block_num); + mName.assign(name); + LLString::replaceNonstandardASCII(mName, ' '); + + char desc[DB_INV_ITEM_DESC_BUF_SIZE]; + msg->getStringFast(block, _PREHASH_Description, DB_INV_ITEM_DESC_BUF_SIZE, desc, block_num); + mDescription.assign(desc); + LLString::replaceNonstandardASCII(mDescription, ' '); + + msg->getS32(block, "CreationDate", mCreationDate, block_num); + + U32 local_crc = getCRC32(); + U32 remote_crc = 0; + msg->getU32(block, "CRC", remote_crc, block_num); +//#define CRC_CHECK +#ifdef CRC_CHECK + if(local_crc == remote_crc) + { + lldebugs << "crc matches" << llendl; + return TRUE; + } + else + { + llwarns << "inventory crc mismatch: local=" << std::hex << local_crc + << " remote=" << remote_crc << std::dec << llendl; + return FALSE; + } +#else + return (local_crc == remote_crc); +#endif +} + +// virtual +BOOL LLInventoryItem::importFile(FILE* fp) +{ + char buffer[MAX_STRING]; + char keyword[MAX_STRING]; + char valuestr[MAX_STRING]; + char junk[MAX_STRING]; + BOOL success = TRUE; + + keyword[0] = '\0'; + valuestr[0] = '\0'; + + mInventoryType = LLInventoryType::IT_NONE; + mAssetUUID.setNull(); + while(success && (!feof(fp))) + { + fgets(buffer, MAX_STRING, fp); + sscanf(buffer, " %254s %254s", keyword, valuestr); + if(!keyword) + { + continue; + } + if(0 == strcmp("{",keyword)) + { + continue; + } + if(0 == strcmp("}", keyword)) + { + break; + } + else if(0 == strcmp("item_id", keyword)) + { + mUUID.set(valuestr); + } + else if(0 == strcmp("parent_id", keyword)) + { + mParentUUID.set(valuestr); + } + else if(0 == strcmp("permissions", keyword)) + { + success = mPermissions.importFile(fp); + } + else if(0 == strcmp("sale_info", keyword)) + { + // Sale info used to contain next owner perm. It is now in + // the permissions. Thus, we read that out, and fix legacy + // objects. It's possible this op would fail, but it + // should pick up the vast majority of the tasks. + BOOL has_perm_mask = FALSE; + U32 perm_mask = 0; + success = mSaleInfo.importFile(fp, has_perm_mask, perm_mask); + if(has_perm_mask) + { + if(perm_mask == PERM_NONE) + { + perm_mask = mPermissions.getMaskOwner(); + } + // fair use fix. + if(!(perm_mask & PERM_COPY)) + { + perm_mask |= PERM_TRANSFER; + } + mPermissions.setMaskNext(perm_mask); + } + } + else if(0 == strcmp("shadow_id", keyword)) + { + mAssetUUID.set(valuestr); + LLXORCipher cipher(MAGIC_ID.mData, UUID_BYTES); + cipher.decrypt(mAssetUUID.mData, UUID_BYTES); + } + else if(0 == strcmp("asset_id", keyword)) + { + mAssetUUID.set(valuestr); + } + else if(0 == strcmp("type", keyword)) + { + mType = LLAssetType::lookup(valuestr); + } + else if(0 == strcmp("inv_type", keyword)) + { + mInventoryType = LLInventoryType::lookup(valuestr); + } + else if(0 == strcmp("flags", keyword)) + { + sscanf(valuestr, "%x", &mFlags); + } + else if(0 == strcmp("name", keyword)) + { + //strcpy(valuestr, buffer + strlen(keyword) + 3); + // *NOTE: Not ANSI C, but widely supported. + sscanf(buffer, " %254s%[\t]%[^|]", keyword, junk, valuestr); + + // IW: sscanf chokes and puts | in valuestr if there's no name + if (valuestr[0] == '|') + { + valuestr[0] = '\000'; + } + + mName.assign(valuestr); + LLString::replaceNonstandardASCII(mName, ' '); + LLString::replaceChar(mName, '|', ' '); + } + else if(0 == strcmp("desc", keyword)) + { + //strcpy(valuestr, buffer + strlen(keyword) + 3); + // *NOTE: Not ANSI C, but widely supported. + sscanf(buffer, " %s%[\t]%[^|]", keyword, junk, valuestr); + + if (valuestr[0] == '|') + { + valuestr[0] = '\000'; + } + + mDescription.assign(valuestr); + LLString::replaceNonstandardASCII(mDescription, ' '); + /* TODO -- ask Ian about this code + const char *donkey = mDescription.c_str(); + if (donkey[0] == '|') + { + llerrs << "Donkey" << llendl; + } + */ + } + else if(0 == strcmp("creation_date", keyword)) + { + sscanf(valuestr, "%d", &mCreationDate); + } + else + { + llwarns << "unknown keyword '" << keyword + << "' in inventory import of item " << mUUID << llendl; + } + } + + // Need to convert 1.0 simstate files to a useful inventory type + // and potentially deal with bad inventory tyes eg, a landmark + // marked as a texture. + if((LLInventoryType::IT_NONE == mInventoryType) + || !inventory_and_asset_types_match(mInventoryType, mType)) + { + lldebugs << "Resetting inventory type for " << mUUID << llendl; + mInventoryType = LLInventoryType::defaultForAssetType(mType); + } + return success; +} + +BOOL LLInventoryItem::exportFile(FILE* fp, BOOL include_asset_key) const +{ + char uuid_str[UUID_STR_LENGTH]; + fprintf(fp, "\tinv_item\t0\n\t{\n"); + mUUID.toString(uuid_str); + fprintf(fp, "\t\titem_id\t%s\n", uuid_str); + mParentUUID.toString(uuid_str); + fprintf(fp, "\t\tparent_id\t%s\n", uuid_str); + mPermissions.exportFile(fp); + + // Check for permissions to see the asset id, and if so write it + // out as an asset id. Otherwise, apply our cheesy encryption. + if(include_asset_key) + { + U32 mask = mPermissions.getMaskBase(); + if(((mask & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED) + || (mAssetUUID.isNull())) + { + mAssetUUID.toString(uuid_str); + fprintf(fp, "\t\tasset_id\t%s\n", uuid_str); + } + else + { + LLUUID shadow_id(mAssetUUID); + LLXORCipher cipher(MAGIC_ID.mData, UUID_BYTES); + cipher.encrypt(shadow_id.mData, UUID_BYTES); + shadow_id.toString(uuid_str); + fprintf(fp, "\t\tshadow_id\t%s\n", uuid_str); + } + } + else + { + LLUUID::null.toString(uuid_str); + fprintf(fp, "\t\tasset_id\t%s\n", uuid_str); + } + fprintf(fp, "\t\ttype\t%s\n", LLAssetType::lookup(mType)); + const char* inv_type_str = LLInventoryType::lookup(mInventoryType); + if(inv_type_str) fprintf(fp, "\t\tinv_type\t%s\n", inv_type_str); + fprintf(fp, "\t\tflags\t%08x\n", mFlags); + mSaleInfo.exportFile(fp); + fprintf(fp, "\t\tname\t%s|\n", mName.c_str()); + fprintf(fp, "\t\tdesc\t%s|\n", mDescription.c_str()); + fprintf(fp, "\t\tcreation_date\t%d\n", mCreationDate); + fprintf(fp,"\t}\n"); + return TRUE; +} + +// virtual +BOOL LLInventoryItem::importLegacyStream(std::istream& input_stream) +{ + char buffer[MAX_STRING]; + char keyword[MAX_STRING]; + char valuestr[MAX_STRING]; + char junk[MAX_STRING]; + BOOL success = TRUE; + + keyword[0] = '\0'; + valuestr[0] = '\0'; + + mInventoryType = LLInventoryType::IT_NONE; + mAssetUUID.setNull(); + while(success && input_stream.good()) + { + input_stream.getline(buffer, MAX_STRING); + sscanf(buffer, " %s %s", keyword, valuestr); + if(!keyword) + { + continue; + } + if(0 == strcmp("{",keyword)) + { + continue; + } + if(0 == strcmp("}", keyword)) + { + break; + } + else if(0 == strcmp("item_id", keyword)) + { + mUUID.set(valuestr); + } + else if(0 == strcmp("parent_id", keyword)) + { + mParentUUID.set(valuestr); + } + else if(0 == strcmp("permissions", keyword)) + { + success = mPermissions.importLegacyStream(input_stream); + } + else if(0 == strcmp("sale_info", keyword)) + { + // Sale info used to contain next owner perm. It is now in + // the permissions. Thus, we read that out, and fix legacy + // objects. It's possible this op would fail, but it + // should pick up the vast majority of the tasks. + BOOL has_perm_mask = FALSE; + U32 perm_mask = 0; + success = mSaleInfo.importLegacyStream(input_stream, has_perm_mask, perm_mask); + if(has_perm_mask) + { + if(perm_mask == PERM_NONE) + { + perm_mask = mPermissions.getMaskOwner(); + } + // fair use fix. + if(!(perm_mask & PERM_COPY)) + { + perm_mask |= PERM_TRANSFER; + } + mPermissions.setMaskNext(perm_mask); + } + } + else if(0 == strcmp("shadow_id", keyword)) + { + mAssetUUID.set(valuestr); + LLXORCipher cipher(MAGIC_ID.mData, UUID_BYTES); + cipher.decrypt(mAssetUUID.mData, UUID_BYTES); + } + else if(0 == strcmp("asset_id", keyword)) + { + mAssetUUID.set(valuestr); + } + else if(0 == strcmp("type", keyword)) + { + mType = LLAssetType::lookup(valuestr); + } + else if(0 == strcmp("inv_type", keyword)) + { + mInventoryType = LLInventoryType::lookup(valuestr); + } + else if(0 == strcmp("flags", keyword)) + { + sscanf(valuestr, "%x", &mFlags); + } + else if(0 == strcmp("name", keyword)) + { + //strcpy(valuestr, buffer + strlen(keyword) + 3); + // *NOTE: Not ANSI C, but widely supported. + sscanf(buffer, " %s%[\t]%[^|]", keyword, junk, valuestr); + + // IW: sscanf chokes and puts | in valuestr if there's no name + if (valuestr[0] == '|') + { + valuestr[0] = '\000'; + } + + mName.assign(valuestr); + LLString::replaceNonstandardASCII(mName, ' '); + LLString::replaceChar(mName, '|', ' '); + } + else if(0 == strcmp("desc", keyword)) + { + //strcpy(valuestr, buffer + strlen(keyword) + 3); + // *NOTE: Not ANSI C, but widely supported. + sscanf(buffer, " %s%[\t]%[^|]", keyword, junk, valuestr); + + if (valuestr[0] == '|') + { + valuestr[0] = '\000'; + } + + mDescription.assign(valuestr); + LLString::replaceNonstandardASCII(mDescription, ' '); + /* TODO -- ask Ian about this code + const char *donkey = mDescription.c_str(); + if (donkey[0] == '|') + { + llerrs << "Donkey" << llendl; + } + */ + } + else if(0 == strcmp("creation_date", keyword)) + { + sscanf(valuestr, "%d", &mCreationDate); + } + else + { + llwarns << "unknown keyword '" << keyword + << "' in inventory import of item " << mUUID << llendl; + } + } + + // Need to convert 1.0 simstate files to a useful inventory type + // and potentially deal with bad inventory tyes eg, a landmark + // marked as a texture. + if((LLInventoryType::IT_NONE == mInventoryType) + || !inventory_and_asset_types_match(mInventoryType, mType)) + { + lldebugs << "Resetting inventory type for " << mUUID << llendl; + mInventoryType = LLInventoryType::defaultForAssetType(mType); + } + return success; +} + +BOOL LLInventoryItem::exportLegacyStream(std::ostream& output_stream, BOOL include_asset_key) const +{ + char uuid_str[UUID_STR_LENGTH]; + output_stream << "\tinv_item\t0\n\t{\n"; + mUUID.toString(uuid_str); + output_stream << "\t\titem_id\t" << uuid_str << "\n"; + mParentUUID.toString(uuid_str); + output_stream << "\t\tparent_id\t" << uuid_str << "\n"; + mPermissions.exportLegacyStream(output_stream); + + // Check for permissions to see the asset id, and if so write it + // out as an asset id. Otherwise, apply our cheesy encryption. + if(include_asset_key) + { + U32 mask = mPermissions.getMaskBase(); + if(((mask & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED) + || (mAssetUUID.isNull())) + { + mAssetUUID.toString(uuid_str); + output_stream << "\t\tasset_id\t" << uuid_str << "\n"; + } + else + { + LLUUID shadow_id(mAssetUUID); + LLXORCipher cipher(MAGIC_ID.mData, UUID_BYTES); + cipher.encrypt(shadow_id.mData, UUID_BYTES); + shadow_id.toString(uuid_str); + output_stream << "\t\tshadow_id\t" << uuid_str << "\n"; + } + } + else + { + LLUUID::null.toString(uuid_str); + output_stream << "\t\tasset_id\t" << uuid_str << "\n"; + } + output_stream << "\t\ttype\t" << LLAssetType::lookup(mType) << "\n"; + const char* inv_type_str = LLInventoryType::lookup(mInventoryType); + if(inv_type_str) + output_stream << "\t\tinv_type\t" << inv_type_str << "\n"; + char buffer[32]; + sprintf(buffer, "\t\tflags\t%08x\n", mFlags); + output_stream << buffer; + mSaleInfo.exportLegacyStream(output_stream); + output_stream << "\t\tname\t" << mName.c_str() << "|\n"; + output_stream << "\t\tdesc\t" << mDescription.c_str() << "|\n"; + output_stream << "\t\tcreation_date\t" << mCreationDate << "\n"; + output_stream << "\t}\n"; + return TRUE; +} + +LLSD LLInventoryItem::asLLSD() const +{ + LLSD sd = LLSD(); + sd["item_id"] = mUUID; + sd["parent_id"] = mParentUUID; + sd["permissions"] = ll_create_sd_from_permissions(mPermissions); + + U32 mask = mPermissions.getMaskBase(); + if(((mask & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED) + || (mAssetUUID.isNull())) + { + sd["asset_id"] = mAssetUUID; + } + else + { + LLUUID shadow_id(mAssetUUID); + LLXORCipher cipher(MAGIC_ID.mData, UUID_BYTES); + cipher.encrypt(shadow_id.mData, UUID_BYTES); + sd["shadow_id"] = shadow_id; + } + sd["type"] = LLAssetType::lookup(mType); + const char* inv_type_str = LLInventoryType::lookup(mInventoryType); + if(inv_type_str) + { + sd["inv_type"] = inv_type_str; + } + sd["flags"] = ll_sd_from_U32(mFlags); + sd["sale_info"] = mSaleInfo; + sd["name"] = mName; + sd["desc"] = mDescription; + sd["creation_date"] = mCreationDate; + + return sd; +} + +bool LLInventoryItem::fromLLSD(LLSD& sd) +{ + mInventoryType = LLInventoryType::IT_NONE; + mAssetUUID.setNull(); + const char *w; + + w = "item_id"; + if (sd.has(w)) + { + mUUID = sd[w]; + } + w = "parent_id"; + if (sd.has(w)) + { + mParentUUID = sd[w]; + } + w = "permissions"; + if (sd.has(w)) + { + mPermissions = ll_permissions_from_sd(sd[w]); + } + w = "sale_info"; + if (sd.has(w)) + { + // Sale info used to contain next owner perm. It is now in + // the permissions. Thus, we read that out, and fix legacy + // objects. It's possible this op would fail, but it + // should pick up the vast majority of the tasks. + BOOL has_perm_mask = FALSE; + U32 perm_mask = 0; + if (!mSaleInfo.fromLLSD(sd[w], has_perm_mask, perm_mask)) + { + goto fail; + } + if (has_perm_mask) + { + if(perm_mask == PERM_NONE) + { + perm_mask = mPermissions.getMaskOwner(); + } + // fair use fix. + if(!(perm_mask & PERM_COPY)) + { + perm_mask |= PERM_TRANSFER; + } + mPermissions.setMaskNext(perm_mask); + } + } + w = "shadow_id"; + if (sd.has(w)) + { + mAssetUUID = sd[w]; + LLXORCipher cipher(MAGIC_ID.mData, UUID_BYTES); + cipher.decrypt(mAssetUUID.mData, UUID_BYTES); + } + w = "asset_id"; + if (sd.has(w)) + { + mAssetUUID = sd[w]; + } + w = "type"; + if (sd.has(w)) + { + mType = LLAssetType::lookup(sd[w].asString().c_str()); + } + w = "inv_type"; + if (sd.has(w)) + { + mInventoryType = LLInventoryType::lookup(sd[w].asString().c_str()); + } + w = "flags"; + if (sd.has(w)) + { + mFlags = ll_U32_from_sd(sd[w]); + } + w = "name"; + if (sd.has(w)) + { + mName = sd[w].asString(); + LLString::replaceNonstandardASCII(mName, ' '); + LLString::replaceChar(mName, '|', ' '); + } + w = "desc"; + if (sd.has(w)) + { + mDescription = sd[w].asString(); + LLString::replaceNonstandardASCII(mDescription, ' '); + } + w = "creation_date"; + if (sd.has(w)) + { + mCreationDate = sd[w]; + } + + // Need to convert 1.0 simstate files to a useful inventory type + // and potentially deal with bad inventory tyes eg, a landmark + // marked as a texture. + if((LLInventoryType::IT_NONE == mInventoryType) + || !inventory_and_asset_types_match(mInventoryType, mType)) + { + lldebugs << "Resetting inventory type for " << mUUID << llendl; + mInventoryType = LLInventoryType::defaultForAssetType(mType); + } + + return true; +fail: + return false; + +} + +LLXMLNode *LLInventoryItem::exportFileXML(BOOL include_asset_key) const +{ + LLMemType m1(LLMemType::MTYPE_INVENTORY); + LLXMLNode *ret = new LLXMLNode("item", FALSE); + + ret->createChild("uuid", TRUE)->setUUIDValue(1, &mUUID); + ret->createChild("parent_uuid", TRUE)->setUUIDValue(1, &mParentUUID); + + mPermissions.exportFileXML()->setParent(ret); + + // Check for permissions to see the asset id, and if so write it + // out as an asset id. Otherwise, apply our cheesy encryption. + if(include_asset_key) + { + U32 mask = mPermissions.getMaskBase(); + if(((mask & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED) + || (mAssetUUID.isNull())) + { + ret->createChild("asset_id", FALSE)->setUUIDValue(1, &mAssetUUID); + } + else + { + LLUUID shadow_id(mAssetUUID); + LLXORCipher cipher(MAGIC_ID.mData, UUID_BYTES); + cipher.encrypt(shadow_id.mData, UUID_BYTES); + + ret->createChild("shadow_id", FALSE)->setUUIDValue(1, &shadow_id); + } + } + + LLString type_str = LLAssetType::lookup(mType); + LLString inv_type_str = LLInventoryType::lookup(mInventoryType); + + ret->createChild("asset_type", FALSE)->setStringValue(1, &type_str); + ret->createChild("inventory_type", FALSE)->setStringValue(1, &inv_type_str); + S32 tmp_flags = (S32) mFlags; + ret->createChild("flags", FALSE)->setByteValue(4, (U8*)(&tmp_flags), LLXMLNode::ENCODING_HEX); + + mSaleInfo.exportFileXML()->setParent(ret); + + LLString temp; + temp.assign(mName); + ret->createChild("name", FALSE)->setStringValue(1, &temp); + temp.assign(mDescription); + ret->createChild("description", FALSE)->setStringValue(1, &temp); + ret->createChild("creation_date", FALSE)->setIntValue(1, &mCreationDate); + + return ret; +} + +BOOL LLInventoryItem::importXML(LLXMLNode* node) +{ + BOOL success = FALSE; + if (node) + { + success = TRUE; + LLXMLNodePtr sub_node; + if (node->getChild("uuid", sub_node)) + success = (1 == sub_node->getUUIDValue(1, &mUUID)); + if (node->getChild("parent_uuid", sub_node)) + success = success && (1 == sub_node->getUUIDValue(1, &mParentUUID)); + if (node->getChild("permissions", sub_node)) + success = success && mPermissions.importXML(sub_node); + if (node->getChild("asset_id", sub_node)) + success = success && (1 == sub_node->getUUIDValue(1, &mAssetUUID)); + if (node->getChild("shadow_id", sub_node)) + { + success = success && (1 == sub_node->getUUIDValue(1, &mAssetUUID)); + LLXORCipher cipher(MAGIC_ID.mData, UUID_BYTES); + cipher.decrypt(mAssetUUID.mData, UUID_BYTES); + } + if (node->getChild("asset_type", sub_node)) + mType = LLAssetType::lookup(sub_node->getValue().c_str()); + if (node->getChild("inventory_type", sub_node)) + mInventoryType = LLInventoryType::lookup(sub_node->getValue().c_str()); + if (node->getChild("flags", sub_node)) + { + S32 tmp_flags = 0; + success = success && (1 == sub_node->getIntValue(1, &tmp_flags)); + mFlags = (U32) tmp_flags; + } + if (node->getChild("sale_info", sub_node)) + success = success && mSaleInfo.importXML(sub_node); + if (node->getChild("name", sub_node)) + mName = sub_node->getValue(); + if (node->getChild("description", sub_node)) + mDescription = sub_node->getValue(); + if (node->getChild("creation_date", sub_node)) + success = success && (1 == sub_node->getIntValue(1, &mCreationDate)); + if (!success) + { + lldebugs << "LLInventory::importXML() failed for node named '" + << node->getName() << "'" << llendl; + } + } + return success; +} + +S32 LLInventoryItem::packBinaryBucket(U8* bin_bucket, LLPermissions* perm_override) const +{ + // Figure out which permissions to use. + LLPermissions perm; + if (perm_override) + { + // Use the permissions override. + perm = *perm_override; + } + else + { + // Use the current permissions. + perm = getPermissions(); + } + + // describe the inventory item + char* buffer = (char*) bin_bucket; + char creator_id_str[UUID_STR_LENGTH]; + + perm.getCreator().toString(creator_id_str); + char owner_id_str[UUID_STR_LENGTH]; + perm.getOwner().toString(owner_id_str); + char last_owner_id_str[UUID_STR_LENGTH]; + perm.getLastOwner().toString(last_owner_id_str); + char group_id_str[UUID_STR_LENGTH]; + perm.getGroup().toString(group_id_str); + char asset_id_str[UUID_STR_LENGTH]; + getAssetUUID().toString(asset_id_str); + S32 size = sprintf(buffer, + "%d|%d|%s|%s|%s|%s|%s|%x|%x|%x|%x|%x|%s|%s|%d|%d|%x", + getType(), + getInventoryType(), + getName().c_str(), + creator_id_str, + owner_id_str, + last_owner_id_str, + group_id_str, + perm.getMaskBase(), + perm.getMaskOwner(), + perm.getMaskGroup(), + perm.getMaskEveryone(), + perm.getMaskNextOwner(), + asset_id_str, + getDescription().c_str(), + getSaleInfo().getSaleType(), + getSaleInfo().getSalePrice(), + getFlags()) + 1; + + return size; +} + +void LLInventoryItem::unpackBinaryBucket(U8* bin_bucket, S32 bin_bucket_size) +{ + // Early exit on an empty binary bucket. + if (bin_bucket_size <= 1) return; + + // Convert the bin_bucket into a string. + char* item_buffer = new char[bin_bucket_size+1]; + memcpy(item_buffer, bin_bucket, bin_bucket_size); + item_buffer[bin_bucket_size] = '\0'; + std::string str(item_buffer); + + lldebugs << "item buffer: " << item_buffer << llendl; + delete[] item_buffer; + + // Tokenize the string. + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("|", "", boost::keep_empty_tokens); + tokenizer tokens(str, sep); + tokenizer::iterator iter = tokens.begin(); + + // Extract all values. + LLUUID item_id; + item_id.generate(); + setUUID(item_id); + + LLAssetType::EType type; + type = (LLAssetType::EType)(atoi((*(iter++)).c_str())); + setType( type ); + + LLInventoryType::EType inv_type; + inv_type = (LLInventoryType::EType)(atoi((*(iter++)).c_str())); + setInventoryType( inv_type ); + + LLString name((*(iter++)).c_str()); + rename( name ); + + LLUUID creator_id((*(iter++)).c_str()); + LLUUID owner_id((*(iter++)).c_str()); + LLUUID last_owner_id((*(iter++)).c_str()); + LLUUID group_id((*(iter++)).c_str()); + PermissionMask mask_base = strtoul((*(iter++)).c_str(), NULL, 16); + PermissionMask mask_owner = strtoul((*(iter++)).c_str(), NULL, 16); + PermissionMask mask_group = strtoul((*(iter++)).c_str(), NULL, 16); + PermissionMask mask_every = strtoul((*(iter++)).c_str(), NULL, 16); + PermissionMask mask_next = strtoul((*(iter++)).c_str(), NULL, 16); + LLPermissions perm; + perm.init(creator_id, owner_id, last_owner_id, group_id); + perm.initMasks(mask_base, mask_owner, mask_group, mask_every, mask_next); + setPermissions(perm); + //lldebugs << "perm: " << perm << llendl; + + LLUUID asset_id((*(iter++)).c_str()); + setAssetUUID(asset_id); + + LLString desc((*(iter++)).c_str()); + setDescription(desc); + + LLSaleInfo::EForSale sale_type; + sale_type = (LLSaleInfo::EForSale)(atoi((*(iter++)).c_str())); + S32 price = atoi((*(iter++)).c_str()); + LLSaleInfo sale_info(sale_type, price); + setSaleInfo(sale_info); + + U32 flags = strtoul((*(iter++)).c_str(), NULL, 16); + setFlags(flags); + + time_t now = time(NULL); + setCreationDate(now); +} + +// returns TRUE if a should appear before b +BOOL item_dictionary_sort( LLInventoryItem* a, LLInventoryItem* b ) +{ + return (LLString::compareDict( a->getName().c_str(), b->getName().c_str() ) < 0); +} + +// returns TRUE if a should appear before b +BOOL item_date_sort( LLInventoryItem* a, LLInventoryItem* b ) +{ + return a->getCreationDate() < b->getCreationDate(); +} + + +///---------------------------------------------------------------------------- +/// Class LLInventoryCategory +///---------------------------------------------------------------------------- + +LLInventoryCategory::LLInventoryCategory( + const LLUUID& uuid, + const LLUUID& parent_uuid, + LLAssetType::EType preferred_type, + const LLString& name) : + LLInventoryObject(uuid, parent_uuid, LLAssetType::AT_CATEGORY, name), + mPreferredType(preferred_type) +{ +} + +LLInventoryCategory::LLInventoryCategory() : + mPreferredType(LLAssetType::AT_NONE) +{ + mType = LLAssetType::AT_CATEGORY; +} + +LLInventoryCategory::LLInventoryCategory(const LLInventoryCategory* other) : + LLInventoryObject() +{ + copy(other); +} + +LLInventoryCategory::~LLInventoryCategory() +{ +} + +// virtual +void LLInventoryCategory::copy(const LLInventoryCategory* other) +{ + LLInventoryObject::copy(other); + mPreferredType = other->mPreferredType; +} + +LLAssetType::EType LLInventoryCategory::getPreferredType() const +{ + return mPreferredType; +} + +void LLInventoryCategory::setPreferredType(LLAssetType::EType type) +{ + mPreferredType = type; +} + +// virtual +void LLInventoryCategory::packMessage(LLMessageSystem* msg) const +{ + msg->addUUIDFast(_PREHASH_FolderID, mUUID); + msg->addUUIDFast(_PREHASH_ParentID, mParentUUID); + S8 type = static_cast(mPreferredType); + msg->addS8Fast(_PREHASH_Type, type); + msg->addStringFast(_PREHASH_Name, mName); +} + +// virtual +void LLInventoryCategory::unpackMessage(LLMessageSystem* msg, + const char* block, + S32 block_num) +{ + msg->getUUIDFast(block, _PREHASH_FolderID, mUUID, block_num); + msg->getUUIDFast(block, _PREHASH_ParentID, mParentUUID, block_num); + S8 type; + msg->getS8Fast(block, _PREHASH_Type, type, block_num); + mPreferredType = static_cast(type); + char name[DB_INV_ITEM_NAME_BUF_SIZE]; + msg->getStringFast(block, _PREHASH_Name, DB_INV_ITEM_NAME_BUF_SIZE, name, block_num); + mName.assign(name); + LLString::replaceNonstandardASCII(mName, ' '); +} + +// virtual +BOOL LLInventoryCategory::importFile(FILE* fp) +{ + char buffer[MAX_STRING]; + char keyword[MAX_STRING]; + char valuestr[MAX_STRING]; + + keyword[0] = '\0'; + valuestr[0] = '\0'; + while(!feof(fp)) + { + fgets(buffer, MAX_STRING, fp); + sscanf(buffer, " %s %s", keyword, valuestr); + if(!keyword) + { + continue; + } + if(0 == strcmp("{",keyword)) + { + continue; + } + if(0 == strcmp("}", keyword)) + { + break; + } + else if(0 == strcmp("cat_id", keyword)) + { + mUUID.set(valuestr); + } + else if(0 == strcmp("parent_id", keyword)) + { + mParentUUID.set(valuestr); + } + else if(0 == strcmp("type", keyword)) + { + mType = LLAssetType::lookup(valuestr); + } + else if(0 == strcmp("pref_type", keyword)) + { + mPreferredType = LLAssetType::lookup(valuestr); + } + else if(0 == strcmp("name", keyword)) + { + //strcpy(valuestr, buffer + strlen(keyword) + 3); + // *NOTE: Not ANSI C, but widely supported. + sscanf(buffer, " %s %[^|]", keyword, valuestr); + mName.assign(valuestr); + LLString::replaceNonstandardASCII(mName, ' '); + LLString::replaceChar(mName, '|', ' '); + } + else + { + llwarns << "unknown keyword '" << keyword + << "' in inventory import category " << mUUID << llendl; + } + } + return TRUE; +} + +BOOL LLInventoryCategory::exportFile(FILE* fp, BOOL) const +{ + char uuid_str[UUID_STR_LENGTH]; + fprintf(fp, "\tinv_category\t0\n\t{\n"); + mUUID.toString(uuid_str); + fprintf(fp, "\t\tcat_id\t%s\n", uuid_str); + mParentUUID.toString(uuid_str); + fprintf(fp, "\t\tparent_id\t%s\n", uuid_str); + fprintf(fp, "\t\ttype\t%s\n", LLAssetType::lookup(mType)); + fprintf(fp, "\t\tpref_type\t%s\n", LLAssetType::lookup(mPreferredType)); + fprintf(fp, "\t\tname\t%s|\n", mName.c_str()); + fprintf(fp,"\t}\n"); + return TRUE; +} + + +// virtual +BOOL LLInventoryCategory::importLegacyStream(std::istream& input_stream) +{ + char buffer[MAX_STRING]; + char keyword[MAX_STRING]; + char valuestr[MAX_STRING]; + + keyword[0] = '\0'; + valuestr[0] = '\0'; + while(input_stream.good()) + { + input_stream.getline(buffer, MAX_STRING); + sscanf(buffer, " %s %s", keyword, valuestr); + if(!keyword) + { + continue; + } + if(0 == strcmp("{",keyword)) + { + continue; + } + if(0 == strcmp("}", keyword)) + { + break; + } + else if(0 == strcmp("cat_id", keyword)) + { + mUUID.set(valuestr); + } + else if(0 == strcmp("parent_id", keyword)) + { + mParentUUID.set(valuestr); + } + else if(0 == strcmp("type", keyword)) + { + mType = LLAssetType::lookup(valuestr); + } + else if(0 == strcmp("pref_type", keyword)) + { + mPreferredType = LLAssetType::lookup(valuestr); + } + else if(0 == strcmp("name", keyword)) + { + //strcpy(valuestr, buffer + strlen(keyword) + 3); + // *NOTE: Not ANSI C, but widely supported. + sscanf(buffer, " %s %[^|]", keyword, valuestr); + mName.assign(valuestr); + LLString::replaceNonstandardASCII(mName, ' '); + LLString::replaceChar(mName, '|', ' '); + } + else + { + llwarns << "unknown keyword '" << keyword + << "' in inventory import category " << mUUID << llendl; + } + } + return TRUE; +} + +BOOL LLInventoryCategory::exportLegacyStream(std::ostream& output_stream, BOOL) const +{ + char uuid_str[UUID_STR_LENGTH]; + output_stream << "\tinv_category\t0\n\t{\n"; + mUUID.toString(uuid_str); + output_stream << "\t\tcat_id\t" << uuid_str << "\n"; + mParentUUID.toString(uuid_str); + output_stream << "\t\tparent_id\t" << uuid_str << "\n"; + output_stream << "\t\ttype\t" << LLAssetType::lookup(mType) << "\n"; + output_stream << "\t\tpref_type\t" << LLAssetType::lookup(mPreferredType) << "\n"; + output_stream << "\t\tname\t" << mName.c_str() << "|\n"; + output_stream << "\t}\n"; + return TRUE; +} + +///---------------------------------------------------------------------------- +/// Local function definitions +///---------------------------------------------------------------------------- + +bool inventory_and_asset_types_match( + LLInventoryType::EType inventory_type, + LLAssetType::EType asset_type) +{ + bool rv = false; + if((inventory_type >= 0) && (inventory_type < LLInventoryType::IT_COUNT)) + { + for(S32 i = 0; i < MAX_POSSIBLE_ASSET_TYPES; ++i) + { + if(INVENTORY_TO_ASSET_TYPE[inventory_type][i] == asset_type) + { + rv = true; + break; + } + } + } + return rv; +} + +///---------------------------------------------------------------------------- +/// exported functions +///---------------------------------------------------------------------------- + +static const std::string INV_ITEM_ID_LABEL("item_id"); +static const std::string INV_FOLDER_ID_LABEL("folder_id"); +static const std::string INV_PARENT_ID_LABEL("parent_id"); +static const std::string INV_ASSET_TYPE_LABEL("asset_type"); +static const std::string INV_PREFERRED_TYPE_LABEL("preferred_type"); +static const std::string INV_INVENTORY_TYPE_LABEL("inv_type"); +static const std::string INV_NAME_LABEL("name"); +static const std::string INV_DESC_LABEL("description"); +static const std::string INV_PERMISSIONS_LABEL("permissions"); +static const std::string INV_ASSET_ID_LABEL("asset_id"); +static const std::string INV_SALE_INFO_LABEL("sale_info"); +static const std::string INV_FLAGS_LABEL("flags"); +static const std::string INV_CREATION_DATE_LABEL("created_at"); + +LLSD ll_create_sd_from_inventory_item(LLPointer item) +{ + LLSD rv; + if(item.isNull()) return rv; + if (item->getType() == LLAssetType::AT_NONE) + { + llwarns << "ll_create_sd_from_inventory_item() for item with AT_NONE" + << llendl; + return rv; + } + rv[INV_ITEM_ID_LABEL] = item->getUUID(); + rv[INV_PARENT_ID_LABEL] = item->getParentUUID(); + rv[INV_NAME_LABEL] = item->getName(); + rv[INV_ASSET_TYPE_LABEL] = LLAssetType::lookup(item->getType()); + rv[INV_ASSET_ID_LABEL] = item->getAssetUUID(); + rv[INV_DESC_LABEL] = item->getDescription(); + rv[INV_SALE_INFO_LABEL] = ll_create_sd_from_sale_info(item->getSaleInfo()); + rv[INV_PERMISSIONS_LABEL] = + ll_create_sd_from_permissions(item->getPermissions()); + rv[INV_INVENTORY_TYPE_LABEL] = + LLInventoryType::lookup(item->getInventoryType()); + rv[INV_FLAGS_LABEL] = (S32)item->getFlags(); + rv[INV_CREATION_DATE_LABEL] = item->getCreationDate(); + return rv; +} + +LLPointer ll_create_item_from_sd(const LLSD& sd_item) +{ + LLPointer rv = new LLInventoryItem; + rv->setUUID(sd_item[INV_ITEM_ID_LABEL].asUUID()); + rv->setParent(sd_item[INV_PARENT_ID_LABEL].asUUID()); + rv->rename(sd_item[INV_NAME_LABEL].asString()); + rv->setType( + LLAssetType::lookup(sd_item[INV_ASSET_TYPE_LABEL].asString().c_str())); + rv->setAssetUUID(sd_item[INV_ASSET_ID_LABEL].asUUID()); + rv->setDescription(sd_item[INV_DESC_LABEL].asString()); + rv->setSaleInfo(ll_sale_info_from_sd(sd_item[INV_SALE_INFO_LABEL])); + rv->setPermissions(ll_permissions_from_sd(sd_item[INV_PERMISSIONS_LABEL])); + rv->setInventoryType( + LLInventoryType::lookup( + sd_item[INV_INVENTORY_TYPE_LABEL].asString().c_str())); + rv->setFlags((U32)(sd_item[INV_FLAGS_LABEL].asInteger())); + rv->setCreationDate(sd_item[INV_CREATION_DATE_LABEL].asInteger()); + return rv; +} + +LLSD ll_create_sd_from_inventory_category(LLPointer cat) +{ + LLSD rv; + if(cat.isNull()) return rv; + if (cat->getType() == LLAssetType::AT_NONE) + { + llwarns << "ll_create_sd_from_inventory_category() for cat with AT_NONE" + << llendl; + return rv; + } + rv[INV_FOLDER_ID_LABEL] = cat->getUUID(); + rv[INV_PARENT_ID_LABEL] = cat->getParentUUID(); + rv[INV_NAME_LABEL] = cat->getName(); + rv[INV_ASSET_TYPE_LABEL] = LLAssetType::lookup(cat->getType()); + if(LLAssetType::AT_NONE != cat->getPreferredType()) + { + rv[INV_PREFERRED_TYPE_LABEL] = + LLAssetType::lookup(cat->getPreferredType()); + } + return rv; +} + +LLPointer ll_create_category_from_sd(const LLSD& sd_cat) +{ + LLPointer rv = new LLInventoryCategory; + rv->setUUID(sd_cat[INV_FOLDER_ID_LABEL].asUUID()); + rv->setParent(sd_cat[INV_PARENT_ID_LABEL].asUUID()); + rv->rename(sd_cat[INV_NAME_LABEL].asString()); + rv->setType( + LLAssetType::lookup(sd_cat[INV_ASSET_TYPE_LABEL].asString().c_str())); + rv->setPreferredType( + LLAssetType::lookup( + sd_cat[INV_PREFERRED_TYPE_LABEL].asString().c_str())); + return rv; +} diff --git a/indra/llinventory/llinventory.h b/indra/llinventory/llinventory.h new file mode 100644 index 0000000000..9f750c46b2 --- /dev/null +++ b/indra/llinventory/llinventory.h @@ -0,0 +1,411 @@ +/** + * @file llinventory.h + * @brief LLInventoryItem and LLInventoryCategory class declaration. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLINVENTORY_H +#define LL_LLINVENTORY_H + +#include + +#include "llassetstorage.h" +#include "lldarray.h" +#include "llpermissions.h" +#include "llsaleinfo.h" +#include "llsd.h" +#include "lluuid.h" +#include "llxmlnode.h" + +// consts for Key field in the task inventory update message +extern const U8 TASK_INVENTORY_ITEM_KEY; +extern const U8 TASK_INVENTORY_ASSET_KEY; + +// anonymous enumeration to specify a max inventory buffer size for +// use in packBinaryBucket() +enum +{ + MAX_INVENTORY_BUFFER_SIZE = 1024 +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryType +// +// Class used to encapsulate operations around inventory type. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLInventoryType +{ +public: + enum EType + { + IT_TEXTURE = 0, + IT_SOUND = 1, + IT_CALLINGCARD = 2, + IT_LANDMARK = 3, + //IT_SCRIPT = 4, + //IT_CLOTHING = 5, + IT_OBJECT = 6, + IT_NOTECARD = 7, + IT_CATEGORY = 8, + IT_ROOT_CATEGORY = 9, + IT_LSL = 10, + //IT_LSL_BYTECODE = 11, + //IT_TEXTURE_TGA = 12, + //IT_BODYPART = 13, + //IT_TRASH = 14, + IT_SNAPSHOT = 15, + //IT_LOST_AND_FOUND = 16, + IT_ATTACHMENT = 17, + IT_WEARABLE = 18, + IT_ANIMATION = 19, + IT_GESTURE = 20, + IT_COUNT = 21, + + IT_NONE = -1 + }; + + // machine transation between type and strings + static EType lookup(const char* name); + static const char* lookup(EType type); + + // translation from a type to a human readable form. + static const char* lookupHumanReadable(EType type); + + // return the default inventory for the given asset type. + static EType defaultForAssetType(LLAssetType::EType asset_type); + +private: + // don't instantiate or derive one of these objects + LLInventoryType( void ) {} + ~LLInventoryType( void ) {} + +//private: +// static const char* mInventoryTypeNames[]; +// static const char* mInventoryTypeHumanNames[]; +// static LLInventoryType::EType mInventoryAssetType[]; +}; + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryObject +// +// This is the base class for inventory objects that handles the +// common code between items and categories. The 'mParentUUID' member +// means the parent category since all inventory objects except each +// user's root category are in some category. Each user's root +// category will have mParentUUID==LLUUID::null. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMessageSystem; + +class LLInventoryObject : public LLRefCount +{ +protected: + LLUUID mUUID; + LLUUID mParentUUID; + LLAssetType::EType mType; + LLString mName; + +protected: + virtual ~LLInventoryObject( void ); + +public: + MEM_TYPE_NEW(LLMemType::MTYPE_INVENTORY); + LLInventoryObject(const LLUUID& uuid, const LLUUID& parent_uuid, + LLAssetType::EType type, const LLString& name); + LLInventoryObject(); + virtual void copy(const LLInventoryObject* other); // LLRefCount requires custom copy + + // accessors + const LLUUID& getUUID() const; + const LLUUID& getParentUUID() const; + const LLString& getName() const; + LLAssetType::EType getType() const; + + // mutators - will not call updateServer(); + void setUUID(const LLUUID& new_uuid); + void rename(const LLString& new_name); + void setParent(const LLUUID& new_parent); + void setType(LLAssetType::EType type); + + // file support - implemented here so that a minimal information + // set can be transmitted between simulator and viewer. +// virtual BOOL importFile(FILE* fp); + virtual BOOL exportFile(FILE* fp, BOOL include_asset_key = TRUE) const; + + virtual BOOL importLegacyStream(std::istream& input_stream); + virtual BOOL exportLegacyStream(std::ostream& output_stream, BOOL include_asset_key = TRUE) const; + + // virtual methods + virtual void removeFromServer(); + virtual void updateParentOnServer(BOOL) const; + virtual void updateServer(BOOL) const; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryItem +// +// An inventory item represents something that the current user has in +// their inventory. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLInventoryItem : public LLInventoryObject +{ +public: + typedef LLDynamicArray > item_array_t; + +protected: + LLPermissions mPermissions; + LLUUID mAssetUUID; + LLString mDescription; + LLSaleInfo mSaleInfo; + LLInventoryType::EType mInventoryType; + U32 mFlags; + S32 mCreationDate; // seconds from 1/1/1970, UTC + +public: + enum + { + // The meaning of LLInventoryItem::mFlags is distinct for each + // inventory type. + II_FLAGS_NONE = 0, + + // landmark flags + II_FLAGS_LANDMARK_VISITED = 1, + + // flag to indicate that object permissions should have next + // owner perm be more restrictive on rez. We bump this into + // the second byte of the flags since the low byte is used to + // track attachment points. + II_FLAGS_OBJECT_SLAM_PERM = 0x100, + + // These flags specify which permissions masks to overwrite + // upon rez. Normally, if no permissions slam (above) or + // overwrite flags are set, the asset's permissions are + // used and the inventory's permissions are ignored. If + // any of these flags are set, the inventory's permissions + // take precedence. + II_FLAGS_OBJECT_PERM_OVERWRITE_BASE = 0x010000, + II_FLAGS_OBJECT_PERM_OVERWRITE_OWNER = 0x020000, + II_FLAGS_OBJECT_PERM_OVERWRITE_GROUP = 0x040000, + II_FLAGS_OBJECT_PERM_OVERWRITE_EVERYONE = 0x080000, + II_FLAGS_OBJECT_PERM_OVERWRITE_NEXT_OWNER = 0x100000, + + // wearables use the low order byte of flags to store the + // EWearableType enumeration found in newview/llwearable.h + }; + +protected: + ~LLInventoryItem(); // ref counted + +public: + MEM_TYPE_NEW(LLMemType::MTYPE_INVENTORY); + LLInventoryItem(const LLUUID& uuid, + const LLUUID& parent_uuid, + const LLPermissions& permissions, + const LLUUID& asset_uuid, + LLAssetType::EType type, + LLInventoryType::EType inv_type, + const LLString& name, + const LLString& desc, + const LLSaleInfo& sale_info, + U32 flags, + S32 creation_date_utc); + LLInventoryItem(); + // Create a copy of an inventory item from a pointer to another item + // Note: Because InventoryItems are ref counted, reference copy (a = b) + // is prohibited + LLInventoryItem(const LLInventoryItem* other); + virtual void copy(const LLInventoryItem* other); // LLRefCount requires custom copy + + // As a constructor alternative, the clone() method works like a + // copy constructor, but gens a new UUID. + // It is up to the caller to delete (unref) the item. + virtual void clone(LLPointer& newitem) const; + + // accessors + const LLPermissions& getPermissions() const; + const LLUUID& getCreatorUUID() const; + const LLUUID& getAssetUUID() const; + const LLString& getDescription() const; + const LLSaleInfo& getSaleInfo() const; + LLInventoryType::EType getInventoryType() const; + U32 getFlags() const; + S32 getCreationDate() const; + U32 getCRC32() const; // really more of a checksum. + + // mutators - will not call updateServer(), and will never fail + // (though it may correct to sane values) + void setAssetUUID(const LLUUID& asset_id); + void setDescription(const LLString& new_desc); + void setSaleInfo(const LLSaleInfo& sale_info); + void setPermissions(const LLPermissions& perm); + void setInventoryType(LLInventoryType::EType inv_type); + void setFlags(U32 flags); + void setCreationDate(S32 creation_date_utc); + + // Put this inventory item onto the current outgoing mesage. It + // assumes you have already called nextBlock(). + virtual void packMessage(LLMessageSystem* msg) const; + + // unpack returns TRUE if the inventory item came through the + // network ok. It uses a simple crc check which is defeatable, but + // we want to detect network mangling somehow. + virtual BOOL unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num = 0); + + // file support + virtual BOOL importFile(FILE* fp); + virtual BOOL exportFile(FILE* fp, BOOL include_asset_key = TRUE) const; + + virtual BOOL importLegacyStream(std::istream& input_stream); + virtual BOOL exportLegacyStream(std::ostream& output_stream, BOOL include_asset_key = TRUE) const; + + virtual LLXMLNode *exportFileXML(BOOL include_asset_key = TRUE) const; + BOOL importXML(LLXMLNode* node); + + // helper functions + + // pack all information needed to reconstruct this item into the given binary bucket. + // perm_override is optional + S32 packBinaryBucket(U8* bin_bucket, LLPermissions* perm_override = NULL) const; + void unpackBinaryBucket(U8* bin_bucket, S32 bin_bucket_size); + LLSD asLLSD() const; + bool fromLLSD(LLSD& sd); + +}; + +BOOL item_dictionary_sort(LLInventoryItem* a,LLInventoryItem* b); +BOOL item_date_sort(LLInventoryItem* a, LLInventoryItem* b); + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryCategory +// +// An instance of this class represents a category of inventory +// items. Users come with a set of default categories, and can create +// new ones as needed. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLInventoryCategory : public LLInventoryObject +{ +public: + typedef LLDynamicArray > cat_array_t; + +protected: + ~LLInventoryCategory(); + +public: + MEM_TYPE_NEW(LLMemType::MTYPE_INVENTORY); + LLInventoryCategory(const LLUUID& uuid, const LLUUID& parent_uuid, + LLAssetType::EType preferred_type, + const LLString& name); + LLInventoryCategory(); + LLInventoryCategory(const LLInventoryCategory* other); + virtual void copy(const LLInventoryCategory* other); // LLRefCount requires custom copy + + // accessors and mutators + LLAssetType::EType getPreferredType() const; + void setPreferredType(LLAssetType::EType type); + // For messaging system support + virtual void packMessage(LLMessageSystem* msg) const; + virtual void unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num = 0); + + // file support + virtual BOOL importFile(FILE* fp); + virtual BOOL exportFile(FILE* fp, BOOL include_asset_key = TRUE) const; + + virtual BOOL importLegacyStream(std::istream& input_stream); + virtual BOOL exportLegacyStream(std::ostream& output_stream, BOOL include_asset_key = TRUE) const; + +protected: + // The type of asset that this category was "meant" to hold + // (although it may in fact hold any type). + LLAssetType::EType mPreferredType; + +}; + + +//----------------------------------------------------------------------------- +// Useful bits +//----------------------------------------------------------------------------- + +// This functor tests if an item is transferrable and returns true if +// it is. Derived from unary_function<> so that the object can be used +// in stl-compliant adaptable predicates (eg, not1<>). You might want +// to use this in std::partition() or similar logic. +struct IsItemTransferable : public std::unary_function +{ + LLUUID mDestID; + IsItemTransferable(const LLUUID& dest_id) : mDestID(dest_id) {} + bool operator()(const LLInventoryItem* item) const + { + return (item->getPermissions().allowTransferTo(mDestID)) ? true:false; + } +}; + +// This functor is used to set the owner and group of inventory items, +// for example, in a simple std::for_each() loop. Note that the call +// to setOwnerAndGroup can fail if authority_id != LLUUID::null. +struct SetItemOwnerAndGroup +{ + LLUUID mAuthorityID; + LLUUID mOwnerID; + LLUUID mGroupID; + SetItemOwnerAndGroup(const LLUUID& authority_id, + const LLUUID& owner_id, + const LLUUID& group_id) : + mAuthorityID(authority_id), mOwnerID(owner_id), mGroupID(group_id) {} + void operator()(LLInventoryItem* item) const + { + LLPermissions perm = item->getPermissions(); + bool is_atomic = (LLAssetType::AT_OBJECT == item->getType()) ? false : true; + perm.setOwnerAndGroup(mAuthorityID, mOwnerID, mGroupID, is_atomic); + item->setPermissions(perm); + } +}; + +// This functor is used to unset the share with group, everyone perms, and +// for sale info for objects being sold through contents. +struct SetNotForSale +{ + LLUUID mAgentID; + LLUUID mGroupID; + SetNotForSale(const LLUUID& agent_id, + const LLUUID& group_id) : + mAgentID(agent_id), mGroupID(group_id) {} + void operator()(LLInventoryItem* item) const + { + // Clear group & everyone permissions. + LLPermissions perm = item->getPermissions(); + perm.setGroupBits(mAgentID, mGroupID, FALSE, PERM_MODIFY | PERM_MOVE | PERM_COPY); + perm.setEveryoneBits(mAgentID, mGroupID, FALSE, PERM_MOVE | PERM_COPY); + item->setPermissions(perm); + + // Mark group & everyone permissions for overwrite on the next + // rez if it is an object. + if(LLAssetType::AT_OBJECT == item->getType()) + { + U32 flags = item->getFlags(); + flags |= LLInventoryItem::II_FLAGS_OBJECT_PERM_OVERWRITE_GROUP; + flags |= LLInventoryItem::II_FLAGS_OBJECT_PERM_OVERWRITE_EVERYONE; + item->setFlags(flags); + } + + // Clear for sale info. + item->setSaleInfo(LLSaleInfo::DEFAULT); + } +}; + +typedef std::list > InventoryObjectList; + +// These functions convert between structured data and an inventroy +// item, appropriate for serialization. +LLSD ll_create_sd_from_inventory_item(LLPointer item); +LLPointer ll_create_item_from_sd(const LLSD& sd_item); +LLSD ll_create_sd_from_inventory_category(LLPointer cat); +LLPointer ll_create_category_from_sd(const LLSD& sd_cat); + +#endif // LL_LLINVENTORY_H diff --git a/indra/llinventory/llnotecard.cpp b/indra/llinventory/llnotecard.cpp new file mode 100644 index 0000000000..3e994a61aa --- /dev/null +++ b/indra/llinventory/llnotecard.cpp @@ -0,0 +1,286 @@ +/** + * @file llnotecard.cpp + * @brief LLNotecard class definition + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llinventory.h" +#include "llnotecard.h" +#include "llstreamtools.h" + +LLNotecard::LLNotecard(U32 max_text) +: mMaxText(max_text) +{ +} + +LLNotecard::~LLNotecard() +{ +} + +bool LLNotecard::importEmbeddedItemsStream(std::istream& str) +{ + // Version 1 format: + // LLEmbeddedItems version 1 + // { + // count + // { + // ext char index + // + // } + // } + + S32 i; + S32 count = 0; + + str >> std::ws >> "LLEmbeddedItems version" >> mEmbeddedVersion >> "\n"; + if (str.fail()) + { + llwarns << "Invalid Linden text file header" << llendl; + goto import_file_failed; + } + + if( 1 != mEmbeddedVersion ) + { + llwarns << "Invalid LLEmbeddedItems version: " << mEmbeddedVersion << llendl; + goto import_file_failed; + } + + str >> std::ws >> "{\n"; + if(str.fail()) + { + llwarns << "Invalid Linden text file format: missing {" << llendl; + goto import_file_failed; + } + + str >> std::ws >> "count " >> count >> "\n"; + if(str.fail()) + { + llwarns << "Invalid LLEmbeddedItems count" << llendl; + goto import_file_failed; + } + + if((count < 0)) + { + llwarns << "Invalid LLEmbeddedItems count value: " << count << llendl; + goto import_file_failed; + } + + for(i = 0; i < count; i++) + { + str >> std::ws >> "{\n"; + if(str.fail()) + { + llwarns << "Invalid LLEmbeddedItems file format: missing {" << llendl; + goto import_file_failed; + } + + U32 index = 0; + str >> std::ws >> "ext char index " >> index >> "\n"; + if(str.fail()) + { + llwarns << "Invalid LLEmbeddedItems file format: missing ext char index" << llendl; + goto import_file_failed; + } + + if( (index < 0) ) + { + llwarns << "Invalid LLEmbeddedItems file format: invalid ext char index: " << index << llendl; + goto import_file_failed; + } + + str >> std::ws >> "inv_item\t0\n"; + if(str.fail()) + { + llwarns << "Invalid LLEmbeddedItems file format: missing inv_item" << llendl; + goto import_file_failed; + } + + LLPointer item = new LLInventoryItem; + if (!item->importLegacyStream(str)) + { + llinfos << "notecard import failed" << llendl; + goto import_file_failed; + } + mItems.push_back(item); + + str >> std::ws >> "}\n"; + if(str.fail()) + { + llwarns << "Invalid LLEmbeddedItems file format: missing }" << llendl; + goto import_file_failed; + } + } + + str >> std::ws >> "}\n"; + if(str.fail()) + { + llwarns << "Invalid LLEmbeddedItems file format: missing }" << llendl; + goto import_file_failed; + } + + return true; + + import_file_failed: + return false; +} + +bool LLNotecard::importStream(std::istream& str) +{ + // Version 1 format: + // Linden text version 1 + // { + // + // Text length + // + // } + + // Version 2 format: (NOTE: Imports identically to version 1) + // Linden text version 2 + // { + // + // Text length + // + // } + + str >> std::ws >> "Linden text version " >> mVersion >> "\n"; + if(str.fail()) + { + llwarns << "Invalid Linden text file header " << llendl; + return FALSE; + } + + if( 1 != mVersion && 2 != mVersion) + { + llwarns << "Invalid Linden text file version: " << mVersion << llendl; + return FALSE; + } + + str >> std::ws >> "{\n"; + if(str.fail()) + { + llwarns << "Invalid Linden text file format" << llendl; + return FALSE; + } + + if(!importEmbeddedItemsStream(str)) + { + return FALSE; + } + + char line_buf[STD_STRING_BUF_SIZE]; + str.getline(line_buf, STD_STRING_BUF_SIZE); + if(str.fail()) + { + llwarns << "Invalid Linden text length field" << llendl; + return FALSE; + } + line_buf[STD_STRING_STR_LEN] = '\0'; + + U32 text_len = 0; + if( 1 != sscanf(line_buf, "Text length %d", &text_len) ) + { + llwarns << "Invalid Linden text length field" << llendl; + return FALSE; + } + + if(text_len > mMaxText) + { + llwarns << "Invalid Linden text length: " << text_len << llendl; + return FALSE; + } + + BOOL success = TRUE; + + char* text = new char[text_len + 1]; + fullread(str, text, text_len); + if(str.fail()) + { + llwarns << "Invalid Linden text: text shorter than text length: " << text_len << llendl; + success = FALSE; + } + text[text_len] = '\0'; + + if(success) + { + // Actually set the text + mText = text; + } + + delete[] text; + + return success; +} + +//////////////////////////////////////////////////////////////////////////// + +bool LLNotecard::exportEmbeddedItemsStream( std::ostream& out_stream ) +{ + out_stream << "LLEmbeddedItems version 1\n"; + out_stream << "{\n"; + + out_stream << llformat("count %d\n", mItems.size() ); + + S32 idx = 0; + for (std::vector >::iterator iter = mItems.begin(); + iter != mItems.end(); ++iter) + { + LLInventoryItem* item = *iter; + if (item) + { + out_stream << "{\n"; + out_stream << llformat("ext char index %d\n", idx ); + if( !item->exportLegacyStream( out_stream ) ) + { + return FALSE; + } + out_stream << "}\n"; + } + ++idx; + } + + out_stream << "}\n"; + + return TRUE; +} + +bool LLNotecard::exportStream( std::ostream& out_stream ) +{ + out_stream << "Linden text version 2\n"; + out_stream << "{\n"; + + if( !exportEmbeddedItemsStream( out_stream ) ) + { + return FALSE; + } + + out_stream << llformat("Text length %d\n", mText.length() ); + out_stream << mText; + out_stream << "}\n"; + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////// + +const std::vector >& LLNotecard::getItems() const +{ + return mItems; +} + +LLString& LLNotecard::getText() +{ + return mText; +} + +void LLNotecard::setItems(const std::vector >& items) +{ + mItems = items; +} + +void LLNotecard::setText(const LLString& text) +{ + mText = text; +} diff --git a/indra/llinventory/llnotecard.h b/indra/llinventory/llnotecard.h new file mode 100644 index 0000000000..5f510a674f --- /dev/null +++ b/indra/llinventory/llnotecard.h @@ -0,0 +1,41 @@ +/** + * @file llnotecard.h + * @brief LLNotecard class declaration + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_NOTECARD_H +#define LL_NOTECARD_H + +const S32 MAX_NOTECARD_SIZE = 65536; + +class LLNotecard +{ +public: + LLNotecard(U32 max_text); + virtual ~LLNotecard(); + + bool importStream(std::istream& str); + bool exportStream(std::ostream& str); + + const std::vector >& getItems() const; + LLString& getText(); + + void setItems(const std::vector >& items); + void setText(const LLString& text); + S32 getVersion() { return mVersion; } + S32 getEmbeddedVersion() { return mEmbeddedVersion; } + +private: + bool importEmbeddedItemsStream(std::istream& str); + bool exportEmbeddedItemsStream(std::ostream& str); + std::vector > mItems; + LLString mText; + U32 mMaxText; + S32 mVersion; + S32 mEmbeddedVersion; +}; + +#endif /* LL_NOTECARD_H */ diff --git a/indra/llinventory/llparcel.cpp b/indra/llinventory/llparcel.cpp new file mode 100644 index 0000000000..a19c2216df --- /dev/null +++ b/indra/llinventory/llparcel.cpp @@ -0,0 +1,1813 @@ +/** + * @file llparcel.cpp + * @brief A land parcel. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "indra_constants.h" +#include + +#include "llparcel.h" +#include "llstreamtools.h" + +#include "llmath.h" +#include "lltransactiontypes.h" +#include "lltransactionflags.h" +#include "message.h" +#include "u64.h" + +static const F32 SOME_BIG_NUMBER = 1000.0f; +static const F32 SOME_BIG_NEG_NUMBER = -1000.0f; +static const char* PARCEL_OWNERSHIP_STATUS_STRING[LLParcel::OS_COUNT] = +{ + "leased", + "lease_pending", + "abandoned" +}; + +// NOTE: Adding parcel categories also requires updating: +// * newview/app_settings/floater_directory.xml category combobox +// * Web site "create event" tools +static const char* PARCEL_CATEGORY_STRING[LLParcel::C_COUNT] = +{ + "none", + "linden", + "adult", + "arts", + "store", // "business" legacy name + "educational", + "game", // "gaming" legacy name + "gather", // "hangout" legacy name + "newcomer", + "park", + "home", // "residential" legacy name + "shopping", + "stage", + "other", +}; +static const char* PARCEL_CATEGORY_UI_STRING[LLParcel::C_COUNT + 1] = +{ + "None", + "Linden Location", + "Adult", + "Arts & Culture", + "Business", + "Educational", + "Gaming", + "Hangout", + "Newcomer Friendly", + "Parks & Nature", + "Residential", + "Shopping", + "Stage", + "Other", + "Any", // valid string for parcel searches +}; + +static const char* PARCEL_ACTION_STRING[LLParcel::A_COUNT + 1] = +{ + "create", + "release", + "absorb", + "absorbed", + "divide", + "division", + "acquire", + "relinquish", + "confirm", + "unknown" +}; + +// Timeouts for parcels +// default is 21 days * 24h/d * 60m/h * 60s/m *1000000 usec/s = 1814400000000 +const U64 DEFAULT_USEC_CONVERSION_TIMEOUT = U64L(1814400000000); +// ***** TESTING is 10 minutes +//const U64 DEFAULT_USEC_CONVERSION_TIMEOUT = U64L(600000000); + +// group is 60 days * 24h/d * 60m/h * 60s/m *1000000 usec/s = 5184000000000 +const U64 GROUP_USEC_CONVERSION_TIMEOUT = U64L(5184000000000); +// ***** TESTING is 10 minutes +//const U64 GROUP_USEC_CONVERSION_TIMEOUT = U64L(600000000); + +// default sale timeout is 2 days -> 172800000000 +const U64 DEFAULT_USEC_SALE_TIMEOUT = U64L(172800000000); +// ***** TESTING is 10 minutes +//const U64 DEFAULT_USEC_SALE_TIMEOUT = U64L(600000000); + +// more grace period extensions. +const U64 SEVEN_DAYS_IN_USEC = U64L(604800000000); + +// if more than 100,000s before sale revert, and no extra extension +// has been given, go ahead and extend it more. That's about 1.2 days. +const S32 EXTEND_GRACE_IF_MORE_THAN_SEC = 100000; + + +const char* ownership_status_to_string(LLParcel::EOwnershipStatus status); +LLParcel::EOwnershipStatus ownership_string_to_status(const char* s); +//const char* revert_action_to_string(LLParcel::ESaleTimerExpireAction action); +//LLParcel::ESaleTimerExpireAction revert_string_to_action(const char* s); +const char* category_to_string(LLParcel::ECategory category); +const char* category_to_ui_string(LLParcel::ECategory category); +LLParcel::ECategory category_string_to_category(const char* s); +LLParcel::ECategory category_ui_string_to_category(const char* s); + +LLParcel::LLParcel() +{ + init(LLUUID::null, TRUE, FALSE, FALSE, 0, 0, 0, 0, 0, 1.f, 0); +} + + +LLParcel::LLParcel(const LLUUID &owner_id, + BOOL modify, BOOL terraform, BOOL damage, + time_t claim_date, S32 claim_price_per_meter, + S32 rent_price_per_meter, S32 area, S32 sim_object_limit, F32 parcel_object_bonus, + BOOL is_group_owned) +{ + init( owner_id, modify, terraform, damage, claim_date, + claim_price_per_meter, rent_price_per_meter, area, sim_object_limit, parcel_object_bonus, + is_group_owned); +} + + +// virtual +LLParcel::~LLParcel() +{ + // user list cleaned up by LLDynamicArray destructor. +} + +void LLParcel::init(const LLUUID &owner_id, + BOOL modify, BOOL terraform, BOOL damage, + time_t claim_date, S32 claim_price_per_meter, + S32 rent_price_per_meter, S32 area, S32 sim_object_limit, F32 parcel_object_bonus, + BOOL is_group_owned) +{ + mID.setNull(); + mOwnerID = owner_id; + mGroupOwned = is_group_owned; + mClaimDate = claim_date; + mClaimPricePerMeter = claim_price_per_meter; + mRentPricePerMeter = rent_price_per_meter; + mArea = area; + mDiscountRate = 1.0f; + mDrawDistance = 512.f; + + mUserLookAt.setVec(0.0f, 0.f, 0.f); + // Default to using the parcel's landing point, if any. + mLandingType = L_LANDING_POINT; + + // *FIX: if owner_id != null, should be owned or sale pending, + // investigate init callers. + mStatus = OS_NONE; + mCategory = C_NONE; + mAuthBuyerID.setNull(); + //mBuyerID.setNull(); + //mJoinNeighbors = 0x0; + mSaleTimerExpires.setTimerExpirySec(0); + mSaleTimerExpires.stop(); + mGraceExtension = 0; + //mExpireAction = STEA_REVERT; + mRecordTransaction = FALSE; + + mAuctionID = 0; + mIsReservedForNewbie = FALSE; + mInEscrow = false; + + mParcelFlags = PF_DEFAULT; + setParcelFlag(PF_CREATE_OBJECTS, modify); + setParcelFlag(PF_ALLOW_TERRAFORM, terraform); + setParcelFlag(PF_ALLOW_DAMAGE, damage); + + mSalePrice = 10000; + setName(NULL); + setDesc(NULL); + setMusicURL(NULL); + setMediaURL(NULL); + mMediaID.setNull(); + mMediaAutoScale = 0; + + mGroupID.setNull(); + + mPassPrice = PARCEL_PASS_PRICE_DEFAULT; + mPassHours = PARCEL_PASS_HOURS_DEFAULT; + + mAABBMin.setVec(SOME_BIG_NUMBER, SOME_BIG_NUMBER, SOME_BIG_NUMBER); + mAABBMax.setVec(SOME_BIG_NEG_NUMBER, SOME_BIG_NEG_NUMBER, SOME_BIG_NEG_NUMBER); + + mLocalID = 0; + + //mSimWidePrimCorrection = 0; + setMaxPrimCapacity((S32)(sim_object_limit * area / (F32)(REGION_WIDTH_METERS * REGION_WIDTH_METERS))); + setSimWideMaxPrimCapacity(0); + setSimWidePrimCount(0); + setOwnerPrimCount(0); + setGroupPrimCount(0); + setOtherPrimCount(0); + setSelectedPrimCount(0); + setTempPrimCount(0); + setCleanOtherTime(0); + setParcelPrimBonus(parcel_object_bonus); + + setPreviousOwnerID(LLUUID::null); + setPreviouslyGroupOwned(FALSE); +} + +void LLParcel::overrideOwner(const LLUUID& owner_id, BOOL is_group_owned) +{ + // Override with system permission (LLUUID::null) + // Overridden parcels have no group + mOwnerID = owner_id; + mGroupOwned = is_group_owned; + if(mGroupOwned) + { + mGroupID = mOwnerID; + } + else + { + mGroupID.setNull(); + } + mInEscrow = false; +} + +void LLParcel::overrideParcelFlags(U32 flags) +{ + mParcelFlags = flags; +} + +void set_std_string(const char* src, std::string& dest) +{ + if(src) + { + dest.assign(src); + } + else + { +#if (LL_LINUX && __GNUC__ < 3) + dest.assign(std::string("")); +#else + dest.clear(); +#endif + } +} + +void LLParcel::setName(const char* name) +{ + // The escaping here must match the escaping in the database + // abstraction layer. + set_std_string(name, mName); + LLStringFn::replace_nonprintable(mName, LL_UNKNOWN_CHAR); +} + +void LLParcel::setDesc(const char* desc) +{ + // The escaping here must match the escaping in the database + // abstraction layer. + set_std_string(desc, mDesc); + mDesc = rawstr_to_utf8(mDesc); +} + +void LLParcel::setMusicURL(const char* url) +{ + set_std_string(url, mMusicURL); + + // The escaping here must match the escaping in the database + // abstraction layer. + // This should really filter the url in some way. Other than + // simply requiring non-printable. + LLStringFn::replace_nonprintable(mMusicURL, LL_UNKNOWN_CHAR); +} + +void LLParcel::setMediaURL(const char* url) +{ + set_std_string(url, mMediaURL); + + // The escaping here must match the escaping in the database + // abstraction layer if it's ever added. + // This should really filter the url in some way. Other than + // simply requiring non-printable. + LLStringFn::replace_nonprintable(mMediaURL, LL_UNKNOWN_CHAR); +} + +// virtual +void LLParcel::setLocalID(S32 local_id) +{ + mLocalID = local_id; +} + +void LLParcel::setParcelFlag(U32 flag, BOOL b) +{ + if (b) + { + mParcelFlags |= flag; + } + else + { + mParcelFlags &= ~flag; + } +} + + +BOOL LLParcel::allowModifyBy(const LLUUID &agent_id, const LLUUID &group_id) const +{ + if (agent_id == LLUUID::null) + { + // system always can enter + return TRUE; + } + else if (isPublic()) + { + return TRUE; + } + else if (agent_id == mOwnerID) + { + // owner can always perform operations + return TRUE; + } + else if (mParcelFlags & PF_CREATE_OBJECTS) + { + return TRUE; + } + else if ((mParcelFlags & PF_CREATE_GROUP_OBJECTS) + && group_id.notNull() ) + { + return (getGroupID() == group_id); + } + + return FALSE; +} + +BOOL LLParcel::allowTerraformBy(const LLUUID &agent_id) const +{ + if (agent_id == LLUUID::null) + { + // system always can enter + return TRUE; + } + else if(OS_LEASED == mStatus) + { + if(agent_id == mOwnerID) + { + // owner can modify leased land + return TRUE; + } + else + { + // otherwise check other people + return mParcelFlags & PF_ALLOW_TERRAFORM; + } + } + else + { + return FALSE; + } +} + + +bool LLParcel::isAgentBlockedFromParcel(LLParcel* parcelp, + const LLUUID& agent_id, + const std::vector& group_ids, + const BOOL is_agent_identified, + const BOOL is_agent_transacted) +{ + S32 current_group_access = parcelp->blockAccess(agent_id, LLUUID::null, is_agent_identified, is_agent_transacted); + S32 count; + bool is_allowed = (current_group_access == BA_ALLOWED) ? true: false; + LLUUID group_id; + + count = group_ids.size(); + for (int i = 0; i < count && !is_allowed; i++) + { + group_id = group_ids[i]; + current_group_access = parcelp->blockAccess(agent_id, group_id, is_agent_identified, is_agent_transacted); + + if (current_group_access == BA_ALLOWED) is_allowed = true; + } + + return !is_allowed; +} + +BOOL LLParcel::isAgentBanned(const LLUUID& agent_id) const +{ + // Test ban list + if (getParcelFlag(PF_USE_BAN_LIST) + && (mBanList.find(agent_id) != mBanList.end())) + { + return TRUE; + } + + return FALSE; +} +S32 LLParcel::blockAccess(const LLUUID& agent_id, const LLUUID& group_id, + const BOOL is_agent_identified, + const BOOL is_agent_transacted) const +{ + // Test ban list + if (isAgentBanned(agent_id)) + { + return BA_BANNED; + } + + // Always allow owner on (unless he banned himself, useful for + // testing). We will also allow estate owners/managers in if they + // are not explicitly banned. + if (agent_id == mOwnerID) + { + return BA_ALLOWED; + } + + // Special case when using pass list where group access is being restricted but not + // using access list. In this case group members are allowed only if they buy a pass. + // We return BA_NOT_IN_LIST if not in list + BOOL passWithGroup = getParcelFlag(PF_USE_PASS_LIST) && !getParcelFlag(PF_USE_ACCESS_LIST) + && getParcelFlag(PF_USE_ACCESS_GROUP) && !mGroupID.isNull() && group_id == mGroupID; + + + // Test group list + if (getParcelFlag(PF_USE_ACCESS_GROUP) + && !mGroupID.isNull() + && group_id == mGroupID + && !passWithGroup) + { + return BA_ALLOWED; + } + + // Test access list + if (getParcelFlag(PF_USE_ACCESS_LIST) || passWithGroup ) + { + if (mAccessList.find(agent_id) != mAccessList.end()) + { + return BA_ALLOWED; + } + + return BA_NOT_ON_LIST; + } + + // If we're not doing any other limitations, all users + // can enter, unless + if ( !getParcelFlag(PF_USE_ACCESS_GROUP) + && !getParcelFlag(PF_USE_ACCESS_LIST)) + { + //If the land is group owned, and you are in the group, bypass these checks + if(getIsGroupOwned() && group_id == mGroupID) + { + return BA_ALLOWED; + } + + // Test for "payment" access levels + // Anonymous - No Payment Info on File + if(getParcelFlag(PF_DENY_ANONYMOUS) && !is_agent_identified && !is_agent_transacted) + { + return BA_NO_ACCESS_LEVEL; + } + // Identified - Payment Info on File + // Must check to make sure we're only banning Identified, since Transacted accounts + // also have their identified flag set + if(getParcelFlag(PF_DENY_IDENTIFIED) && is_agent_identified && !is_agent_transacted) + { + return BA_NO_ACCESS_LEVEL; + } + // Transacted - Payment Info Used + if(getParcelFlag(PF_DENY_TRANSACTED) && is_agent_transacted) + { + return BA_NO_ACCESS_LEVEL; + } + return BA_ALLOWED; + } + + return BA_NOT_IN_GROUP; + +} + + +void LLParcel::setArea(S32 area, S32 sim_object_limit) +{ + mArea = area; + setMaxPrimCapacity((S32)(sim_object_limit * area / (F32)(REGION_WIDTH_METERS * REGION_WIDTH_METERS))); +} + +void LLParcel::setDiscountRate(F32 rate) +{ + // this is to make sure that the rate is at least sane - this is + // not intended to enforce economy rules. It only enfoces that the + // rate is a scaler between 0 and 1. + mDiscountRate = llclampf(rate); +} + + +//----------------------------------------------------------- +// File input and output +//----------------------------------------------------------- + + +// WARNING: Area will be wrong until you calculate it. +BOOL LLParcel::importStream(std::istream& input_stream) +{ + U32 setting; + S32 secs_until_revert = 0; + + skip_to_end_of_next_keyword("{", input_stream); + if (!input_stream.good()) + { + llwarns << "LLParcel::importStream() - bad input_stream" << llendl; + return FALSE; + } + + while (input_stream.good()) + { + skip_comments_and_emptyspace(input_stream); + LLString line, keyword, value; + get_line(line, input_stream, MAX_STRING); + get_keyword_and_value(keyword, value, line); + + if ("}" == keyword) + { + break; + } + else if ("parcel_id" == keyword) + { + mID.set(value.c_str()); + } + else if ("status" == keyword) + { + mStatus = ownership_string_to_status(value.c_str()); + } + else if ("category" == keyword) + { + mCategory = category_string_to_category(value.c_str()); + } + else if ("local_id" == keyword) + { + LLString::convertToS32(value, mLocalID); + } + else if ("name" == keyword) + { + setName( value.c_str() ); + } + else if ("desc" == keyword) + { + setDesc( value.c_str() ); + } + else if ("music_url" == keyword) + { + setMusicURL( value.c_str() ); + } + else if ("media_url" == keyword) + { + setMediaURL( value.c_str() ); + } + else if ("media_id" == keyword) + { + mMediaID.set( value.c_str() ); + } + else if ("media_auto_scale" == keyword) + { + LLString::convertToU8(value, mMediaAutoScale); + } + else if ("owner_id" == keyword) + { + mOwnerID.set( value.c_str() ); + } + else if ("group_owned" == keyword) + { + LLString::convertToBOOL(value, mGroupOwned); + } + else if ("clean_other_time" == keyword) + { + S32 time; + LLString::convertToS32(value, time); + setCleanOtherTime(time); + } + else if ("auth_buyer_id" == keyword) + { + mAuthBuyerID.set(value.c_str()); + } + else if ("snapshot_id" == keyword) + { + mSnapshotID.set(value.c_str()); + } + else if ("user_location" == keyword) + { + sscanf(value.c_str(), "%f %f %f", + &mUserLocation.mV[VX], + &mUserLocation.mV[VY], + &mUserLocation.mV[VZ]); + } + else if ("user_look_at" == keyword) + { + sscanf(value.c_str(), "%f %f %f", + &mUserLookAt.mV[VX], + &mUserLookAt.mV[VY], + &mUserLookAt.mV[VZ]); + } + else if ("landing_type" == keyword) + { + S32 landing_type = 0; + LLString::convertToS32(value, landing_type); + mLandingType = (ELandingType) landing_type; + } + else if ("join_neighbors" == keyword) + { + llinfos << "found deprecated keyword join_neighbors" << llendl; + } + else if ("revert_sale" == keyword) + { + LLString::convertToS32(value, secs_until_revert); + if (secs_until_revert > 0) + { + mSaleTimerExpires.start(); + mSaleTimerExpires.setTimerExpirySec((F32)secs_until_revert); + } + } + else if("extended_grace" == keyword) + { + LLString::convertToS32(value, mGraceExtension); + } + else if ("user_list_type" == keyword) + { + // deprecated + } + else if("auction_id" == keyword) + { + LLString::convertToU32(value, mAuctionID); + } + else if("reserved_newbie" == keyword) + { + LLString::convertToBOOL(value, mIsReservedForNewbie); + } + else if ("allow_modify" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_CREATE_OBJECTS, setting); + } + else if ("allow_group_modify" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_CREATE_GROUP_OBJECTS, setting); + } + else if ("allow_all_object_entry" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_ALLOW_ALL_OBJECT_ENTRY, setting); + } + else if ("allow_group_object_entry" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_ALLOW_GROUP_OBJECT_ENTRY, setting); + } + else if ("allow_deed_to_group" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_ALLOW_DEED_TO_GROUP, setting); + } + else if("contribute_with_deed" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_CONTRIBUTE_WITH_DEED, setting); + } + else if ("allow_terraform" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_ALLOW_TERRAFORM, setting); + } + else if ("allow_damage" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_ALLOW_DAMAGE, setting); + } + else if ("allow_fly" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_ALLOW_FLY, setting); + } + else if ("allow_landmark" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_ALLOW_LANDMARK, setting); + } + else if ("sound_local" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_SOUND_LOCAL, setting); + } + else if ("allow_group_scripts" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_ALLOW_GROUP_SCRIPTS, setting); + } + else if ("allow_scripts" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_ALLOW_OTHER_SCRIPTS, setting); + } + else if ("for_sale" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_FOR_SALE, setting); + } + else if ("sell_w_objects" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_SELL_PARCEL_OBJECTS, setting); + } + else if ("use_pass_list" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_USE_PASS_LIST, setting); + } + else if ("show_directory" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_SHOW_DIRECTORY, setting); + } + else if ("allow_publish" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_ALLOW_PUBLISH, setting); + } + else if ("mature_publish" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_MATURE_PUBLISH, setting); + } + else if ("claim_date" == keyword) + { + // BUG: This will fail when time rolls over in 2038. + S32 time; + LLString::convertToS32(value, time); + mClaimDate = time; + } + else if ("claim_price" == keyword) + { + LLString::convertToS32(value, mClaimPricePerMeter); + } + else if ("rent_price" == keyword) + { + LLString::convertToS32(value, mRentPricePerMeter); + } + else if ("discount_rate" == keyword) + { + LLString::convertToF32(value, mDiscountRate); + } + else if ("draw_distance" == keyword) + { + LLString::convertToF32(value, mDrawDistance); + } + else if ("sale_price" == keyword) + { + LLString::convertToS32(value, mSalePrice); + } + else if ("pass_price" == keyword) + { + LLString::convertToS32(value, mPassPrice); + } + else if ("pass_hours" == keyword) + { + LLString::convertToF32(value, mPassHours); + } + else if ("box" == keyword) + { + // deprecated + } + else if ("aabb_min" == keyword) + { + sscanf(value.c_str(), "%f %f %f", + &mAABBMin.mV[VX], &mAABBMin.mV[VY], &mAABBMin.mV[VZ]); + } + else if ("use_access_group" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_USE_ACCESS_GROUP, setting); + } + else if ("use_access_list" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_USE_ACCESS_LIST, setting); + } + else if ("use_ban_list" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_USE_BAN_LIST, setting); + } + else if ("group_name" == keyword) + { + llinfos << "found deprecated keyword group_name" << llendl; + } + else if ("group_id" == keyword) + { + mGroupID.set( value.c_str() ); + } + // TODO: DEPRECATED FLAG + // Flag removed from simstate files in 1.11.1 + // Remove at some point where we have guarenteed this flag + // no longer exists anywhere in simstate files. + else if ("require_identified" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_DENY_ANONYMOUS, setting); + } + // TODO: DEPRECATED FLAG + // Flag removed from simstate files in 1.11.1 + // Remove at some point where we have guarenteed this flag + // no longer exists anywhere in simstate files. + else if ("require_transacted" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_DENY_ANONYMOUS, setting); + setParcelFlag(PF_DENY_IDENTIFIED, setting); + } + else if ("restrict_pushobject" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_RESTRICT_PUSHOBJECT, setting); + } + else if ("deny_anonymous" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_DENY_ANONYMOUS, setting); + } + else if ("deny_identified" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_DENY_IDENTIFIED, setting); + } + else if ("deny_transacted" == keyword) + { + LLString::convertToU32(value, setting); + setParcelFlag(PF_DENY_TRANSACTED, setting); + } + else if ("access_list" == keyword) + { + S32 entry_count = 0; + LLString::convertToS32(value, entry_count); + for (S32 i = 0; i < entry_count; i++) + { + LLAccessEntry entry; + if (importAccessEntry(input_stream, &entry)) + { + mAccessList[entry.mID] = entry; + } + } + } + else if ("ban_list" == keyword) + { + S32 entry_count = 0; + LLString::convertToS32(value, entry_count); + for (S32 i = 0; i < entry_count; i++) + { + LLAccessEntry entry; + if (importAccessEntry(input_stream, &entry)) + { + mBanList[entry.mID] = entry; + } + } + } + else if ("renter_list" == keyword) + { + /* + S32 entry_count = 0; + LLString::convertToS32(value, entry_count); + for (S32 i = 0; i < entry_count; i++) + { + LLAccessEntry entry; + if (importAccessEntry(input_stream, &entry)) + { + mRenterList.put(entry); + } + }*/ + } + else if ("pass_list" == keyword) + { + // legacy - put into access list + S32 entry_count = 0; + LLString::convertToS32(value, entry_count); + for (S32 i = 0; i < entry_count; i++) + { + LLAccessEntry entry; + if (importAccessEntry(input_stream, &entry)) + { + mAccessList[entry.mID] = entry; + } + } + } + + else + { + llwarns << "Unknown keyword in parcel section: <" + << keyword << ">" << llendl; + } + } + + // this code block detects if we have loaded a 1.1 simstate file, + // and follows the conversion rules specified in + // design_docs/land/pay_for_parcel.txt. + F32 time_to_expire = 0.0f; + if(mID.isNull()) + { + mID.generate(); + mStatus = OS_LEASE_PENDING; + //mBuyerID = mOwnerID; + if(getIsGroupOwned()) + { + time_to_expire += GROUP_USEC_CONVERSION_TIMEOUT / SEC_TO_MICROSEC; + } + else + { + time_to_expire += DEFAULT_USEC_CONVERSION_TIMEOUT / SEC_TO_MICROSEC; + } + //mExpireAction = STEA_PUBLIC; + mRecordTransaction = TRUE; + } + + // this code block deals with giving an extension to pending + // parcels to the midday of 2004-01-19 if they were originally set + // for some time on 2004-01-12. + if((0 == mGraceExtension) + && (EXTEND_GRACE_IF_MORE_THAN_SEC < secs_until_revert)) + { + const S32 NEW_CONVERSION_DATE = 1074538800; // 2004-01-19T11:00:00 + time_t now = time(NULL); // now in epoch + secs_until_revert = (S32)(NEW_CONVERSION_DATE - now); + time_to_expire = (F32)secs_until_revert; + mGraceExtension = 1; + } + + // This code block adds yet another week to the deadline. :( + if(1 == mGraceExtension) + { + time_to_expire += SEVEN_DAYS_IN_USEC / SEC_TO_MICROSEC; + mGraceExtension = 2; + } + + if (time_to_expire > 0) + { + mSaleTimerExpires.setTimerExpirySec(time_to_expire); + mSaleTimerExpires.start(); + } + + // successful import + return TRUE; +} + + +BOOL LLParcel::importAccessEntry(std::istream& input_stream, LLAccessEntry* entry) +{ + skip_to_end_of_next_keyword("{", input_stream); + while (input_stream.good()) + { + skip_comments_and_emptyspace(input_stream); + LLString line, keyword, value; + get_line(line, input_stream, MAX_STRING); + get_keyword_and_value(keyword, value, line); + + if ("}" == keyword) + { + break; + } + else if ("id" == keyword) + { + entry->mID.set( value.c_str() ); + } + else if ("name" == keyword) + { + // deprecated + } + else if ("time" == keyword) + { + S32 when; + LLString::convertToS32(value, when); + entry->mTime = when; + } + else if ("flags" == keyword) + { + U32 setting; + LLString::convertToU32(value, setting); + entry->mFlags = setting; + } + else + { + llwarns << "Unknown keyword in parcel access entry section: <" + << keyword << ">" << llendl; + } + } + return input_stream.good(); +} + +BOOL LLParcel::exportStream(std::ostream& output_stream) +{ + S32 setting; + char id_string[MAX_STRING]; + + std::ios::fmtflags old_flags = output_stream.flags(); + output_stream.setf(std::ios::showpoint); + output_stream << "\t{\n"; + + mID.toString(id_string); + output_stream << "\t\t parcel_id " << id_string << "\n"; + output_stream << "\t\t status " << ownership_status_to_string(mStatus) << "\n"; + output_stream << "\t\t category " << category_to_string(mCategory) << "\n"; + + output_stream << "\t\t local_id " << mLocalID << "\n"; + + const char* name = (mName.empty() ? "" : mName.c_str() ); + output_stream << "\t\t name " << name << "\n"; + + const char* desc = (mDesc.empty() ? "" : mDesc.c_str() ); + output_stream << "\t\t desc " << desc << "\n"; + + const char* music_url = (mMusicURL.empty() ? "" : mMusicURL.c_str() ); + output_stream << "\t\t music_url " << music_url << "\n"; + + const char* media_url = (mMediaURL.empty() ? "" : mMediaURL.c_str() ); + output_stream << "\t\t media_url " << media_url << "\n"; + + output_stream << "\t\t media_auto_scale " << (mMediaAutoScale ? 1 : 0) << "\n"; + + mMediaID.toString(id_string); + output_stream << "\t\t media_id " << id_string << "\n"; + + mOwnerID.toString(id_string); + output_stream << "\t\t owner_id " << id_string << "\n"; + output_stream << "\t\t group_owned " << (mGroupOwned ? 1 : 0) << "\n"; + output_stream << "\t\t clean_other_time " << getCleanOtherTime() << "\n"; + + if(!mAuthBuyerID.isNull()) + { + mAuthBuyerID.toString(id_string); + output_stream << "\t\t auth_buyer_id " << id_string << "\n"; + } + if (!mSnapshotID.isNull()) + { + mSnapshotID.toString(id_string); + output_stream << "\t\t snapshot_id " << id_string << "\n"; + } + if (!mUserLocation.isExactlyZero()) + { + output_stream << "\t\t user_location " + << (F64)mUserLocation.mV[VX] + << " " << (F64)mUserLocation.mV[VY] + << " " << (F64)mUserLocation.mV[VZ] << "\n"; + output_stream << "\t\t user_look_at " + << (F64)mUserLookAt.mV[VX] + << " " << (F64)mUserLookAt.mV[VY] + << " " << (F64)mUserLookAt.mV[VZ] << "\n"; + } + output_stream << "\t\t landing_type " << mLandingType << "\n"; + //if(mJoinNeighbors) + //{ + // output_stream << "\t\t join_neighbors " << mJoinNeighbors << "\n"; + //} + if(mSaleTimerExpires.getStarted()) + { + S32 dt_sec = (S32) mSaleTimerExpires.getRemainingTimeF32()+60; // Add a minute to prevent race conditions + output_stream << "\t\t revert_sale " << dt_sec << "\n"; + //output_stream << "\t\t revert_action " << revert_action_to_string(mExpireAction) << "\n"; + output_stream << "\t\t extended_grace " << mGraceExtension << "\n"; + } + + if(0 != mAuctionID) + { + output_stream << "\t\t auction_id " << mAuctionID << "\n"; + } + if(mIsReservedForNewbie) + { + output_stream << "\t\t reserved_newbie " << mIsReservedForNewbie << "\n"; + } + + output_stream << "\t\t allow_modify " << getAllowModify() << "\n"; + output_stream << "\t\t allow_group_modify " << getAllowGroupModify() << "\n"; + output_stream << "\t\t allow_all_object_entry " << getAllowAllObjectEntry() << "\n"; + output_stream << "\t\t allow_group_object_entry " << getAllowGroupObjectEntry() << "\n"; + output_stream << "\t\t allow_terraform " << getAllowTerraform() << "\n"; + output_stream << "\t\t allow_deed_to_group " << getAllowDeedToGroup() << "\n"; + output_stream << "\t\t contribute_with_deed " << getContributeWithDeed() << "\n"; + output_stream << "\t\t allow_damage " << getAllowDamage() << "\n"; + output_stream << "\t\t claim_date " << (S32)mClaimDate << "\n"; + output_stream << "\t\t claim_price " << mClaimPricePerMeter << "\n"; + output_stream << "\t\t rent_price " << mRentPricePerMeter << "\n"; + output_stream << "\t\t discount_rate " << mDiscountRate << "\n"; + output_stream << "\t\t allow_fly " << (getAllowFly() ? 1 : 0) << "\n"; + output_stream << "\t\t allow_landmark " << (getAllowLandmark() ? 1 : 0) << "\n"; + output_stream << "\t\t sound_local " << (getSoundLocal() ? 1 : 0) << "\n"; + output_stream << "\t\t allow_scripts " << (getAllowOtherScripts() ? 1 : 0) << "\n"; + output_stream << "\t\t allow_group_scripts " << (getAllowGroupScripts() ? 1 : 0) << "\n"; + output_stream << "\t\t for_sale " << (getForSale() ? 1 : 0) << "\n"; + output_stream << "\t\t sell_w_objects " << (getSellWithObjects() ? 1 : 0) << "\n"; + output_stream << "\t\t draw_distance " << mDrawDistance << "\n"; + output_stream << "\t\t sale_price " << mSalePrice << "\n"; + + setting = (getParcelFlag(PF_USE_ACCESS_GROUP) ? 1 : 0); + output_stream << "\t\t use_access_group " << setting << "\n"; + + setting = (getParcelFlag(PF_USE_ACCESS_LIST) ? 1 : 0); + output_stream << "\t\t use_access_list " << setting << "\n"; + + setting = (getParcelFlag(PF_USE_BAN_LIST) ? 1 : 0); + output_stream << "\t\t use_ban_list " << setting << "\n"; + + mGroupID.toString(id_string); + output_stream << "\t\t group_id " << id_string << "\n"; + + //const char* group_name + // = (mGroupName.isEmpty() ? "" : mGroupName.c_str() ); + //output_stream << "\t\t group_name " << group_name << "\n"; + + setting = (getParcelFlag(PF_USE_PASS_LIST) ? 1 : 0); + output_stream << "\t\t use_pass_list " << setting << "\n"; + + output_stream << "\t\t pass_price " << mPassPrice << "\n"; + output_stream << "\t\t pass_hours " << mPassHours << "\n"; + + setting = (getParcelFlag(PF_SHOW_DIRECTORY) ? 1 : 0); + output_stream << "\t\t show_directory " << setting << "\n"; + + setting = (getParcelFlag(PF_ALLOW_PUBLISH) ? 1 : 0); + output_stream << "\t\t allow_publish " << setting << "\n"; + + setting = (getParcelFlag(PF_MATURE_PUBLISH) ? 1 : 0); + output_stream << "\t\t mature_publish " << setting << "\n"; + + setting = (getParcelFlag(PF_DENY_ANONYMOUS) ? 1 : 0); + output_stream << "\t\t deny_anonymous " << setting << "\n"; + + setting = (getParcelFlag(PF_DENY_IDENTIFIED) ? 1 : 0); + output_stream << "\t\t deny_identified " << setting << "\n"; + + setting = (getParcelFlag(PF_DENY_TRANSACTED) ? 1 : 0); + output_stream << "\t\t deny_transacted " << setting << "\n"; + + setting = (getParcelFlag(PF_RESTRICT_PUSHOBJECT) ? 1 : 0); + output_stream << "\t\t restrict_pushobject " << setting << "\n"; + + output_stream << "\t\t aabb_min " + << mAABBMin.mV[VX] + << " " << mAABBMin.mV[VY] + << " " << mAABBMin.mV[VZ] << "\n"; + + if (!mAccessList.empty()) + { + output_stream << "\t\t access_list " << mAccessList.size() << "\n"; + access_map_const_iterator cit = mAccessList.begin(); + access_map_const_iterator end = mAccessList.end(); + + for ( ; cit != end; ++cit) + { + output_stream << "\t\t{\n"; + const LLAccessEntry& entry = (*cit).second; + entry.mID.toString(id_string); + output_stream << "\t\t\tid " << id_string << "\n"; + output_stream << "\t\t\ttime " << entry.mTime << "\n"; + output_stream << "\t\t\tflags " << entry.mFlags << "\n"; + output_stream << "\t\t}\n"; + } + } + + if (!mBanList.empty()) + { + output_stream << "\t\t ban_list " << mBanList.size() << "\n"; + access_map_const_iterator cit = mBanList.begin(); + access_map_const_iterator end = mBanList.end(); + + for ( ; cit != end; ++cit) + { + output_stream << "\t\t{\n"; + const LLAccessEntry& entry = (*cit).second; + entry.mID.toString(id_string); + output_stream << "\t\t\tid " << id_string << "\n"; + output_stream << "\t\t\ttime " << entry.mTime << "\n"; + output_stream << "\t\t\tflags " << entry.mFlags << "\n"; + output_stream << "\t\t}\n"; + } + } + + /*if (mRenterList.count() > 0) + { + output_stream << "\t\t renter_list " << mRenterList.count() << "\n"; + for (i = 0; i < mRenterList.count(); i++) + { + output_stream << "\t\t{\n"; + const LLAccessEntry& entry = mRenterList.get(i); + entry.mID.toString(id_string); + output_stream << "\t\t\tid " << id_string << "\n"; + output_stream << "\t\t\ttime " << entry.mTime << "\n"; + output_stream << "\t\t\tflags " << entry.mFlags << "\n"; + output_stream << "\t\t}\n"; + } + }*/ + + output_stream << "\t}\n"; + output_stream.flags(old_flags); + + return TRUE; +} + + +// Assumes we are in a block "ParcelData" +void LLParcel::packMessage(LLMessageSystem* msg) +{ + msg->addU32Fast( _PREHASH_ParcelFlags, getParcelFlags() ); + msg->addS32Fast( _PREHASH_SalePrice, getSalePrice() ); + msg->addStringFast( _PREHASH_Name, getName() ); + msg->addStringFast( _PREHASH_Desc, getDesc() ); + msg->addStringFast( _PREHASH_MusicURL, getMusicURL() ); + msg->addStringFast( _PREHASH_MediaURL, getMediaURL() ); + msg->addU8 ( "MediaAutoScale", getMediaAutoScale () ); + msg->addUUIDFast( _PREHASH_MediaID, getMediaID() ); + msg->addUUIDFast( _PREHASH_GroupID, getGroupID() ); + msg->addS32Fast( _PREHASH_PassPrice, mPassPrice ); + msg->addF32Fast( _PREHASH_PassHours, mPassHours ); + msg->addU8Fast( _PREHASH_Category, (U8)mCategory); + msg->addUUIDFast( _PREHASH_AuthBuyerID, mAuthBuyerID); + msg->addUUIDFast( _PREHASH_SnapshotID, mSnapshotID); + msg->addVector3Fast(_PREHASH_UserLocation, mUserLocation); + msg->addVector3Fast(_PREHASH_UserLookAt, mUserLookAt); + msg->addU8Fast( _PREHASH_LandingType, (U8)mLandingType); +} + + +void LLParcel::unpackMessage(LLMessageSystem* msg) +{ + char buffer[256]; + + msg->getU32Fast( _PREHASH_ParcelData,_PREHASH_ParcelFlags, mParcelFlags ); + msg->getS32Fast( _PREHASH_ParcelData,_PREHASH_SalePrice, mSalePrice ); + msg->getStringFast( _PREHASH_ParcelData,_PREHASH_Name, 256, buffer ); + setName(buffer); + msg->getStringFast( _PREHASH_ParcelData,_PREHASH_Desc, 256, buffer ); + setDesc(buffer); + msg->getStringFast( _PREHASH_ParcelData,_PREHASH_MusicURL, 256, buffer ); + setMusicURL(buffer); + msg->getStringFast( _PREHASH_ParcelData,_PREHASH_MediaURL, 256, buffer ); + setMediaURL(buffer); + + // non-optimized version + msg->getU8 ( "ParcelData", "MediaAutoScale", mMediaAutoScale ); + + msg->getUUIDFast( _PREHASH_ParcelData,_PREHASH_MediaID, mMediaID ); + msg->getUUIDFast( _PREHASH_ParcelData,_PREHASH_GroupID, mGroupID ); + msg->getS32Fast( _PREHASH_ParcelData,_PREHASH_PassPrice, mPassPrice ); + msg->getF32Fast( _PREHASH_ParcelData,_PREHASH_PassHours, mPassHours ); + U8 category; + msg->getU8Fast( _PREHASH_ParcelData,_PREHASH_Category, category); + mCategory = (ECategory)category; + msg->getUUIDFast( _PREHASH_ParcelData,_PREHASH_AuthBuyerID, mAuthBuyerID); + msg->getUUIDFast( _PREHASH_ParcelData,_PREHASH_SnapshotID, mSnapshotID); + msg->getVector3Fast(_PREHASH_ParcelData,_PREHASH_UserLocation, mUserLocation); + msg->getVector3Fast(_PREHASH_ParcelData,_PREHASH_UserLookAt, mUserLookAt); + U8 landing_type; + msg->getU8Fast( _PREHASH_ParcelData,_PREHASH_LandingType, landing_type); + mLandingType = (ELandingType)landing_type; +} + + +void LLParcel::packAccessEntries(LLMessageSystem* msg, + const std::map& list) +{ + access_map_const_iterator cit = list.begin(); + access_map_const_iterator end = list.end(); + + if (cit == end) + { + msg->nextBlockFast(_PREHASH_List); + msg->addUUIDFast(_PREHASH_ID, LLUUID::null ); + msg->addS32Fast(_PREHASH_Time, 0 ); + msg->addU32Fast(_PREHASH_Flags, 0 ); + return; + } + + for ( ; cit != end; ++cit) + { + const LLAccessEntry& entry = (*cit).second; + + msg->nextBlockFast(_PREHASH_List); + msg->addUUIDFast(_PREHASH_ID, entry.mID ); + msg->addS32Fast(_PREHASH_Time, entry.mTime ); + msg->addU32Fast(_PREHASH_Flags, entry.mFlags ); + } +} + + +void LLParcel::unpackAccessEntries(LLMessageSystem* msg, + std::map* list) +{ + LLUUID id; + S32 time; + U32 flags; + + S32 i; + S32 count = msg->getNumberOfBlocksFast(_PREHASH_List); + for (i = 0; i < count; i++) + { + msg->getUUIDFast(_PREHASH_List, _PREHASH_ID, id, i); + msg->getS32Fast( _PREHASH_List, _PREHASH_Time, time, i); + msg->getU32Fast( _PREHASH_List, _PREHASH_Flags, flags, i); + + if (id.notNull()) + { + LLAccessEntry entry; + entry.mID = id; + entry.mTime = time; + entry.mFlags = flags; + + (*list)[entry.mID] = entry; + } + } +} + + +void LLParcel::expirePasses(S32 now) +{ + access_map_iterator itor = mAccessList.begin(); + while (itor != mAccessList.end()) + { + const LLAccessEntry& entry = (*itor).second; + + if (entry.mTime != 0 && entry.mTime < now) + { + mAccessList.erase(itor++); + } + else + { + ++itor; + } + } +} + + +bool LLParcel::operator==(const LLParcel &rhs) const +{ + if (mOwnerID != rhs.mOwnerID) + return FALSE; + + if (mParcelFlags != rhs.mParcelFlags) + return FALSE; + + if (mClaimDate != rhs.mClaimDate) + return FALSE; + + if (mClaimPricePerMeter != rhs.mClaimPricePerMeter) + return FALSE; + + if (mRentPricePerMeter != rhs.mRentPricePerMeter) + return FALSE; + + return TRUE; +} + +// Calculate rent +S32 LLParcel::getTotalRent() const +{ + return (S32)floor(0.5f + (F32)mArea * (F32)mRentPricePerMeter * (1.0f - mDiscountRate)); +} + +F32 LLParcel::getAdjustedRentPerMeter() const +{ + return ((F32)mRentPricePerMeter * (1.0f - mDiscountRate)); +} + +LLVector3 LLParcel::getCenterpoint() const +{ + LLVector3 rv; + rv.mV[VX] = (getAABBMin().mV[VX] + getAABBMax().mV[VX]) * 0.5f; + rv.mV[VY] = (getAABBMin().mV[VY] + getAABBMax().mV[VY]) * 0.5f; + rv.mV[VZ] = 0.0f; + return rv; +} + +void LLParcel::extendAABB(const LLVector3& box_min, const LLVector3& box_max) +{ + // Patch up min corner of AABB + S32 i; + for (i=0; i<3; i++) + { + if (box_min.mV[i] < mAABBMin.mV[i]) + { + mAABBMin.mV[i] = box_min.mV[i]; + } + } + + // Patch up max corner of AABB + for (i=0; i<3; i++) + { + if (box_max.mV[i] > mAABBMax.mV[i]) + { + mAABBMax.mV[i] = box_max.mV[i]; + } + } +} + +BOOL LLParcel::addToAccessList(const LLUUID& agent_id, S32 time) +{ + if (!((mParcelFlags & PF_USE_ACCESS_LIST) || (mParcelFlags & PF_USE_PASS_LIST)) + || mAccessList.size() >= (U32) PARCEL_MAX_ACCESS_LIST) + { + // Not using access list, so not a rational thing to do + return FALSE; + } + if (agent_id == getOwnerID()) + { + // Can't add owner to these lists + return FALSE; + } + access_map_iterator itor = mAccessList.begin(); + while (itor != mAccessList.end()) + { + const LLAccessEntry& entry = (*itor).second; + if (entry.mID == agent_id) + { + if (time == 0 || (entry.mTime != 0 && entry.mTime < time)) + { + mAccessList.erase(itor++); + } + else + { + // existing one expires later + return FALSE; + } + } + else + { + ++itor; + } + } + + removeFromBanList(agent_id); + + LLAccessEntry new_entry; + new_entry.mID = agent_id; + new_entry.mTime = time; + new_entry.mFlags = 0x0; + mAccessList[new_entry.mID] = new_entry; + return TRUE; +} + +BOOL LLParcel::addToBanList(const LLUUID& agent_id, S32 time) +{ + if (!(mParcelFlags & PF_USE_BAN_LIST) + || mBanList.size() >= (U32) PARCEL_MAX_ACCESS_LIST) + { + // Not using ban list, so not a rational thing to do + return FALSE; + } + if (agent_id == getOwnerID()) + { + // Can't add owner to these lists + return FALSE; + } + + access_map_iterator itor = mBanList.begin(); + while (itor != mBanList.end()) + { + const LLAccessEntry& entry = (*itor).second; + if (entry.mID == agent_id) + { + if (time == 0 || (entry.mTime != 0 && entry.mTime < time)) + { + mBanList.erase(itor++); + } + else + { + // existing one expires later + return FALSE; + } + } + else + { + ++itor; + } + } + + removeFromAccessList(agent_id); + + LLAccessEntry new_entry; + new_entry.mID = agent_id; + new_entry.mTime = time; + new_entry.mFlags = 0x0; + mBanList[new_entry.mID] = new_entry; + return TRUE; +} + +BOOL remove_from_access_array(std::map* list, + const LLUUID& agent_id) +{ + BOOL removed = FALSE; + access_map_iterator itor = list->begin(); + while (itor != list->end()) + { + const LLAccessEntry& entry = (*itor).second; + if (entry.mID == agent_id) + { + list->erase(itor++); + removed = TRUE; + } + else + { + ++itor; + } + } + return removed; +} + +BOOL LLParcel::removeFromAccessList(const LLUUID& agent_id) +{ + return remove_from_access_array(&mAccessList, agent_id); +} + +BOOL LLParcel::removeFromBanList(const LLUUID& agent_id) +{ + return remove_from_access_array(&mBanList, agent_id); +} + +// static +const char* LLParcel::getOwnershipStatusString(EOwnershipStatus status) +{ + return ownership_status_to_string(status); +} + +// static +const char* LLParcel::getCategoryString(ECategory category) +{ + return category_to_string(category); +} + +// static +const char* LLParcel::getCategoryUIString(ECategory category) +{ + return category_to_ui_string(category); +} + +// static +LLParcel::ECategory LLParcel::getCategoryFromString(const char* string) +{ + return category_string_to_category(string); +} + +// static +LLParcel::ECategory LLParcel::getCategoryFromUIString(const char* string) +{ + return category_ui_string_to_category(string); +} + +// static +const char* LLParcel::getActionString(LLParcel::EAction action) +{ + S32 index = 0; + if((action >= 0) && (action < LLParcel::A_COUNT)) + { + index = action; + } + else + { + index = A_COUNT; + } + return PARCEL_ACTION_STRING[index]; +} + +BOOL LLParcel::isSaleTimerExpired(const U64& time) +{ + if (mSaleTimerExpires.getStarted() == FALSE) + { + return FALSE; + } + BOOL expired = mSaleTimerExpires.checkExpirationAndReset(0.0); + if (expired) + { + mSaleTimerExpires.stop(); + } + return expired; +} + + +void LLParcel::startSale(const LLUUID& buyer_id, BOOL is_buyer_group) +{ + // TODO -- this and all Sale related methods need to move out of the LLParcel + // base class and into server-side-only LLSimParcel class + setPreviousOwnerID(mOwnerID); + setPreviouslyGroupOwned(mGroupOwned); + + mOwnerID = buyer_id; + mGroupOwned = is_buyer_group; + if(mGroupOwned) + { + mGroupID = mOwnerID; + } + else + { + mGroupID.setNull(); + } + mSaleTimerExpires.start(); + mSaleTimerExpires.setTimerExpirySec(DEFAULT_USEC_SALE_TIMEOUT / SEC_TO_MICROSEC); + mStatus = OS_LEASE_PENDING; + mClaimDate = time(NULL); + mAuctionID = 0; + // clear the autoreturn whenever land changes hands + setCleanOtherTime(0); +} + +void LLParcel::expireSale(U32& type, U8& flags, LLUUID& from_id, LLUUID& to_id) +{ + mSaleTimerExpires.setTimerExpirySec(0.0); + mSaleTimerExpires.stop(); + setPreviousOwnerID(LLUUID::null); + setPreviouslyGroupOwned(FALSE); + setSellWithObjects(FALSE); + type = TRANS_LAND_RELEASE; + mStatus = OS_NONE; + mIsReservedForNewbie = FALSE; + flags = pack_transaction_flags(mGroupOwned, FALSE); + mAuthBuyerID.setNull(); + from_id = mOwnerID; + mOwnerID.setNull(); + to_id.setNull(); +} + +void LLParcel::completeSale(U32& type, U8& flags, + LLUUID& to_id) +{ + mSaleTimerExpires.setTimerExpirySec(0.0); + mSaleTimerExpires.stop(); + mStatus = OS_LEASED; + type = TRANS_LAND_SALE; + flags = pack_transaction_flags(mGroupOwned, mGroupOwned); + to_id = mOwnerID; + mAuthBuyerID.setNull(); + mIsReservedForNewbie = FALSE; + + // Purchased parcels are assumed to no longer be for sale. + // Otherwise someone can snipe the sale. + setForSale(FALSE); + + // Turn off show directory, since it's a recurring fee that + // the buyer may not want. + setParcelFlag(PF_SHOW_DIRECTORY, FALSE); + + //should be cleared on sale. + mAccessList.clear(); + mBanList.clear(); + +} + +void LLParcel::clearSale() +{ + mSaleTimerExpires.setTimerExpirySec(0.0); + mSaleTimerExpires.stop(); + if(isPublic()) + { + mStatus = OS_NONE; + } + else + { + mStatus = OS_LEASED; + } + mAuthBuyerID.setNull(); + setForSale(FALSE); + setPreviousOwnerID(LLUUID::null); + setPreviouslyGroupOwned(FALSE); + setSellWithObjects(FALSE); + mIsReservedForNewbie = FALSE; +} + +BOOL LLParcel::isPublic() const +{ + return (mOwnerID.isNull()); +} + +BOOL LLParcel::isBuyerAuthorized(const LLUUID& buyer_id) const +{ + if(mAuthBuyerID.isNull()) + { + return TRUE; + } + return (mAuthBuyerID == buyer_id); +} + +void LLParcel::clearParcel() +{ + overrideParcelFlags(PF_DEFAULT); + setName(NULL); + setDesc(NULL); + setMusicURL(NULL); + setMediaURL(NULL); + setMediaID(LLUUID::null); + setMediaAutoScale(0); + setInEscrow(FALSE); + setAuthorizedBuyerID(LLUUID::null); + setCategory(C_NONE); + setSnapshotID(LLUUID::null); + setUserLocation(LLVector3::zero); + setUserLookAt(LLVector3::x_axis); + setLandingType(L_LANDING_POINT); + setAuctionID(0); + setReservedForNewbie(FALSE); + setGroupID(LLUUID::null); + setPassPrice(0); + setPassHours(0.f); + mAccessList.clear(); + mBanList.clear(); + //mRenterList.reset(); +} + +void LLParcel::dump() +{ + llinfos << "parcel " << mLocalID << " area " << mArea << llendl; + llinfos << " name <" << mName << ">" << llendl; + llinfos << " desc <" << mDesc << ">" << llendl; +} + +const char* ownership_status_to_string(LLParcel::EOwnershipStatus status) +{ + if(status >= 0 && status < LLParcel::OS_COUNT) + { + return PARCEL_OWNERSHIP_STATUS_STRING[status]; + } + return "none"; +} + +LLParcel::EOwnershipStatus ownership_string_to_status(const char* s) +{ + for(S32 i = 0; i < LLParcel::OS_COUNT; ++i) + { + if(0 == strcmp(s, PARCEL_OWNERSHIP_STATUS_STRING[i])) + { + return (LLParcel::EOwnershipStatus)i; + } + } + return LLParcel::OS_NONE; +} + +//const char* revert_action_to_string(LLParcel::ESaleTimerExpireAction action) +//{ +// S32 index = 0; +// if(action >= 0 && action < LLParcel::STEA_COUNT) +// { +// index = action; +// } +// return PARCEL_SALE_TIMER_ACTION[index]; +//} + +//LLParcel::ESaleTimerExpireAction revert_string_to_action(const char* s) +//{ +// for(S32 i = 0; i < LLParcel::STEA_COUNT; ++i) +// { +// if(0 == strcmp(s, PARCEL_SALE_TIMER_ACTION[i])) +// { +// return (LLParcel::ESaleTimerExpireAction)i; +// } +// } +// return LLParcel::STEA_REVERT; +//} + +const char* category_to_string(LLParcel::ECategory category) +{ + S32 index = 0; + if((category >= 0) && (category < LLParcel::C_COUNT)) + { + index = category; + } + return PARCEL_CATEGORY_STRING[index]; +} + +const char* category_to_ui_string(LLParcel::ECategory category) +{ + S32 index = 0; + if((category >= 0) && (category < LLParcel::C_COUNT)) + { + index = category; + } + else + { + // C_ANY = -1 , but the "Any" string is at the end of the list + index = ((S32) LLParcel::C_COUNT) + 1; + } + return PARCEL_CATEGORY_UI_STRING[index]; +} + +LLParcel::ECategory category_string_to_category(const char* s) +{ + for(S32 i = 0; i < LLParcel::C_COUNT; ++i) + { + if(0 == strcmp(s, PARCEL_CATEGORY_STRING[i])) + { + return (LLParcel::ECategory)i; + } + } + llwarns << "Parcel category outside of possibilities " << s << llendl; + return LLParcel::C_NONE; +} + +LLParcel::ECategory category_ui_string_to_category(const char* s) +{ + for(S32 i = 0; i < LLParcel::C_COUNT; ++i) + { + if(0 == strcmp(s, PARCEL_CATEGORY_UI_STRING[i])) + { + return (LLParcel::ECategory)i; + } + } + // "Any" is a valid category for searches, and + // is a distinct option from "None" and "Other" + return LLParcel::C_ANY; +} + diff --git a/indra/llinventory/llparcel.h b/indra/llinventory/llparcel.h new file mode 100644 index 0000000000..16d3cb01e5 --- /dev/null +++ b/indra/llinventory/llparcel.h @@ -0,0 +1,576 @@ +/** + * @file llparcel.h + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLPARCEL_H +#define LL_LLPARCEL_H + +#include +#include + +#include "lldarray.h" +#include "lluuid.h" +#include "llparcelflags.h" +#include "llpermissions.h" +#include "v3math.h" + + +// Grid out of which parcels taken is stepped every 4 meters. +const F32 PARCEL_GRID_STEP_METERS = 4.f; + +// Area of one "square" of parcel +const S32 PARCEL_UNIT_AREA = 16; + +// Height _above_ground_ that parcel boundary ends +const F32 PARCEL_HEIGHT = 50.f; + +//Height above ground which parcel boundries exist for explicitly banned avatars +const F32 BAN_HEIGHT = 768.f; + +// Maximum number of entries in an access list +const S32 PARCEL_MAX_ACCESS_LIST = 300; +//Maximum number of entires in an update packet +//for access/ban lists. +const F32 PARCEL_MAX_ENTRIES_PER_PACKET = 48.f; + +// Weekly charge for listing a parcel in the directory +const S32 PARCEL_DIRECTORY_FEE = 30; + +const S32 PARCEL_PASS_PRICE_DEFAULT = 10; +const F32 PARCEL_PASS_HOURS_DEFAULT = 1.f; + +// Number of "chunks" in which parcel overlay data is sent +// Chunk 0 = southern rows, entire width +const S32 PARCEL_OVERLAY_CHUNKS = 4; + +// Bottom three bits are a color index for the land overlay +const U8 PARCEL_COLOR_MASK = 0x07; +const U8 PARCEL_PUBLIC = 0x00; +const U8 PARCEL_OWNED = 0x01; +const U8 PARCEL_GROUP = 0x02; +const U8 PARCEL_SELF = 0x03; +const U8 PARCEL_FOR_SALE = 0x04; +const U8 PARCEL_AUCTION = 0x05; +// unused 0x06 +// unused 0x07 +// flag, unused 0x08 +// flag, unused 0x10 +const U8 PARCEL_SOUND_LOCAL = 0x20; +const U8 PARCEL_WEST_LINE = 0x40; // flag, property line on west edge +const U8 PARCEL_SOUTH_LINE = 0x80; // flag, property line on south edge + +// Transmission results for parcel properties +const S32 PARCEL_RESULT_NO_DATA = -1; +const S32 PARCEL_RESULT_SUCCESS = 0; // got exactly one parcel +const S32 PARCEL_RESULT_MULTIPLE = 1; // got multiple parcels + +const S32 SELECTED_PARCEL_SEQ_ID = -10000; +const S32 COLLISION_NOT_IN_GROUP_PARCEL_SEQ_ID = -20000; +const S32 COLLISION_BANNED_PARCEL_SEQ_ID = -30000; +const S32 COLLISION_NOT_ON_LIST_PARCEL_SEQ_ID = -40000; +const S32 HOVERED_PARCEL_SEQ_ID = -50000; + +const U32 RT_NONE = 0x1 << 0; +const U32 RT_OWNER = 0x1 << 1; +const U32 RT_GROUP = 0x1 << 2; +const U32 RT_OTHER = 0x1 << 3; +const U32 RT_LIST = 0x1 << 4; +const U32 RT_SELL = 0x1 << 5; + +class LLMessageSystem; + +class LLAccessEntry +{ +public: + LLUUID mID; + S32 mTime; + U32 mFlags; +}; + +typedef std::map::iterator access_map_iterator; +typedef std::map::const_iterator access_map_const_iterator; + +class LLParcel +{ +public: + enum EOwnershipStatus + { + OS_LEASED = 0, + OS_LEASE_PENDING = 1, + OS_ABANDONED = 2, + OS_COUNT = 3, + OS_NONE = -1 + }; + enum ECategory + { + C_NONE = 0, + C_LINDEN, + C_ADULT, + C_ARTS, // "arts & culture" + C_BUSINESS, // was "store" + C_EDUCATIONAL, + C_GAMING, // was "game" + C_HANGOUT, // was "gathering place" + C_NEWCOMER, + C_PARK, // "parks & nature" + C_RESIDENTIAL, // was "homestead" + C_SHOPPING, + C_STAGE, + C_OTHER, + C_COUNT, + C_ANY = -1 // only useful in queries + }; + enum EAction + { + A_CREATE = 0, + A_RELEASE = 1, + A_ABSORB = 2, + A_ABSORBED = 3, + A_DIVIDE = 4, + A_DIVISION = 5, + A_ACQUIRE = 6, + A_RELINQUISH = 7, + A_CONFIRM = 8, + A_COUNT = 9, + A_UNKNOWN = -1 + }; + + enum ELandingType + { + L_NONE = 0, + L_LANDING_POINT = 1, + L_DIRECT = 2 + }; + + // CREATORS + LLParcel(); + LLParcel( const LLUUID &owner_id, + BOOL modify, BOOL terraform, BOOL damage, + time_t claim_date, S32 claim_price, S32 rent_price, S32 area, S32 sim_object_limit, F32 parcel_object_bonus, + BOOL is_group_owned = FALSE); + virtual ~LLParcel(); + + void init( const LLUUID &owner_id, + BOOL modify, BOOL terraform, BOOL damage, + time_t claim_date, S32 claim_price, S32 rent_price, + S32 area, S32 sim_object_limit, F32 parcel_object_bonus, BOOL is_group_owned = FALSE); + + // TODO: make an actual copy constructor for this + void overrideParcelFlags(U32 flags); + // if you specify an agent id here, the group id will be zeroed + void overrideOwner(const LLUUID& owner_id, BOOL is_group_owned = FALSE); + void overrideSaleTimerExpires(F32 secs_left) { mSaleTimerExpires.setTimerExpirySec(secs_left); } + + // MANIPULATORS + void generateNewID() { mID.generate(); } + void setName(const char* name); + void setDesc(const char* desc); + void setMusicURL(const char* url); + void setMediaURL(const char* url); + void setMediaID(const LLUUID& id) { mMediaID = id; } + void setMediaAutoScale ( U8 flagIn ) { mMediaAutoScale = flagIn; } + virtual void setLocalID(S32 local_id); + + // blow away all the extra crap lurking in parcels, including urls, access lists, etc + void clearParcel(); + + // This value is not persisted out to the parcel file, it is only + // a per-process blocker for attempts to purchase. + void setInEscrow(bool in_escrow) { mInEscrow = in_escrow; } + + void setAuthorizedBuyerID(const LLUUID& id) { mAuthBuyerID = id; } + //void overrideBuyerID(const LLUUID& id) { mBuyerID = id; } + void setCategory(ECategory category) { mCategory = category; } + void setSnapshotID(const LLUUID& id) { mSnapshotID = id; } + void setUserLocation(const LLVector3& pos) { mUserLocation = pos; } + void setUserLookAt(const LLVector3& rot) { mUserLookAt = rot; } + void setLandingType(const ELandingType type) { mLandingType = type; } + + void setAuctionID(U32 auction_id) { mAuctionID = auction_id;} + void setReservedForNewbie(BOOL reserve) { mIsReservedForNewbie = reserve; } + + void setAllParcelFlags(U32 flags) { mParcelFlags = flags; } + void setParcelFlag(U32 flag, BOOL b); + + void setArea(S32 area, S32 sim_object_limit); + void setDiscountRate(F32 rate); + + void setAllowModify(BOOL b) { setParcelFlag(PF_CREATE_OBJECTS, b); } + void setAllowGroupModify(BOOL b) { setParcelFlag(PF_CREATE_GROUP_OBJECTS, b); } + void setAllowAllObjectEntry(BOOL b) { setParcelFlag(PF_ALLOW_ALL_OBJECT_ENTRY, b); } + void setAllowGroupObjectEntry(BOOL b) { setParcelFlag(PF_ALLOW_GROUP_OBJECT_ENTRY, b); } + void setAllowTerraform(BOOL b){setParcelFlag(PF_ALLOW_TERRAFORM, b); } + void setAllowDamage(BOOL b) { setParcelFlag(PF_ALLOW_DAMAGE, b); } + void setAllowFly(BOOL b) { setParcelFlag(PF_ALLOW_FLY, b); } + void setAllowLandmark(BOOL b){ setParcelFlag(PF_ALLOW_LANDMARK, b); } + void setAllowGroupScripts(BOOL b) { setParcelFlag(PF_ALLOW_GROUP_SCRIPTS, b); } + void setAllowOtherScripts(BOOL b) { setParcelFlag(PF_ALLOW_OTHER_SCRIPTS, b); } + void setAllowDeedToGroup(BOOL b) { setParcelFlag(PF_ALLOW_DEED_TO_GROUP, b); } + void setContributeWithDeed(BOOL b) { setParcelFlag(PF_CONTRIBUTE_WITH_DEED, b); } + void setForSale(BOOL b) { setParcelFlag(PF_FOR_SALE, b); } + void setSoundOnly(BOOL b) { setParcelFlag(PF_SOUND_LOCAL, b); } + void setDenyAnonymous(BOOL b) { setParcelFlag(PF_DENY_ANONYMOUS, b); } + void setDenyIdentified(BOOL b) { setParcelFlag(PF_DENY_IDENTIFIED, b); } + void setDenyTransacted(BOOL b) { setParcelFlag(PF_DENY_TRANSACTED, b); } + void setRestrictPushObject(BOOL b) { setParcelFlag(PF_RESTRICT_PUSHOBJECT, b); } + + void setDrawDistance(F32 dist) { mDrawDistance = dist; } + void setSalePrice(S32 price) { mSalePrice = price; } + void setGroupID(const LLUUID& id) { mGroupID = id; } + //void setGroupName(const char* s) { mGroupName.assign(s); } + void setPassPrice(S32 price) { mPassPrice = price; } + void setPassHours(F32 hours) { mPassHours = hours; } + + BOOL importStream(std::istream& input_stream); + BOOL importAccessEntry(std::istream& input_stream, LLAccessEntry* entry); + BOOL exportStream(std::ostream& output_stream); + + void packMessage(LLMessageSystem* msg); + void unpackMessage(LLMessageSystem* msg); + + void packAccessEntries(LLMessageSystem* msg, + const std::map& list); + void unpackAccessEntries(LLMessageSystem* msg, + std::map* list); + + void setAABBMin(const LLVector3& min) { mAABBMin = min; } + void setAABBMax(const LLVector3& max) { mAABBMax = max; } + + // Extend AABB to include rectangle from min to max. + void extendAABB(const LLVector3& box_min, const LLVector3& box_max); + + void dump(); + + // Scans the pass list and removes any items with an expiration + // time earlier than "now". + void expirePasses(S32 now); + + // Add to list, suppressing duplicates. Returns TRUE if added. + BOOL addToAccessList(const LLUUID& agent_id, S32 time); + BOOL addToBanList(const LLUUID& agent_id, S32 time); + BOOL removeFromAccessList(const LLUUID& agent_id); + BOOL removeFromBanList(const LLUUID& agent_id); + + // ACCESSORS + const LLUUID& getID() { return mID; } + const char* getName() const { return mName.c_str(); } + const char* getDesc() const { return mDesc.c_str(); } + const char* getMusicURL() const { return mMusicURL.c_str(); } + const char* getMediaURL() const { return mMediaURL.c_str(); } + const LLUUID& getMediaID() const { return mMediaID; } + const U8 getMediaAutoScale() const { return mMediaAutoScale; } + S32 getLocalID() const { return mLocalID; } + const LLUUID& getOwnerID() const { return mOwnerID; } + const LLUUID& getGroupID() const { return mGroupID; } + //const char* getGroupName() const { return mGroupName.c_str(); } + S32 getPassPrice() const { return mPassPrice; } + F32 getPassHours() const { return mPassHours; } + BOOL getIsGroupOwned() const { return mGroupOwned; } + + U32 getAuctionID() { return mAuctionID; } + BOOL getReservedForNewbie() { return mIsReservedForNewbie; } + bool isInEscrow() const { return mInEscrow; } + + BOOL isPublic() const; + + // Region-local user-specified position + const LLVector3& getUserLocation() const { return mUserLocation; } + const LLVector3& getUserLookAt() const { return mUserLookAt; } + ELandingType getLandingType() const { return mLandingType; } + + // User-specified snapshot + const LLUUID& getSnapshotID() const { return mSnapshotID; } + + // the authorized buyer id is the person who is the only + // agent/group that has authority to purchase. (ie, ui specified a + // particular agent could buy the plot). + const LLUUID& getAuthorizedBuyerID() const { return mAuthBuyerID; } + + // helper function + BOOL isBuyerAuthorized(const LLUUID& buyer_id) const; + + // The buyer of a plot is set when someone indicates they want to + // buy the plot, and the system is simply waiting for tier-up + // approval + //const LLUUID& getBuyerID() const { return mBuyerID; } + + // functions to deal with ownership status. + EOwnershipStatus getOwnershipStatus() const { return mStatus; } + static const char* getOwnershipStatusString(EOwnershipStatus status); + void setOwnershipStatus(EOwnershipStatus status) { mStatus = status; } + + // dealing with parcel category information + ECategory getCategory() const {return mCategory; } + static const char* getCategoryString(ECategory category); + static const char* getCategoryUIString(ECategory category); + static ECategory getCategoryFromString(const char* string); + static ECategory getCategoryFromUIString(const char* string); + + // functions for parcel action (used for logging) + static const char* getActionString(EAction action); + + // dealing with sales and parcel conversion. + // + // the isSaleTimerExpired will trivially return FALSE if there is + // no sale going on. Pass in the current time in usec which will + // be used for comparison. + BOOL isSaleTimerExpired(const U64& time); + + F32 getSaleTimerExpires() { return mSaleTimerExpires.getRemainingTimeF32(); } + + // should the parcel join on complete? + //U32 getJoinNeighbors() const { return mJoinNeighbors; } + + // need to record a few things with the parcel when a sale + // starts. + void startSale(const LLUUID& buyer_id, BOOL is_buyer_group); + + // do the expiration logic, which needs to return values usable in + // a money transaction. + void expireSale(U32& type, U8& flags, LLUUID& from_id, LLUUID& to_id); + void completeSale(U32& type, U8& flags, LLUUID& to_id); + void clearSale(); + + // this function returns TRUE if the parcel needs conversion to a + // lease from a non-owned-status state. + BOOL getRecordTransaction() const { return mRecordTransaction; } + void setRecordTransaction(BOOL record) { mRecordTransaction = record; } + + + // more accessors + U32 getParcelFlags() const { return mParcelFlags; } + + BOOL getParcelFlag(U32 flag) const + { return (mParcelFlags & flag) ? TRUE : FALSE; } + + // objects can be added or modified by anyone (only parcel owner if disabled) + BOOL getAllowModify() const + { return (mParcelFlags & PF_CREATE_OBJECTS) ? TRUE : FALSE; } + + // objects can be added or modified by group members + BOOL getAllowGroupModify() const + { return (mParcelFlags & PF_CREATE_GROUP_OBJECTS) ? TRUE : FALSE; } + + // the parcel can be deeded to the group + BOOL getAllowDeedToGroup() const + { return (mParcelFlags & PF_ALLOW_DEED_TO_GROUP) ? TRUE : FALSE; } + + // Does the owner want to make a contribution along with the deed. + BOOL getContributeWithDeed() const + { return (mParcelFlags & PF_CONTRIBUTE_WITH_DEED) ? TRUE : FALSE; } + + // heightfield can be modified + BOOL getAllowTerraform() const + { return (mParcelFlags & PF_ALLOW_TERRAFORM) ? TRUE : FALSE; } + + // avatars can be hurt here + BOOL getAllowDamage() const + { return (mParcelFlags & PF_ALLOW_DAMAGE) ? TRUE : FALSE; } + + BOOL getAllowFly() const + { return (mParcelFlags & PF_ALLOW_FLY) ? TRUE : FALSE; } + + BOOL getAllowLandmark() const + { return (mParcelFlags & PF_ALLOW_LANDMARK) ? TRUE : FALSE; } + + BOOL getAllowGroupScripts() const + { return (mParcelFlags & PF_ALLOW_GROUP_SCRIPTS) ? TRUE : FALSE; } + + BOOL getAllowOtherScripts() const + { return (mParcelFlags & PF_ALLOW_OTHER_SCRIPTS) ? TRUE : FALSE; } + + BOOL getAllowAllObjectEntry() const + { return (mParcelFlags & PF_ALLOW_ALL_OBJECT_ENTRY) ? TRUE : FALSE; } + + BOOL getAllowGroupObjectEntry() const + { return (mParcelFlags & PF_ALLOW_GROUP_OBJECT_ENTRY) ? TRUE : FALSE; } + + BOOL getForSale() const + { return (mParcelFlags & PF_FOR_SALE) ? TRUE : FALSE; } + BOOL getSoundLocal() const + { return (mParcelFlags & PF_SOUND_LOCAL) ? TRUE : FALSE; } + BOOL getAllowPublish() const + { return (mParcelFlags & PF_ALLOW_PUBLISH) ? TRUE : FALSE; } + BOOL getMaturePublish() const + { return (mParcelFlags & PF_MATURE_PUBLISH) ? TRUE : FALSE; } + BOOL getRestrictPushObject() const + { return (mParcelFlags & PF_RESTRICT_PUSHOBJECT) ? TRUE : FALSE; } + BOOL getRegionPushOverride() const + { return mRegionPushOverride; } + BOOL getRegionDenyAnonymousOverride() const + { return mRegionDenyAnonymousOverride; } + BOOL getRegionDenyIdentifiedOverride() const + { return mRegionDenyIdentifiedOverride; } + BOOL getRegionDenyTransactedOverride() const + { return mRegionDenyTransactedOverride; } + + F32 getDrawDistance() const { return mDrawDistance; } + S32 getSalePrice() const { return mSalePrice; } + time_t getClaimDate() const { return mClaimDate; } + S32 getClaimPricePerMeter() const { return mClaimPricePerMeter; } + S32 getRentPricePerMeter() const { return mRentPricePerMeter; } + + // Area is NOT automatically calculated. You must calculate it + // and store it with setArea. + S32 getArea() const { return mArea; } + + // deprecated 12/11/2003 + //F32 getDiscountRate() const { return mDiscountRate; } + + S32 getClaimPrice() const { return mClaimPricePerMeter * mArea; } + + // Can this agent create objects here? + BOOL allowModifyBy(const LLUUID &agent_id, const LLUUID &group_id) const; + + // Can this agent change the shape of the land? + BOOL allowTerraformBy(const LLUUID &agent_id) const; + + // Returns 0 if access is OK, otherwise a BA_ return code above. + S32 blockAccess(const LLUUID& agent_id, const LLUUID& group_id, const BOOL is_agent_identified, const BOOL is_agent_transacted) const; + + // Only checks if the agent is explicitly banned from this parcel + BOOL isAgentBanned(const LLUUID& agent_id) const; + + static bool isAgentBlockedFromParcel(LLParcel* parcelp, + const LLUUID& agent_id, + const std::vector& group_ids, + const BOOL is_agent_identified, + const BOOL is_agent_transacted); + + bool operator==(const LLParcel &rhs) const; + + // Calculate rent - area * rent * discount rate + S32 getTotalRent() const; + F32 getAdjustedRentPerMeter() const; + + const LLVector3& getAABBMin() const { return mAABBMin; } + const LLVector3& getAABBMax() const { return mAABBMax; } + LLVector3 getCenterpoint() const; + + // simwide + S32 getSimWideMaxPrimCapacity() const { return mSimWideMaxPrimCapacity; } + S32 getSimWidePrimCount() const { return mSimWidePrimCount; } + + // this parcel only (not simwide) + S32 getMaxPrimCapacity() const { return mMaxPrimCapacity; } + S32 getPrimCount() const { return mOwnerPrimCount + mGroupPrimCount + mOtherPrimCount + mSelectedPrimCount; } + S32 getOwnerPrimCount() const { return mOwnerPrimCount; } + S32 getGroupPrimCount() const { return mGroupPrimCount; } + S32 getOtherPrimCount() const { return mOtherPrimCount; } + S32 getSelectedPrimCount() const{ return mSelectedPrimCount; } + S32 getTempPrimCount() const { return mTempPrimCount; } + F32 getParcelPrimBonus() const { return mParcelPrimBonus; } + + S32 getCleanOtherTime() const { return mCleanOtherTime; } + + void setMaxPrimCapacity(S32 max) { mMaxPrimCapacity = max; } + // simwide + void setSimWideMaxPrimCapacity(S32 current) { mSimWideMaxPrimCapacity = current; } + void setSimWidePrimCount(S32 current) { mSimWidePrimCount = current; } + + // this parcel only (not simwide) + void setOwnerPrimCount(S32 current) { mOwnerPrimCount = current; } + void setGroupPrimCount(S32 current) { mGroupPrimCount = current; } + void setOtherPrimCount(S32 current) { mOtherPrimCount = current; } + void setSelectedPrimCount(S32 current) { mSelectedPrimCount = current; } + void setTempPrimCount(S32 current) { mTempPrimCount = current; } + void setParcelPrimBonus(F32 bonus) { mParcelPrimBonus = bonus; } + + void setCleanOtherTime(S32 time) { mCleanOtherTime = time; } + void setRegionPushOverride(BOOL override) {mRegionPushOverride = override; } + void setRegionDenyAnonymousOverride(BOOL override) { mRegionDenyAnonymousOverride = override; } + void setRegionDenyIdentifiedOverride(BOOL override) { mRegionDenyIdentifiedOverride = override; } + void setRegionDenyTransactedOverride(BOOL override) { mRegionDenyTransactedOverride = override; } + + // Accessors for parcel sellWithObjects + void setPreviousOwnerID(LLUUID prev_owner) { mPreviousOwnerID = prev_owner; } + void setPreviouslyGroupOwned(BOOL b) { mPreviouslyGroupOwned = b; } + void setSellWithObjects(BOOL b) { setParcelFlag(PF_SELL_PARCEL_OBJECTS, b); } + + LLUUID getPreviousOwnerID() const { return mPreviousOwnerID; } + BOOL getPreviouslyGroupOwned() const { return mPreviouslyGroupOwned; } + BOOL getSellWithObjects() const { return (mParcelFlags & PF_SELL_PARCEL_OBJECTS) ? TRUE : FALSE; } + +protected: + LLUUID mID; + LLUUID mOwnerID; + LLUUID mGroupID; + BOOL mGroupOwned; // TRUE if mOwnerID is a group_id + LLUUID mPreviousOwnerID; + BOOL mPreviouslyGroupOwned; + + EOwnershipStatus mStatus; + ECategory mCategory; + LLUUID mAuthBuyerID; + LLUUID mSnapshotID; + LLVector3 mUserLocation; + LLVector3 mUserLookAt; + ELandingType mLandingType; + LLTimer mSaleTimerExpires; + S32 mGraceExtension; + BOOL mRecordTransaction; + + + // This value is non-zero if there is an auction associated with + // the parcel. + U32 mAuctionID; + + // This value is TRUE if the land is reserved for a newbie. + BOOL mIsReservedForNewbie; + + // value used to temporarily lock attempts to purchase the parcel. + bool mInEscrow; + + time_t mClaimDate; // UTC Unix-format time + S32 mClaimPricePerMeter; // meter squared + S32 mRentPricePerMeter; // meter squared + S32 mArea; // meter squared + F32 mDiscountRate; // 0.0-1.0 + F32 mDrawDistance; + U32 mParcelFlags; + S32 mSalePrice; // linden dollars + std::string mName; + std::string mDesc; + std::string mMusicURL; + std::string mMediaURL; + U8 mMediaAutoScale; + LLUUID mMediaID; + S32 mPassPrice; + F32 mPassHours; + LLVector3 mAABBMin; + LLVector3 mAABBMax; + S32 mMaxPrimCapacity; + S32 mSimWidePrimCount; + S32 mSimWideMaxPrimCapacity; + //S32 mSimWidePrimCorrection; + S32 mOwnerPrimCount; + S32 mGroupPrimCount; + S32 mOtherPrimCount; + S32 mSelectedPrimCount; + S32 mTempPrimCount; + F32 mParcelPrimBonus; + S32 mCleanOtherTime; + BOOL mRegionPushOverride; + BOOL mRegionDenyAnonymousOverride; + BOOL mRegionDenyIdentifiedOverride; + BOOL mRegionDenyTransactedOverride; + + +public: + // HACK, make private + S32 mLocalID; + LLUUID mBanListTransactionID; + LLUUID mAccessListTransactionID; + std::map mAccessList; + std::map mBanList; + std::map mTempBanList; + std::map mTempAccessList; + + //LLDynamicArray mRenterList; +}; + + +#endif diff --git a/indra/llinventory/llparcelflags.h b/indra/llinventory/llparcelflags.h new file mode 100644 index 0000000000..43571abe77 --- /dev/null +++ b/indra/llinventory/llparcelflags.h @@ -0,0 +1,105 @@ +/** + * @file llparcelflags.h + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLPARCEL_FLAGS_H +#define LL_LLPARCEL_FLAGS_H + +//--------------------------------------------------------------------------- +// Parcel Flags (PF) constants +//--------------------------------------------------------------------------- +const U32 PF_ALLOW_FLY = 1 << 0;// Can start flying +const U32 PF_ALLOW_OTHER_SCRIPTS= 1 << 1;// Scripts by others can run. +const U32 PF_FOR_SALE = 1 << 2;// Can buy this land +const U32 PF_FOR_SALE_OBJECTS = 1 << 7;// Can buy all objects on this land +const U32 PF_ALLOW_LANDMARK = 1 << 3; +const U32 PF_ALLOW_TERRAFORM = 1 << 4; +const U32 PF_ALLOW_DAMAGE = 1 << 5; +const U32 PF_CREATE_OBJECTS = 1 << 6; +// 7 is moved above +const U32 PF_USE_ACCESS_GROUP = 1 << 8; +const U32 PF_USE_ACCESS_LIST = 1 << 9; +const U32 PF_USE_BAN_LIST = 1 << 10; +const U32 PF_USE_PASS_LIST = 1 << 11; +const U32 PF_SHOW_DIRECTORY = 1 << 12; +const U32 PF_ALLOW_DEED_TO_GROUP = 1 << 13; +const U32 PF_CONTRIBUTE_WITH_DEED = 1 << 14; +const U32 PF_SOUND_LOCAL = 1 << 15; // Hear sounds in this parcel only +const U32 PF_SELL_PARCEL_OBJECTS = 1 << 16; // Objects on land are included as part of the land when the land is sold +const U32 PF_ALLOW_PUBLISH = 1 << 17; // Allow publishing of parcel information on the web +const U32 PF_MATURE_PUBLISH = 1 << 18; // The information on this parcel is mature +const U32 PF_URL_WEB_PAGE = 1 << 19; // The "media URL" is an HTML page +const U32 PF_URL_RAW_HTML = 1 << 20; // The "media URL" is a raw HTML string like

Foo

+const U32 PF_RESTRICT_PUSHOBJECT = 1 << 21; // Restrict push object to either on agent or on scripts owned by parcel owner +const U32 PF_DENY_ANONYMOUS = 1 << 22; // Deny all non identified/transacted accounts +const U32 PF_DENY_IDENTIFIED = 1 << 23; // Deny identified accounts +const U32 PF_DENY_TRANSACTED = 1 << 24; // Deny identified accounts +const U32 PF_ALLOW_GROUP_SCRIPTS = 1 << 25; // Allow scripts owned by group +const U32 PF_CREATE_GROUP_OBJECTS = 1 << 26; // Allow object creation by group members or objects +const U32 PF_ALLOW_ALL_OBJECT_ENTRY = 1 << 27; // Allow all objects to enter a parcel +const U32 PF_ALLOW_GROUP_OBJECT_ENTRY = 1 << 28; // Only allow group (and owner) objects to enter the parcel + + +const U32 PF_RESERVED = 1 << 31; + +// If any of these are true the parcel is restricting access in some maner. +const U32 PF_USE_RESTRICTED_ACCESS = PF_USE_ACCESS_GROUP + | PF_USE_ACCESS_LIST + | PF_USE_BAN_LIST + | PF_USE_PASS_LIST + | PF_DENY_ANONYMOUS + | PF_DENY_IDENTIFIED + | PF_DENY_TRANSACTED; +const U32 PF_NONE = 0x00000000; +const U32 PF_ALL = 0x7FFFFFFF; +const U32 PF_DEFAULT = PF_ALLOW_FLY + | PF_ALLOW_OTHER_SCRIPTS + | PF_ALLOW_GROUP_SCRIPTS + | PF_ALLOW_LANDMARK + | PF_CREATE_OBJECTS + | PF_CREATE_GROUP_OBJECTS + | PF_USE_BAN_LIST + | PF_ALLOW_ALL_OBJECT_ENTRY + | PF_ALLOW_GROUP_OBJECT_ENTRY; + +// Access list flags +const U32 AL_ACCESS = (1 << 0); +const U32 AL_BAN = (1 << 1); +//const U32 AL_RENTER = (1 << 2); + +// Block access return values. BA_ALLOWED is the only success case +// since some code in the simulator relies on that assumption. All +// other BA_ values should be reasons why you are not allowed. +const S32 BA_ALLOWED = 0; +const S32 BA_NOT_IN_GROUP = 1; +const S32 BA_NOT_ON_LIST = 2; +const S32 BA_BANNED = 3; +const S32 BA_NO_ACCESS_LEVEL = 4; + +// ParcelRelease flags +const U32 PR_NONE = 0x0; +const U32 PR_GOD_FORCE = (1 << 0); + +enum EObjectCategory +{ + OC_INVALID = -1, + OC_NONE = 0, + OC_TOTAL = 0, // yes zero, like OC_NONE + OC_OWNER, + OC_GROUP, + OC_OTHER, + OC_SELECTED, + OC_TEMP, + OC_COUNT +}; + +const S32 PARCEL_DETAILS_NAME = 0; +const S32 PARCEL_DETAILS_DESC = 1; +const S32 PARCEL_DETAILS_OWNER = 2; +const S32 PARCEL_DETAILS_GROUP = 3; +const S32 PARCEL_DETAILS_AREA = 4; + +#endif diff --git a/indra/llinventory/llpermissions.cpp b/indra/llinventory/llpermissions.cpp new file mode 100644 index 0000000000..2063ac33d6 --- /dev/null +++ b/indra/llinventory/llpermissions.cpp @@ -0,0 +1,1171 @@ +/** + * @file llpermissions.cpp + * @author Phoenix + * @brief Permissions for objects and inventory. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llpermissions.h" + +// library includes +#include "message.h" +#include "metapropertyt.h" + +///---------------------------------------------------------------------------- +/// Class LLPermissions +///---------------------------------------------------------------------------- + +const LLPermissions LLPermissions::DEFAULT; + +// No creator = created by system +LLPermissions::LLPermissions() +{ + init(LLUUID::null, LLUUID::null, LLUUID::null, LLUUID::null); +} + + +// Default to created by system +void LLPermissions::init(const LLUUID& creator, const LLUUID& owner, const LLUUID& last_owner, const LLUUID& group) +{ + mCreator = creator; + mOwner = owner; + mLastOwner = last_owner; + mGroup = group; + + mMaskBase = PERM_ALL; + mMaskOwner = PERM_ALL; + mMaskEveryone = PERM_ALL; + mMaskGroup = PERM_ALL; + mMaskNextOwner = PERM_ALL; + fixOwnership(); +} + + +void LLPermissions::initMasks(PermissionMask base, PermissionMask owner, + PermissionMask everyone, PermissionMask group, + PermissionMask next) +{ + mMaskBase = base; + mMaskOwner = owner; + mMaskEveryone = everyone; + mMaskGroup = group; + mMaskNextOwner = next; + fixFairUse(); + fix(); +} + +BOOL LLPermissions::getOwnership(LLUUID& owner_id, BOOL& is_group_owned) const +{ + if(mOwner.notNull()) + { + owner_id = mOwner; + is_group_owned = FALSE; + return TRUE; + } + else if(mIsGroupOwned) + { + owner_id = mGroup; + is_group_owned = TRUE; + return TRUE; + } + return FALSE; +} + +LLUUID LLPermissions::getSafeOwner() const +{ + if(mOwner.notNull()) + { + return mOwner; + } + else if(mIsGroupOwned) + { + return mGroup; + } + else + { + llwarns << "LLPermissions::getSafeOwner() called with no valid owner!" << llendl; + LLUUID unused_uuid; + unused_uuid.generate(); + + return unused_uuid; + } +} + +U32 LLPermissions::getCRC32() const +{ + U32 rv = mCreator.getCRC32(); + rv += mOwner.getCRC32(); + rv += mLastOwner.getCRC32(); + rv += mGroup.getCRC32(); + rv += mMaskBase + mMaskOwner + mMaskEveryone + mMaskGroup; + return rv; +} + +void LLPermissions::set(const LLPermissions& from) +{ + mCreator = from.mCreator; + mOwner = from.mOwner; + mLastOwner = from.mLastOwner; + mGroup = from.mGroup; + + mMaskBase = from.mMaskBase; + mMaskOwner = from.mMaskOwner; + mMaskEveryone = from.mMaskEveryone; + mMaskGroup = from.mMaskGroup; + mMaskNextOwner = from.mMaskNextOwner; + mIsGroupOwned = from.mIsGroupOwned; +} + +// Fix hierarchy of permissions. +void LLPermissions::fix() +{ + mMaskOwner &= mMaskBase; + mMaskGroup &= mMaskOwner; + // next owner uses base, since you may want to sell locked objects. + mMaskNextOwner &= mMaskBase; + mMaskEveryone &= mMaskOwner; + mMaskEveryone &= ~PERM_MODIFY; + if(!(mMaskBase & PERM_TRANSFER) && !mIsGroupOwned) + { + mMaskGroup &= ~PERM_COPY; + mMaskEveryone &= ~PERM_COPY; + // Do not set mask next owner to too restrictive because if we + // rez an object, it may require an ownership transfer during + // rez, which will note the overly restrictive perms, and then + // fix them to allow fair use, which may be different than the + // original intention. + } +} + +// Correct for fair use - you can never take away the right to move +// stuff you own, and you can never take away the right to transfer +// something you cannot otherwise copy. +void LLPermissions::fixFairUse() +{ + mMaskBase |= PERM_MOVE; + if(!(mMaskBase & PERM_COPY)) + { + mMaskBase |= PERM_TRANSFER; + } + // (mask next owner == PERM_NONE) iff mask base is no transfer + if(mMaskNextOwner != PERM_NONE) + { + mMaskNextOwner |= PERM_MOVE; + } +} + +void LLPermissions::fixOwnership() +{ + if(mOwner.isNull() && mGroup.notNull()) + { + mIsGroupOwned = true; + } + else + { + mIsGroupOwned = false; + } +} + +// Allow accumulation of permissions. Results in the tightest +// permissions possible. In the case of clashing UUIDs, it sets the ID +// to LLUUID::null. +void LLPermissions::accumulate(const LLPermissions& perm) +{ + if(perm.mCreator != mCreator) + { + mCreator = LLUUID::null; + } + if(perm.mOwner != mOwner) + { + mOwner = LLUUID::null; + } + if(perm.mLastOwner != mLastOwner) + { + mLastOwner = LLUUID::null; + } + if(perm.mGroup != mGroup) + { + mGroup = LLUUID::null; + } + + mMaskBase &= perm.mMaskBase; + mMaskOwner &= perm.mMaskOwner; + mMaskGroup &= perm.mMaskGroup; + mMaskEveryone &= perm.mMaskEveryone; + mMaskNextOwner &= perm.mMaskNextOwner; + fix(); +} + +// saves last owner, sets current owner, and sets the group. note +// that this function has to more cleverly apply the fair use +// permissions. +BOOL LLPermissions::setOwnerAndGroup( + const LLUUID& agent, + const LLUUID& owner, + const LLUUID& group, + bool is_atomic) +{ + BOOL allowed = FALSE; + + if( agent.isNull() || mOwner.isNull() + || ((agent == mOwner) && ((owner == mOwner) || (mMaskOwner & PERM_TRANSFER)) ) ) + { + // ...system can alway set owner + // ...public objects can be claimed by anyone + // ...otherwise, agent must own it and have transfer ability + allowed = TRUE; + } + + if (allowed) + { + if(mLastOwner.isNull() || (!mOwner.isNull() && (owner != mLastOwner))) + { + mLastOwner = mOwner; + } + if((mOwner != owner) + || (mOwner.isNull() && owner.isNull() && (mGroup != group))) + { + mMaskBase = mMaskNextOwner; + mOwner = owner; + // this is a selective use of fair use for atomic + // permissions. + if(is_atomic && !(mMaskBase & PERM_COPY)) + { + mMaskBase |= PERM_TRANSFER; + } + } + mGroup = group; + fixOwnership(); + // if it's not atomic and we fix fair use, it blows away + //objects as inventory items which have different permissions + //than it's contents. :( + // fixFairUse(); + mMaskBase |= PERM_MOVE; + if(mMaskNextOwner != PERM_NONE) mMaskNextOwner |= PERM_MOVE; + fix(); + } + + return allowed; +} + +BOOL LLPermissions::deedToGroup(const LLUUID& agent, const LLUUID& group) +{ + if(group.notNull() && (agent.isNull() || ((group == mGroup) + && (mMaskOwner & PERM_TRANSFER) + && (mMaskGroup & PERM_MOVE)))) + { + if(mOwner.notNull()) + { + mLastOwner = mOwner; + mOwner.setNull(); + } + mMaskBase = mMaskNextOwner; + mGroup = group; + mIsGroupOwned = true; + fixFairUse(); + fix(); + return TRUE; + } + return FALSE; +} + +BOOL LLPermissions::setBaseBits(const LLUUID& agent, BOOL set, PermissionMask bits) +{ + BOOL ownership = FALSE; + if(agent.isNull()) + { + // only the system is always allowed to change base bits + ownership = TRUE; + } + + if (ownership) + { + if (set) + { + mMaskBase |= bits; // turn on bits + } + else + { + mMaskBase &= ~bits; // turn off bits + } + fix(); + } + + return ownership; +} + + +// Note: If you attempt to set bits that the base bits doesn't allow, +// the function will succeed, but those bits will not be set. +BOOL LLPermissions::setOwnerBits(const LLUUID& agent, BOOL set, PermissionMask bits) +{ + BOOL ownership = FALSE; + + if(agent.isNull()) + { + // ...system always allowed to change things + ownership = TRUE; + } + else if (agent == mOwner) + { + // ...owner bits can only be set by owner + ownership = TRUE; + } + + // If we have correct ownership and + if (ownership) + { + if (set) + { + mMaskOwner |= bits; // turn on bits + } + else + { + mMaskOwner &= ~bits; // turn off bits + } + fix(); + } + + return (ownership); +} + +BOOL LLPermissions::setGroupBits(const LLUUID& agent, const LLUUID& group, BOOL set, PermissionMask bits) +{ + BOOL ownership = FALSE; + if((agent.isNull()) || (agent == mOwner) + || ((group == mGroup) && (!mGroup.isNull()))) + { + // The group bits can be set by the system, the owner, or a + // group member. + ownership = TRUE; + } + + if (ownership) + { + if (set) + { + mMaskGroup |= bits; + } + else + { + mMaskGroup &= ~bits; + } + fix(); + } + return ownership; +} + + +// Note: If you attempt to set bits that the creator or owner doesn't allow, +// the function will succeed, but those bits will not be set. +BOOL LLPermissions::setEveryoneBits(const LLUUID& agent, const LLUUID& group, BOOL set, PermissionMask bits) +{ + BOOL ownership = FALSE; + if((agent.isNull()) || (agent == mOwner) + || ((group == mGroup) && (!mGroup.isNull()))) + { + // The everyone bits can be set by the system, the owner, or a + // group member. + ownership = TRUE; + } + if (ownership) + { + if (set) + { + mMaskEveryone |= bits; + } + else + { + mMaskEveryone &= ~bits; + } + + // Fix hierarchy of permissions + fix(); + } + return ownership; +} + +// Note: If you attempt to set bits that the creator or owner doesn't allow, +// the function will succeed, but those bits will not be set. +BOOL LLPermissions::setNextOwnerBits(const LLUUID& agent, const LLUUID& group, BOOL set, PermissionMask bits) +{ + BOOL ownership = FALSE; + if((agent.isNull()) || (agent == mOwner) + || ((group == mGroup) && (!mGroup.isNull()))) + { + // The next owner bits can be set by the system, the owner, or + // a group member. + ownership = TRUE; + } + if (ownership) + { + if (set) + { + mMaskNextOwner |= bits; + } + else + { + mMaskNextOwner &= ~bits; + } + + // Fix-up permissions + if(!(mMaskNextOwner & PERM_COPY)) + { + mMaskNextOwner |= PERM_TRANSFER; + } + fix(); + } + return ownership; +} + +BOOL LLPermissions::allowOperationBy(PermissionBit op, const LLUUID& requester, const LLUUID& group) const +{ + if(requester.isNull()) + { + // ...system making request + // ...not owned + return TRUE; + } + else if (mIsGroupOwned && (mGroup == requester)) + { + // group checking ownership permissions + return (mMaskOwner & op); + } + else if (!mIsGroupOwned && (mOwner == requester)) + { + // ...owner making request + return (mMaskOwner & op); + } + else if(mGroup.notNull() && (mGroup == group)) + { + // group member making request + return ((mMaskGroup & op) || (mMaskEveryone & op)); + } + return (mMaskEveryone & op); +} + +// +// Messaging support +// +void LLPermissions::packMessage(LLMessageSystem* msg) const +{ + msg->addUUIDFast(_PREHASH_CreatorID, mCreator); + msg->addUUIDFast(_PREHASH_OwnerID, mOwner); + msg->addUUIDFast(_PREHASH_GroupID, mGroup); + + msg->addU32Fast(_PREHASH_BaseMask, mMaskBase ); + msg->addU32Fast(_PREHASH_OwnerMask, mMaskOwner ); + msg->addU32Fast(_PREHASH_GroupMask, mMaskGroup ); + msg->addU32Fast(_PREHASH_EveryoneMask, mMaskEveryone ); + msg->addU32Fast(_PREHASH_NextOwnerMask, mMaskNextOwner ); + msg->addBOOLFast(_PREHASH_GroupOwned, (BOOL)mIsGroupOwned); +} + + +void LLPermissions::unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num) +{ + msg->getUUIDFast(block, _PREHASH_CreatorID, mCreator, block_num); + msg->getUUIDFast(block, _PREHASH_OwnerID, mOwner, block_num); + msg->getUUIDFast(block, _PREHASH_GroupID, mGroup, block_num); + + msg->getU32Fast(block, _PREHASH_BaseMask, mMaskBase, block_num ); + msg->getU32Fast(block, _PREHASH_OwnerMask, mMaskOwner, block_num ); + msg->getU32Fast(block, _PREHASH_GroupMask, mMaskGroup, block_num ); + msg->getU32Fast(block, _PREHASH_EveryoneMask, mMaskEveryone, block_num ); + msg->getU32Fast(block, _PREHASH_NextOwnerMask, mMaskNextOwner, block_num ); + BOOL tmp; + msg->getBOOLFast(block, _PREHASH_GroupOwned, tmp, block_num); + mIsGroupOwned = (bool)tmp; +} + + +// +// File support +// + +BOOL LLPermissions::importFile(FILE *fp) +{ + init(LLUUID::null, LLUUID::null, LLUUID::null, LLUUID::null); + const S32 BUFSIZE = 16384; + + char buffer[BUFSIZE]; + char keyword[256]; + char valuestr[256]; + char uuid_str[256]; + U32 mask; + + keyword[0] = '\0'; + valuestr[0] = '\0'; + + while (!feof(fp)) + { + fgets(buffer, BUFSIZE, fp); + sscanf(buffer, " %s %s", keyword, valuestr); + if (!keyword) + { + continue; + } + if (!strcmp("{", keyword)) + { + continue; + } + if (!strcmp("}",keyword)) + { + break; + } + else if (!strcmp("creator_mask", keyword)) + { + // legacy support for "creator" masks + sscanf(valuestr, "%x", &mask); + mMaskBase = mask; + fixFairUse(); + } + else if (!strcmp("base_mask", keyword)) + { + sscanf(valuestr, "%x", &mask); + mMaskBase = mask; + //fixFairUse(); + } + else if (!strcmp("owner_mask", keyword)) + { + sscanf(valuestr, "%x", &mask); + mMaskOwner = mask; + } + else if (!strcmp("group_mask", keyword)) + { + sscanf(valuestr, "%x", &mask); + mMaskGroup = mask; + } + else if (!strcmp("everyone_mask", keyword)) + { + sscanf(valuestr, "%x", &mask); + mMaskEveryone = mask; + } + else if (!strcmp("next_owner_mask", keyword)) + { + sscanf(valuestr, "%x", &mask); + mMaskNextOwner = mask; + } + else if (!strcmp("creator_id", keyword)) + { + sscanf(valuestr, "%s", uuid_str); + mCreator.set(uuid_str); + } + else if (!strcmp("owner_id", keyword)) + { + sscanf(valuestr, "%s", uuid_str); + mOwner.set(uuid_str); + } + else if (!strcmp("last_owner_id", keyword)) + { + sscanf(valuestr, "%s", uuid_str); + mLastOwner.set(uuid_str); + } + else if (!strcmp("group_id", keyword)) + { + sscanf(valuestr, "%s", uuid_str); + mGroup.set(uuid_str); + } + else if (!strcmp("group_owned", keyword)) + { + sscanf(valuestr, "%d", &mask); + if(mask) mIsGroupOwned = true; + else mIsGroupOwned = false; + } + else + { + llinfos << "unknown keyword " << keyword << " in permissions import" << llendl; + } + } + fix(); + return TRUE; +} + + +BOOL LLPermissions::exportFile(FILE *fp) const +{ + char uuid_str[256]; + + fprintf(fp, "\tpermissions 0\n"); + fprintf(fp, "\t{\n"); + + fprintf(fp, "\t\tbase_mask\t%08x\n", mMaskBase); + fprintf(fp, "\t\towner_mask\t%08x\n", mMaskOwner); + fprintf(fp, "\t\tgroup_mask\t%08x\n", mMaskGroup); + fprintf(fp, "\t\teveryone_mask\t%08x\n", mMaskEveryone); + fprintf(fp, "\t\tnext_owner_mask\t%08x\n", mMaskNextOwner); + + mCreator.toString(uuid_str); + fprintf(fp, "\t\tcreator_id\t%s\n", uuid_str); + + mOwner.toString(uuid_str); + fprintf(fp, "\t\towner_id\t%s\n", uuid_str); + + mLastOwner.toString(uuid_str); + fprintf(fp, "\t\tlast_owner_id\t%s\n", uuid_str); + + mGroup.toString(uuid_str); + fprintf(fp, "\t\tgroup_id\t%s\n", uuid_str); + + if(mIsGroupOwned) + { + fprintf(fp, "\t\tgroup_owned\t1\n"); + } + fprintf(fp,"\t}\n"); + return TRUE; +} + + +BOOL LLPermissions::importLegacyStream(std::istream& input_stream) +{ + init(LLUUID::null, LLUUID::null, LLUUID::null, LLUUID::null); + const S32 BUFSIZE = 16384; + + char buffer[BUFSIZE]; + char keyword[256]; + char valuestr[256]; + char uuid_str[256]; + U32 mask; + + keyword[0] = '\0'; + valuestr[0] = '\0'; + + while (input_stream.good()) + { + input_stream.getline(buffer, BUFSIZE); + sscanf(buffer, " %s %s", keyword, valuestr); + if (!keyword) + { + continue; + } + if (!strcmp("{", keyword)) + { + continue; + } + if (!strcmp("}",keyword)) + { + break; + } + else if (!strcmp("creator_mask", keyword)) + { + // legacy support for "creator" masks + sscanf(valuestr, "%x", &mask); + mMaskBase = mask; + fixFairUse(); + } + else if (!strcmp("base_mask", keyword)) + { + sscanf(valuestr, "%x", &mask); + mMaskBase = mask; + //fixFairUse(); + } + else if (!strcmp("owner_mask", keyword)) + { + sscanf(valuestr, "%x", &mask); + mMaskOwner = mask; + } + else if (!strcmp("group_mask", keyword)) + { + sscanf(valuestr, "%x", &mask); + mMaskGroup = mask; + } + else if (!strcmp("everyone_mask", keyword)) + { + sscanf(valuestr, "%x", &mask); + mMaskEveryone = mask; + } + else if (!strcmp("next_owner_mask", keyword)) + { + sscanf(valuestr, "%x", &mask); + mMaskNextOwner = mask; + } + else if (!strcmp("creator_id", keyword)) + { + sscanf(valuestr, "%s", uuid_str); + mCreator.set(uuid_str); + } + else if (!strcmp("owner_id", keyword)) + { + sscanf(valuestr, "%s", uuid_str); + mOwner.set(uuid_str); + } + else if (!strcmp("last_owner_id", keyword)) + { + sscanf(valuestr, "%s", uuid_str); + mLastOwner.set(uuid_str); + } + else if (!strcmp("group_id", keyword)) + { + sscanf(valuestr, "%s", uuid_str); + mGroup.set(uuid_str); + } + else if (!strcmp("group_owned", keyword)) + { + sscanf(valuestr, "%d", &mask); + if(mask) mIsGroupOwned = true; + else mIsGroupOwned = false; + } + else + { + llinfos << "unknown keyword " << keyword << " in permissions import" << llendl; + } + } + fix(); + return TRUE; +} + + +BOOL LLPermissions::exportLegacyStream(std::ostream& output_stream) const +{ + char uuid_str[256]; + + output_stream << "\tpermissions 0\n"; + output_stream << "\t{\n"; + + char buffer[256]; + sprintf(buffer, "\t\tbase_mask\t%08x\n", mMaskBase); + output_stream << buffer; + sprintf(buffer, "\t\towner_mask\t%08x\n", mMaskOwner); + output_stream << buffer; + sprintf(buffer, "\t\tgroup_mask\t%08x\n", mMaskGroup); + output_stream << buffer; + sprintf(buffer, "\t\teveryone_mask\t%08x\n", mMaskEveryone); + output_stream << buffer; + sprintf(buffer, "\t\tnext_owner_mask\t%08x\n", mMaskNextOwner); + output_stream << buffer; + + mCreator.toString(uuid_str); + output_stream << "\t\tcreator_id\t" << uuid_str << "\n"; + + mOwner.toString(uuid_str); + output_stream << "\t\towner_id\t" << uuid_str << "\n"; + + mLastOwner.toString(uuid_str); + output_stream << "\t\tlast_owner_id\t" << uuid_str << "\n"; + + mGroup.toString(uuid_str); + output_stream << "\t\tgroup_id\t" << uuid_str << "\n"; + + if(mIsGroupOwned) + { + output_stream << "\t\tgroup_owned\t1\n"; + } + output_stream << "\t}\n"; + return TRUE; +} + + +LLXMLNode *LLPermissions::exportFileXML() const +{ + LLXMLNode *ret = new LLXMLNode("permissions", FALSE); + + ret->createChild("group_owned", TRUE)->setBoolValue(1, (const BOOL*)&mIsGroupOwned); + + ret->createChild("base_mask", FALSE)->setByteValue(4, (U8*)&mMaskBase, LLXMLNode::ENCODING_HEX); + ret->createChild("owner_mask", FALSE)->setByteValue(4, (U8*)&mMaskOwner, LLXMLNode::ENCODING_HEX); + ret->createChild("group_mask", FALSE)->setByteValue(4, (U8*)&mMaskGroup, LLXMLNode::ENCODING_HEX); + ret->createChild("everyone_mask", FALSE)->setByteValue(4, (U8*)&mMaskEveryone, LLXMLNode::ENCODING_HEX); + ret->createChild("next_owner_mask", FALSE)->setByteValue(4, (U8*)&mMaskNextOwner, LLXMLNode::ENCODING_HEX); + + ret->createChild("creator_id", FALSE)->setUUIDValue(1, &mCreator); + ret->createChild("owner_id", FALSE)->setUUIDValue(1, &mOwner); + ret->createChild("last_owner_id", FALSE)->setUUIDValue(1, &mLastOwner); + ret->createChild("group_id", FALSE)->setUUIDValue(1, &mGroup); + + return ret; +} + +bool LLPermissions::importXML(LLXMLNode* node) +{ + bool success = false; + if (node) + { + success = true; + LLXMLNodePtr sub_node; + if (node->getChild("base_mask", sub_node)) + success = success && (4 == sub_node->getByteValue(4, (U8*)&mMaskBase)); + if (node->getChild("owner_mask", sub_node)) + success = success && (4 == sub_node->getByteValue(4, (U8*)&mMaskOwner)); + if (node->getChild("group_mask", sub_node)) + success = success && (4 == sub_node->getByteValue(4, (U8*)&mMaskGroup)); + if (node->getChild("everyone_mask", sub_node)) + success = success && (4 == sub_node->getByteValue(4, (U8*)&mMaskEveryone)); + if (node->getChild("next_owner_mask", sub_node)) + success = success && (4 == sub_node->getByteValue(4, (U8*)&mMaskNextOwner)); + + if (node->getChild("creator_id", sub_node)) + success = success && (1 == sub_node->getUUIDValue(1, &mCreator)); + if (node->getChild("owner_id", sub_node)) + success = success && (1 == sub_node->getUUIDValue(1, &mOwner)); + if (node->getChild("last_owner_id", sub_node)) + success = success && (1 == sub_node->getUUIDValue(1, &mLastOwner)); + if (node->getChild("group_id", sub_node)) + success = success && (1 == sub_node->getUUIDValue(1, &mGroup)); + if (node->getChild("group_owned", sub_node)) + success = success && (1 == sub_node->getBoolValue(1, (BOOL*)&mIsGroupOwned)); + if (!success) + { + lldebugs << "LLPermissions::importXML() failed for node named '" + << node->getName() << "'" << llendl; + } + } + return success; +} + +bool LLPermissions::operator==(const LLPermissions &rhs) const +{ + return + (mCreator == rhs.mCreator) && + (mOwner == rhs.mOwner) && + (mLastOwner == rhs.mLastOwner ) && + (mGroup == rhs.mGroup ) && + (mMaskBase == rhs.mMaskBase ) && + (mMaskOwner == rhs.mMaskOwner ) && + (mMaskGroup == rhs.mMaskGroup ) && + (mMaskEveryone == rhs.mMaskEveryone ) && + (mMaskNextOwner == rhs.mMaskNextOwner ) && + (mIsGroupOwned == rhs.mIsGroupOwned); +} + + +bool LLPermissions::operator!=(const LLPermissions &rhs) const +{ + return + (mCreator != rhs.mCreator) || + (mOwner != rhs.mOwner) || + (mLastOwner != rhs.mLastOwner ) || + (mGroup != rhs.mGroup ) || + (mMaskBase != rhs.mMaskBase ) || + (mMaskOwner != rhs.mMaskOwner ) || + (mMaskGroup != rhs.mMaskGroup ) || + (mMaskEveryone != rhs.mMaskEveryone ) || + (mMaskNextOwner != rhs.mMaskNextOwner) || + (mIsGroupOwned != rhs.mIsGroupOwned); +} + +std::ostream& operator<<(std::ostream &s, const LLPermissions &perm) +{ + s << "{Creator=" << perm.getCreator(); + s << ", Owner=" << perm.getOwner(); + s << ", Group=" << perm.getGroup(); + s << std::hex << ", BaseMask=0x" << perm.getMaskBase(); + s << ", OwnerMask=0x" << perm.getMaskOwner(); + s << ", EveryoneMask=0x" << perm.getMaskEveryone(); + s << ", GroupMask=0x" << perm.getMaskGroup(); + s << ", NextOwnerMask=0x" << perm.getMaskNextOwner() << std::dec; + s << "}"; + return s; +} + +template <> +void LLMetaClassT::reflectProperties(LLMetaClass& meta_class) +{ + reflectProperty(meta_class, "mCreator", &LLPermissions::mCreator); + reflectProperty(meta_class, "mOwner", &LLPermissions::mOwner); +} + +// virtual +const LLMetaClass& LLPermissions::getMetaClass() const +{ + return LLMetaClassT::instance(); +} + +///---------------------------------------------------------------------------- +/// Class LLAggregatePermissions +///---------------------------------------------------------------------------- + +const LLAggregatePermissions LLAggregatePermissions::empty; + + +LLAggregatePermissions::LLAggregatePermissions() +{ + for(S32 i = 0; i < PI_COUNT; ++i) + { + mBits[i] = AP_EMPTY; + } +} + +LLAggregatePermissions::EValue LLAggregatePermissions::getValue(PermissionBit bit) const +{ + EPermIndex idx = perm2PermIndex(bit); + EValue rv = AP_EMPTY; + if(idx != PI_END) + { + rv = (LLAggregatePermissions::EValue)(mBits[idx]); + } + return rv; +} + +// returns the bits compressed into a single byte: 00TTMMCC +// where TT = transfer, MM = modify, and CC = copy +// LSB is to the right +U8 LLAggregatePermissions::getU8() const +{ + U8 byte = mBits[PI_TRANSFER]; + byte <<= 2; + byte |= mBits[PI_MODIFY]; + byte <<= 2; + byte |= mBits[PI_COPY]; + return byte; +} + +BOOL LLAggregatePermissions::isEmpty() const +{ + for(S32 i = 0; i < PI_END; ++i) + { + if(mBits[i] != AP_EMPTY) + { + return FALSE; + } + } + return TRUE; +} + +void LLAggregatePermissions::aggregate(PermissionMask mask) +{ + BOOL is_allowed = mask & PERM_COPY; + aggregateBit(PI_COPY, is_allowed); + is_allowed = mask & PERM_MODIFY; + aggregateBit(PI_MODIFY, is_allowed); + is_allowed = mask & PERM_TRANSFER; + aggregateBit(PI_TRANSFER, is_allowed); +} + +void LLAggregatePermissions::aggregate(const LLAggregatePermissions& ag) +{ + for(S32 idx = PI_COPY; idx != PI_END; ++idx) + { + aggregateIndex((EPermIndex)idx, ag.mBits[idx]); + } +} + +void LLAggregatePermissions::aggregateBit(EPermIndex idx, BOOL allowed) +{ + //if(AP_SOME == mBits[idx]) return; // P4 branch prediction optimization + switch(mBits[idx]) + { + case AP_EMPTY: + mBits[idx] = allowed ? AP_ALL : AP_NONE; + break; + case AP_NONE: + mBits[idx] = allowed ? AP_SOME: AP_NONE; + break; + case AP_SOME: + // no-op + break; + case AP_ALL: + mBits[idx] = allowed ? AP_ALL : AP_SOME; + break; + default: + llwarns << "Bad aggregateBit " << (S32)idx << " " + << (allowed ? "true" : "false") << llendl; + break; + } +} + +void LLAggregatePermissions::aggregateIndex(EPermIndex idx, U8 bits) +{ + switch(mBits[idx]) + { + case AP_EMPTY: + mBits[idx] = bits; + break; + case AP_NONE: + switch(bits) + { + case AP_SOME: + case AP_ALL: + mBits[idx] = AP_SOME; + break; + case AP_EMPTY: + case AP_NONE: + default: + // no-op + break; + } + break; + case AP_SOME: + // no-op + break; + case AP_ALL: + switch(bits) + { + case AP_NONE: + case AP_SOME: + mBits[idx] = AP_SOME; + break; + case AP_EMPTY: + case AP_ALL: + default: + // no-op + break; + } + break; + default: + llwarns << "Bad aggregate index " << (S32)idx << " " + << (S32)bits << llendl; + break; + } +} + +// static +LLAggregatePermissions::EPermIndex LLAggregatePermissions::perm2PermIndex(PermissionBit bit) +{ + EPermIndex idx = PI_END; // past any good value. + switch(bit) + { + case PERM_COPY: + idx = PI_COPY; + break; + case PERM_MODIFY: + idx = PI_MODIFY; + break; + case PERM_TRANSFER: + idx = PI_TRANSFER; + break; + default: + break; + } + return idx; +} + + +void LLAggregatePermissions::packMessage(LLMessageSystem* msg, const char* field) const +{ + msg->addU8Fast(field, getU8()); +} + +void LLAggregatePermissions::unpackMessage(LLMessageSystem* msg, const char* block, const char* field, S32 block_num) +{ + const U8 TWO_BITS = 0x3; // binary 00000011 + U8 bits = 0; + msg->getU8Fast(block, field, bits, block_num); + mBits[PI_COPY] = bits & TWO_BITS; + bits >>= 2; + mBits[PI_MODIFY] = bits & TWO_BITS; + bits >>= 2; + mBits[PI_TRANSFER] = bits & TWO_BITS; +} + +const LLString AGGREGATE_VALUES[4] = + { + LLString( "Empty" ), + LLString( "None" ), + LLString( "Some" ), + LLString( "All" ) + }; + +std::ostream& operator<<(std::ostream &s, const LLAggregatePermissions &perm) +{ + s << "{PI_COPY=" << AGGREGATE_VALUES[perm.mBits[LLAggregatePermissions::PI_COPY]]; + s << ", PI_MODIFY=" << AGGREGATE_VALUES[perm.mBits[LLAggregatePermissions::PI_MODIFY]]; + s << ", PI_TRANSFER=" << AGGREGATE_VALUES[perm.mBits[LLAggregatePermissions::PI_TRANSFER]]; + s << "}"; + return s; +} + +// This converts a permissions mask into a string for debugging use. +void mask_to_string(U32 mask, char* str) +{ + if (mask & PERM_MOVE) + { + *str = 'V'; + } + else + { + *str = ' '; + } + str++; + + if (mask & PERM_MODIFY) + { + *str = 'M'; + } + else + { + *str = ' '; + } + str++; + + if (mask & PERM_COPY) + { + *str = 'C'; + } + else + { + *str = ' '; + } + str++; + + if (mask & PERM_TRANSFER) + { + *str = 'T'; + } + else + { + *str = ' '; + } + str++; + *str = '\0'; +} + + +///---------------------------------------------------------------------------- +/// exported functions +///---------------------------------------------------------------------------- +static const std::string PERM_CREATOR_ID_LABEL("creator_id"); +static const std::string PERM_OWNER_ID_LABEL("owner_id"); +static const std::string PERM_LAST_OWNER_ID_LABEL("last_owner_id"); +static const std::string PERM_GROUP_ID_LABEL("group_id"); +static const std::string PERM_IS_OWNER_GROUP_LABEL("is_owner_group"); +static const std::string PERM_BASE_MASK_LABEL("base_mask"); +static const std::string PERM_OWNER_MASK_LABEL("owner_mask"); +static const std::string PERM_GROUP_MASK_LABEL("group_mask"); +static const std::string PERM_EVERYONE_MASK_LABEL("everyone_mask"); +static const std::string PERM_NEXT_OWNER_MASK_LABEL("next_owner_mask"); + +LLSD ll_create_sd_from_permissions(const LLPermissions& perm) +{ + LLSD rv; + rv[PERM_CREATOR_ID_LABEL] = perm.getCreator(); + rv[PERM_OWNER_ID_LABEL] = perm.getOwner(); + rv[PERM_LAST_OWNER_ID_LABEL] = perm.getLastOwner(); + rv[PERM_GROUP_ID_LABEL] = perm.getGroup(); + rv[PERM_IS_OWNER_GROUP_LABEL] = perm.isGroupOwned(); + rv[PERM_BASE_MASK_LABEL] = (S32)perm.getMaskBase(); + rv[PERM_OWNER_MASK_LABEL] = (S32)perm.getMaskOwner(); + rv[PERM_GROUP_MASK_LABEL] = (S32)perm.getMaskGroup(); + rv[PERM_EVERYONE_MASK_LABEL] = (S32)perm.getMaskEveryone(); + rv[PERM_NEXT_OWNER_MASK_LABEL] = (S32)perm.getMaskNextOwner(); + return rv; +} + +LLPermissions ll_permissions_from_sd(const LLSD& sd_perm) +{ + LLPermissions rv; + rv.init( + sd_perm[PERM_CREATOR_ID_LABEL].asUUID(), + sd_perm[PERM_OWNER_ID_LABEL].asUUID(), + sd_perm[PERM_LAST_OWNER_ID_LABEL].asUUID(), + sd_perm[PERM_GROUP_ID_LABEL].asUUID()); + + // We do a cast to U32 here since LLSD does not attempt to + // represent unsigned ints. + PermissionMask mask; + mask = (U32)(sd_perm[PERM_BASE_MASK_LABEL].asInteger()); + rv.setMaskBase(mask); + mask = (U32)(sd_perm[PERM_OWNER_MASK_LABEL].asInteger()); + rv.setMaskOwner(mask); + mask = (U32)(sd_perm[PERM_EVERYONE_MASK_LABEL].asInteger()); + rv.setMaskEveryone(mask); + mask = (U32)(sd_perm[PERM_GROUP_MASK_LABEL].asInteger()); + rv.setMaskGroup(mask); + mask = (U32)(sd_perm[PERM_NEXT_OWNER_MASK_LABEL].asInteger()); + rv.setMaskNext(mask); + rv.fix(); + return rv; +} diff --git a/indra/llinventory/llpermissions.h b/indra/llinventory/llpermissions.h new file mode 100644 index 0000000000..76794e1ed9 --- /dev/null +++ b/indra/llinventory/llpermissions.h @@ -0,0 +1,426 @@ +/** + * @file llpermissions.h + * @brief Permissions structures for objects. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLPERMISSIONS_H +#define LL_LLPERMISSIONS_H + +#include +#include + +#include "llpermissionsflags.h" +#include "llsd.h" +#include "lluuid.h" +#include "llxmlnode.h" +#include "reflective.h" + +// prototypes +class LLMessageSystem; +extern void mask_to_string(U32 mask, char* str); +template class LLMetaClassT; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLPermissions +// +// Class which encapsulates object and inventory permissions/ownership/etc. +// +// Permissions where originally a static state creator/owner and set +// of cap bits. Since then, it has grown to include group information, +// last owner, masks for different people. The implementation has been +// chosen such that a uuid is stored for each current/past owner, and +// a bitmask is stored for the base permissions, owner permissions, +// group permissions, and everyone else permissions. +// +// The base permissions represent the most permissive state that the +// permissions can possibly be in. Thus, if the base permissions do +// not allow copying, no one can ever copy the object. The permissions +// also maintain a tree-like hierarchy of permissions, thus, if we +// (for sake of discussions) denote more permissive as '>', then this +// is invariant: +// +// base mask >= owner mask >= group mask +// >= everyone mask +// >= next owner mask +// NOTE: the group mask does not effect everyone or next, everyone +// does not effect group or next, etc. +// +// It is considered a fair use right to move or delete any object you +// own. Another fair use right is the ability to give away anything +// which you cannot copy. One way to look at that is that if you have +// a unique item, you can always give that one copy you have to +// someone else. +// +// Most of the bitmask is easy to understand, PERM_COPY means you can +// copy !PERM_TRANSFER means you cannot transfer, etc. Given that we +// now track the concept of 'next owner' inside of the permissions +// object, we can describe some new meta-meaning to the PERM_MODIFY +// flag. PERM_MODIFY is usually meant to note if you can change an +// item, but since we record next owner permissions, we can interpret +// a no-modify object as 'you cannot modify this object and you cannot +// make derivative works.' When evaluating functionality, and +// comparisons against permissions, keep this concept in mind for +// logical consistency. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLPermissions : public LLReflective +{ +private: + LLUUID mCreator; // null if object created by system + LLUUID mOwner; // null if object "unowned" (owned by system) + LLUUID mLastOwner; // object's last owner + LLUUID mGroup; // The group association + + PermissionMask mMaskBase; // initially permissive, progressively AND restricted by each owner + + PermissionMask mMaskOwner; // set by owner, applies to owner only, restricts lower permissions + PermissionMask mMaskEveryone; // set by owner, applies to everyone else + + PermissionMask mMaskGroup; // set by owner, applies to group that is associated with permissions + + PermissionMask mMaskNextOwner; // set by owner, applied to base on transfer. + + // Usually set in the fixOwnership() method based on current uuid + // values. + bool mIsGroupOwned; + + // Correct for fair use - you can never take away the right to + // move stuff you own, and you can never take away the right to + // transfer something you cannot otherwise copy. + void fixFairUse(); + + // Fix internal consistency for group/agent ownership + void fixOwnership(); + +public: + static const LLPermissions DEFAULT; + + LLPermissions(); // defaults to created by system + //~LLPermissions(); + + // base initialization code + void init(const LLUUID& creator, const LLUUID& owner, + const LLUUID& last_owner, const LLUUID& group); + void initMasks(PermissionMask base, PermissionMask owner, + PermissionMask everyone, PermissionMask group, + PermissionMask next); + + // + // ACCESSORS + // + + // return the agent_id of the agent that created the item + const LLUUID& getCreator() const { return mCreator; } + + // return the agent_id of the owner. returns LLUUID::null if group + // owned or public (a really big group). + const LLUUID& getOwner() const { return mOwner; } + + // return the group_id of the group associated with the + // object. group_id == owner_id if the object is group owned. + const LLUUID& getGroup() const { return mGroup; } + + // return the agent_id of the last agent owner. Only returns + // LLUUID::null if there has never been a previous owner. + const LLUUID& getLastOwner() const { return mLastOwner; } + + U32 getMaskBase() const { return mMaskBase; } + U32 getMaskOwner() const { return mMaskOwner; } + U32 getMaskGroup() const { return mMaskGroup; } + U32 getMaskEveryone() const { return mMaskEveryone; } + U32 getMaskNextOwner() const { return mMaskNextOwner; } + + // return TRUE if the object has any owner + bool isOwned() const { return (mOwner.notNull() || mIsGroupOwned); } + + // return TRUE if group_id is owner. + bool isGroupOwned() const { return mIsGroupOwned; } + + // This API returns TRUE if the object is owned at all, and FALSE + // otherwise. If it is owned at all, owner id is filled with + // either the owner id or the group id, and the is_group_owned + // parameter is appropriately filled. The values of owner_id and + // is_group_owned are not changed if the object is not owned. + BOOL getOwnership(LLUUID& owner_id, BOOL& is_group_owned) const; + + // Gets the 'safe' owner. This should never return LLUUID::null. + // If no group owned, return the agent owner id normally. + // If group owned, return the group id. + // If not owned, return a random uuid which should have no power. + LLUUID getSafeOwner() const; + + // return a cheap crc + U32 getCRC32() const; + + + // + // MANIPULATORS + // + + // Fix hierarchy of permissions, applies appropriate permissions + // at each level to ensure that base permissions are respected, + // and also ensures that if base cannot transfer, then group and + // other cannot copy. + void fix(); + + // All of these methods just do exactly what they say. There is no + // permissions checking to see if the operation is allowed, and do + // not fix the permissions hierarchy. So please only use these + // methods when you are know what you're doing and coding on + // behalf of the system - ie, acting as god. + void set(const LLPermissions& permissions); + void setMaskBase(U32 mask) { mMaskBase = mask; } + void setMaskOwner(U32 mask) { mMaskOwner = mask; } + void setMaskEveryone(U32 mask) { mMaskEveryone = mask;} + void setMaskGroup(U32 mask) { mMaskGroup = mask;} + void setMaskNext(U32 mask) { mMaskNextOwner = mask; } + + // Allow accumulation of permissions. Results in the tightest + // permissions possible. In the case of clashing UUIDs, it sets + // the ID to LLUUID::null. + void accumulate(const LLPermissions& perm); + + // + // CHECKED MANIPULATORS + // + + // These functions return true on success. They return false if + // the given agent isn't allowed to make the change. You can pass + // LLUUID::null as the agent id if the change is being made by the + // simulator itself, not on behalf of any agent - this will always + // succeed. Passing in group id of LLUUID:null means no group, and + // does not offer special permission to do anything. + + // saves last owner, sets current owner, and sets the group. + // set is_atomic = true means that this permission represents + // an atomic permission and not a collection of permissions. + // Currently, the only way to have a collection is when an object + // has inventory and is then itself rolled up into an inventory + // item. + BOOL setOwnerAndGroup(const LLUUID& agent, const LLUUID& owner, const LLUUID& group, bool is_atomic); + + // saves last owner, sets owner to uuid null, sets group + // owned. group_id must be the group of the object (that's who it + // is being deeded to) and the object must be group + // modify. Technically, the agent id and group id are not + // necessary, but I wanted this function to look like the other + // checked manipulators (since that is how it is used.) If the + // agent is the system or (group == mGroup and group modify and + // owner transfer) then this function will deed the permissions, + // set the next owner mask, and return TRUE. Otherwise, no change + // is effected, and the function returns FALSE. + BOOL deedToGroup(const LLUUID& agent, const LLUUID& group); + // Attempt to set or clear the given bitmask. Returns TRUE if you + // are allowed to modify the permissions. If you attempt to turn + // on bits not allowed by the base bits, the function will return + // TRUE, but those bits will not be set. + BOOL setBaseBits( const LLUUID& agent, BOOL set, PermissionMask bits); + BOOL setOwnerBits( const LLUUID& agent, BOOL set, PermissionMask bits); + BOOL setGroupBits( const LLUUID& agent, const LLUUID& group, BOOL set, PermissionMask bits); + BOOL setEveryoneBits(const LLUUID& agent, const LLUUID& group, BOOL set, PermissionMask bits); + BOOL setNextOwnerBits(const LLUUID& agent, const LLUUID& group, BOOL set, PermissionMask bits); + + // + // METHODS + // + + // All the allow* functions return true if the given agent or + // group can perform the function. Prefer using this set of + // operations to check permissions on an object. These return + // true if the given agent or group can perform the function. + // They also return true if the object isn't owned, or the + // requesting agent is a system agent. See llpermissionsflags.h + // for bits. + BOOL allowOperationBy(PermissionBit op, const LLUUID& agent, const LLUUID& group = LLUUID::null) const; + + inline BOOL allowModifyBy(const LLUUID &agent_id) const; + inline BOOL allowCopyBy(const LLUUID& agent_id) const; + inline BOOL allowMoveBy(const LLUUID& agent_id) const; + inline BOOL allowModifyBy(const LLUUID &agent_id, const LLUUID& group) const; + inline BOOL allowCopyBy(const LLUUID& agent_id, const LLUUID& group) const; + inline BOOL allowMoveBy(const LLUUID &agent_id, const LLUUID &group) const; + + // This somewhat specialized function is meant for testing if the + // current owner is allowed to transfer to the specified agent id. + inline BOOL allowTransferTo(const LLUUID &agent_id) const; + + // + // DEPRECATED. + // + // These return true if the given agent can perform the function. + // They also return true if the object isn't owned, or the + // requesting agent is a system agent. See llpermissionsflags.h + // for bits. + //BOOL allowDeleteBy(const LLUUID& agent_id) const { return allowModifyBy(agent_id); } + //BOOL allowEditBy(const LLUUID& agent_id) const { return allowModifyBy(agent_id); } + // saves last owner and sets current owner + //BOOL setOwner(const LLUUID& agent, const LLUUID& owner); + // This method saves the last owner, sets the current owner to the + // one provided, and sets the base mask as indicated. + //BOOL setOwner(const LLUUID& agent, const LLUUID& owner, U32 new_base_mask); + + // Attempt to set or clear the given bitmask. Returns TRUE if you + // are allowed to modify the permissions. If you attempt to turn + // on bits not allowed by the base bits, the function will return + // TRUE, but those bits will not be set. + //BOOL setGroupBits( const LLUUID& agent, BOOL set, PermissionMask bits); + //BOOL setEveryoneBits(const LLUUID& agent, BOOL set, PermissionMask bits); + + // + // MISC METHODS and OPERATORS + // + + // For messaging system support + void packMessage(LLMessageSystem* msg) const; + void unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num = 0); + + // Load/save support + BOOL importFile(FILE* fp); + BOOL exportFile(FILE* fp) const; + + BOOL importLegacyStream(std::istream& input_stream); + BOOL exportLegacyStream(std::ostream& output_stream) const; + + LLXMLNode *exportFileXML() const; + bool importXML(LLXMLNode* node); + + bool operator==(const LLPermissions &rhs) const; + bool operator!=(const LLPermissions &rhs) const; + + friend std::ostream& operator<<(std::ostream &s, const LLPermissions &perm); + + // Reflection. + friend class LLMetaClassT; + virtual const LLMetaClass& getMetaClass() const; +}; + +// Inlines +BOOL LLPermissions::allowModifyBy(const LLUUID& agent, const LLUUID& group) const +{ + return allowOperationBy(PERM_MODIFY, agent, group); +} + +BOOL LLPermissions::allowCopyBy(const LLUUID& agent, const LLUUID& group) const +{ + return allowOperationBy(PERM_COPY, agent, group); +} + + +BOOL LLPermissions::allowMoveBy(const LLUUID& agent, const LLUUID& group) const +{ + return allowOperationBy(PERM_MOVE, agent, group); +} + +BOOL LLPermissions::allowModifyBy(const LLUUID& agent) const +{ + return allowOperationBy(PERM_MODIFY, agent, LLUUID::null); +} + +BOOL LLPermissions::allowCopyBy(const LLUUID& agent) const +{ + return allowOperationBy(PERM_COPY, agent, LLUUID::null); +} + +BOOL LLPermissions::allowMoveBy(const LLUUID& agent) const +{ + return allowOperationBy(PERM_MOVE, agent, LLUUID::null); +} + +BOOL LLPermissions::allowTransferTo(const LLUUID &agent_id) const +{ + if (mIsGroupOwned) + { + return allowOperationBy(PERM_TRANSFER, mGroup, mGroup); + } + else + { + return ((mOwner == agent_id) ? TRUE : allowOperationBy(PERM_TRANSFER, mOwner)); + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLAggregatePermissions +// +// Class which encapsulates object and inventory permissions, +// ownership, etc. Currently, it only aggregates PERM_COPY, +// PERM_MODIFY, and PERM_TRANSFER. +// +// Usually you will construct an instance and hand the object several +// permissions masks to aggregate the copy, modify, and +// transferability into a nice trinary value. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLAggregatePermissions +{ +public: + enum EValue + { + AP_EMPTY = 0x00, + AP_NONE = 0x01, + AP_SOME = 0x02, + AP_ALL = 0x03 + }; + + // construct an empty aggregate permissions + LLAggregatePermissions(); + + // pass in a PERM_COPY, PERM_TRANSFER, etc, and get out a EValue + // enumeration describing the current aggregate permissions. + EValue getValue(PermissionBit bit) const; + + // returns the permissions packed into the 6 LSB of a U8: + // 00TTMMCC + // where TT = transfer, MM = modify, and CC = copy + // LSB is to the right + U8 getU8() const; + + // return TRUE is the aggregate permissions are empty, otherwise FALSE. + BOOL isEmpty() const ; + + // pass in a PERM_COPY, PERM_TRANSFER, etc, and an EValue + // enumeration to specifically set that value. Not implemented + // because I'm not sure it's a useful api. + //void setValue(PermissionBit bit, EValue); + + // Given a mask, aggregate the useful permissions. + void aggregate(PermissionMask mask); + + // Aggregate aggregates + void aggregate(const LLAggregatePermissions& ag); + + // message handling + void packMessage(LLMessageSystem* msg, const char* field) const; + void unpackMessage(LLMessageSystem* msg, const char* block, const char *field, S32 block_num = 0); + + static const LLAggregatePermissions empty; + + friend std::ostream& operator<<(std::ostream &s, const LLAggregatePermissions &perm); + +protected: + enum EPermIndex + { + PI_COPY = 0, + PI_MODIFY = 1, + PI_TRANSFER = 2, + PI_END = 3, + PI_COUNT = 3 + }; + void aggregateBit(EPermIndex idx, BOOL allowed); + void aggregateIndex(EPermIndex idx, U8 bits); + static EPermIndex perm2PermIndex(PermissionBit bit); + + // structure used to store the aggregate so far. + U8 mBits[PI_COUNT]; +}; + +// These functions convert between structured data and permissions as +// appropriate for serialization. The permissions are a map of things +// like 'creator_id', 'owner_id', etc, with the value copied from the +// permission object. +LLSD ll_create_sd_from_permissions(const LLPermissions& perm); +LLPermissions ll_permissions_from_sd(const LLSD& sd_perm); + +#endif diff --git a/indra/llinventory/llpermissionsflags.h b/indra/llinventory/llpermissionsflags.h new file mode 100644 index 0000000000..f45758c501 --- /dev/null +++ b/indra/llinventory/llpermissionsflags.h @@ -0,0 +1,78 @@ +/** + * @file llpermissionsflags.h + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLPERMISSIONSFLAGS_H +#define LL_LLPERMISSIONSFLAGS_H + +// llpermissionsflags.h +// Copyright 2002, Linden Research, Inc. +// +// Flags for various permissions bits. +// Shared between viewer and simulator. + +// permission bits +typedef U32 PermissionMask; +typedef U32 PermissionBit; + + +// Do you have permission to transfer ownership of the object or +// item. Fair use rules dictate that if you cannot copy, you can +// always transfer. +const PermissionBit PERM_TRANSFER = (1 << 13); // 0x00002000 + +// objects, scale or change textures +// parcels, allow building on it +const PermissionBit PERM_MODIFY = (1 << 14); // 0x00004000 + +// objects, allow copy +const PermissionBit PERM_COPY = (1 << 15); // 0x00008000 + +// parcels, allow entry, deprecated +//const PermissionBit PERM_ENTER = (1 << 16); // 0x00010000 + +// parcels, allow terraform, deprecated +//const PermissionBit PERM_TERRAFORM = (1 << 17); // 0x00020000 + +// NOTA BENE: This flag is NO LONGER USED!!! However, it is possible that some +// objects in the universe have it set so DON"T USE IT going forward. +//const PermissionBit PERM_OWNER_DEBIT = (1 << 18); // 0x00040000 + +// objects, can grab/translate/rotate +const PermissionBit PERM_MOVE = (1 << 19); // 0x00080000 + +// parcels, avatars take damage, deprecated +//const PermissionBit PERM_DAMAGE = (1 << 20); // 0x00100000 + +// don't use bit 31 -- printf/scanf with "%x" assume signed numbers +const PermissionBit PERM_RESERVED = ((U32)1) << 31; + +const PermissionMask PERM_NONE = 0x00000000; +const PermissionMask PERM_ALL = 0x7FFFFFFF; +//const PermissionMask PERM_ALL_PARCEL = PERM_MODIFY | PERM_ENTER | PERM_TERRAFORM | PERM_DAMAGE; +const PermissionMask PERM_ITEM_UNRESTRICTED = PERM_MODIFY | PERM_COPY | PERM_TRANSFER; + + +// Useful stuff for transmission. +// Which permissions field are we trying to change? +const U8 PERM_BASE = 0x01; +// TODO: Add another PERM_OWNER operation type for allowOperationBy DK 04/03/06 +const U8 PERM_OWNER = 0x02; +const U8 PERM_GROUP = 0x04; +const U8 PERM_EVERYONE = 0x08; +const U8 PERM_NEXT_OWNER = 0x10; + +// This is just a quickie debugging key +// no modify: PERM_ALL & ~PERM_MODIFY = 0x7fffbfff +// no copy: PERM_ALL & ~PERM_COPY = 0x7fff7fff +// no modify or copy: = 0x7fff3fff +// no transfer: PERM_ALL & ~PERM_TRANSFER = 0x7fffdfff +// no modify, no transfer = 0x7fff9fff +// no copy, no transfer (INVALID!) = 0x7fff5fff +// no modify, no copy, no transfer (INVALID!) = 0x7fff1fff + + +#endif diff --git a/indra/llinventory/llsaleinfo.cpp b/indra/llinventory/llsaleinfo.cpp new file mode 100644 index 0000000000..7e2b293d42 --- /dev/null +++ b/indra/llinventory/llsaleinfo.cpp @@ -0,0 +1,356 @@ +/** + * @file llsaleinfo.cpp + * @brief + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include +#include "linden_common.h" + +#include "llsaleinfo.h" + +#include "llerror.h" +#include "message.h" +#include "llsdutil.h" + +// use this to avoid temporary object creation +const LLSaleInfo LLSaleInfo::DEFAULT; + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- + +const char* FOR_SALE_NAMES[] = +{ + "not", + "orig", + "copy", + "cntn" +}; + +///---------------------------------------------------------------------------- +/// Class llsaleinfo +///---------------------------------------------------------------------------- + +// Default constructor +LLSaleInfo::LLSaleInfo() : + mSaleType(LLSaleInfo::FS_NOT), + mSalePrice(DEFAULT_PRICE) +{ +} + +LLSaleInfo::LLSaleInfo(EForSale sale_type, S32 sale_price) : + mSaleType(sale_type), + mSalePrice(sale_price) +{ + mSalePrice = llclamp(mSalePrice, 0, S32_MAX); +} + +BOOL LLSaleInfo::isForSale() const +{ + return (FS_NOT != mSaleType); +} + +U32 LLSaleInfo::getCRC32() const +{ + U32 rv = (U32)mSalePrice; + rv += (mSaleType * 0x07073096); + return rv; +} + + +BOOL LLSaleInfo::exportFile(FILE* fp) const +{ + fprintf(fp, "\tsale_info\t0\n\t{\n"); + fprintf(fp, "\t\tsale_type\t%s\n", lookup(mSaleType)); + fprintf(fp, "\t\tsale_price\t%d\n", mSalePrice); + fprintf(fp,"\t}\n"); + return TRUE; +} + +BOOL LLSaleInfo::exportLegacyStream(std::ostream& output_stream) const +{ + output_stream << "\tsale_info\t0\n\t{\n"; + output_stream << "\t\tsale_type\t" << lookup(mSaleType) << "\n"; + output_stream << "\t\tsale_price\t" << mSalePrice << "\n"; + output_stream <<"\t}\n"; + return TRUE; +} + +LLSD LLSaleInfo::asLLSD() const +{ + LLSD sd = LLSD(); + sd["sale_type"] = lookup(mSaleType); + sd["sale_price"] = mSalePrice; + return sd; +} + +bool LLSaleInfo::fromLLSD(LLSD& sd, BOOL& has_perm_mask, U32& perm_mask) +{ + const char *w; + + mSaleType = lookup(sd["sale_type"].asString().c_str()); + mSalePrice = llclamp(sd["sale_price"].asInteger(), 0, S32_MAX); + w = "perm_mask"; + if (sd.has(w)) + { + has_perm_mask = TRUE; + perm_mask = ll_U32_from_sd(sd[w]); + } + return true; +} + +LLXMLNode *LLSaleInfo::exportFileXML() const +{ + LLXMLNode *ret = new LLXMLNode("sale_info", FALSE); + LLString type_str = lookup(mSaleType); + ret->createChild("type", TRUE)->setStringValue(1, &type_str); + ret->createChild("price", TRUE)->setIntValue(1, &mSalePrice); + return ret; +} + +BOOL LLSaleInfo::importXML(LLXMLNode* node) +{ + BOOL success = FALSE; + if (node) + { + success = TRUE; + LLXMLNodePtr sub_node; + if (node->getChild("type", sub_node)) + { + mSaleType = lookup(sub_node->getValue().c_str()); + } + if (node->getChild("price", sub_node)) + { + success &= (1 == sub_node->getIntValue(1, &mSalePrice)); + } + if (!success) + { + lldebugs << "LLSaleInfo::importXML() failed for node named '" + << node->getName() << "'" << llendl; + } + } + return success; +} + +BOOL LLSaleInfo::importFile(FILE* fp, BOOL& has_perm_mask, U32& perm_mask) +{ + has_perm_mask = FALSE; + + char buffer[MAX_STRING]; + char keyword[MAX_STRING]; + char valuestr[MAX_STRING]; + BOOL success = TRUE; + + keyword[0] = '\0'; + valuestr[0] = '\0'; + while(success && (!feof(fp))) + { + fgets(buffer, MAX_STRING, fp); + sscanf(buffer, " %s %s", keyword, valuestr); + if(!keyword) + { + continue; + } + if(0 == strcmp("{",keyword)) + { + continue; + } + if(0 == strcmp("}", keyword)) + { + break; + } + else if(0 == strcmp("sale_type", keyword)) + { + mSaleType = lookup(valuestr); + } + else if(0 == strcmp("sale_price", keyword)) + { + sscanf(valuestr, "%d", &mSalePrice); + mSalePrice = llclamp(mSalePrice, 0, S32_MAX); + } + else if (!strcmp("perm_mask", keyword)) + { + //llinfos << "found deprecated keyword perm_mask" << llendl; + has_perm_mask = TRUE; + sscanf(valuestr, "%x", &perm_mask); + } + else + { + llwarns << "unknown keyword '" << keyword + << "' in sale info import" << llendl; + } + } + return success; +} + +BOOL LLSaleInfo::importLegacyStream(std::istream& input_stream, BOOL& has_perm_mask, U32& perm_mask) +{ + has_perm_mask = FALSE; + + char buffer[MAX_STRING]; + char keyword[MAX_STRING]; + char valuestr[MAX_STRING]; + BOOL success = TRUE; + + keyword[0] = '\0'; + valuestr[0] = '\0'; + while(success && input_stream.good()) + { + input_stream.getline(buffer, MAX_STRING); + sscanf(buffer, " %s %s", keyword, valuestr); + if(!keyword) + { + continue; + } + if(0 == strcmp("{",keyword)) + { + continue; + } + if(0 == strcmp("}", keyword)) + { + break; + } + else if(0 == strcmp("sale_type", keyword)) + { + mSaleType = lookup(valuestr); + } + else if(0 == strcmp("sale_price", keyword)) + { + sscanf(valuestr, "%d", &mSalePrice); + mSalePrice = llclamp(mSalePrice, 0, S32_MAX); + } + else if (!strcmp("perm_mask", keyword)) + { + //llinfos << "found deprecated keyword perm_mask" << llendl; + has_perm_mask = TRUE; + sscanf(valuestr, "%x", &perm_mask); + } + else + { + llwarns << "unknown keyword '" << keyword + << "' in sale info import" << llendl; + } + } + return success; +} + +void LLSaleInfo::setSalePrice(S32 price) +{ + mSalePrice = price; + mSalePrice = llclamp(mSalePrice, 0, S32_MAX); +} + +void LLSaleInfo::packMessage(LLMessageSystem* msg) const +{ + U8 sale_type = static_cast(mSaleType); + msg->addU8Fast(_PREHASH_SaleType, sale_type); + msg->addS32Fast(_PREHASH_SalePrice, mSalePrice); + //msg->addU32Fast(_PREHASH_NextOwnerMask, mNextOwnerPermMask); +} + +void LLSaleInfo::unpackMessage(LLMessageSystem* msg, const char* block) +{ + U8 sale_type; + msg->getU8Fast(block, _PREHASH_SaleType, sale_type); + mSaleType = static_cast(sale_type); + msg->getS32Fast(block, _PREHASH_SalePrice, mSalePrice); + mSalePrice = llclamp(mSalePrice, 0, S32_MAX); + //msg->getU32Fast(block, _PREHASH_NextOwnerMask, mNextOwnerPermMask); +} + +void LLSaleInfo::unpackMultiMessage(LLMessageSystem* msg, const char* block, + S32 block_num) +{ + U8 sale_type; + msg->getU8Fast(block, _PREHASH_SaleType, sale_type, block_num); + mSaleType = static_cast(sale_type); + msg->getS32Fast(block, _PREHASH_SalePrice, mSalePrice, block_num); + mSalePrice = llclamp(mSalePrice, 0, S32_MAX); + //msg->getU32Fast(block, _PREHASH_NextOwnerMask, mNextOwnerPermMask, block_num); +} + +LLSaleInfo::EForSale LLSaleInfo::lookup(const char* name) +{ + for(S32 i = 0; i < FS_COUNT; i++) + { + if(0 == strcmp(name, FOR_SALE_NAMES[i])) + { + // match + return (EForSale)i; + } + } + return FS_NOT; +} + +const char* LLSaleInfo::lookup(EForSale type) +{ + if((type >= 0) && (type < FS_COUNT)) + { + return FOR_SALE_NAMES[S32(type)]; + } + else + { + return NULL; + } +} + +// Allow accumulation of sale info. The price of each is added, +// conflict in sale type results in FS_NOT, and the permissions are +// tightened. +void LLSaleInfo::accumulate(const LLSaleInfo& sale_info) +{ + if(mSaleType != sale_info.mSaleType) + { + mSaleType = FS_NOT; + } + mSalePrice += sale_info.mSalePrice; + //mNextOwnerPermMask &= sale_info.mNextOwnerPermMask; +} + +bool LLSaleInfo::operator==(const LLSaleInfo &rhs) const +{ + return ( + (mSaleType == rhs.mSaleType) && + (mSalePrice == rhs.mSalePrice) + ); +} + +bool LLSaleInfo::operator!=(const LLSaleInfo &rhs) const +{ + return ( + (mSaleType != rhs.mSaleType) || + (mSalePrice != rhs.mSalePrice) + ); +} + + +///---------------------------------------------------------------------------- +/// Local function definitions +///---------------------------------------------------------------------------- + +///---------------------------------------------------------------------------- +/// exported functions +///---------------------------------------------------------------------------- +static const std::string ST_TYPE_LABEL("sale_type"); +static const std::string ST_PRICE_LABEL("sale_price"); + +LLSD ll_create_sd_from_sale_info(const LLSaleInfo& sale) +{ + LLSD rv; + const char* type = LLSaleInfo::lookup(sale.getSaleType()); + if(!type) type = LLSaleInfo::lookup(LLSaleInfo::FS_NOT); + rv[ST_TYPE_LABEL] = type; + rv[ST_PRICE_LABEL] = sale.getSalePrice(); + return rv; +} + +LLSaleInfo ll_sale_info_from_sd(const LLSD& sd) +{ + LLSaleInfo rv; + rv.setSaleType(LLSaleInfo::lookup(sd[ST_TYPE_LABEL].asString().c_str())); + rv.setSalePrice(llclamp((S32)sd[ST_PRICE_LABEL], 0, S32_MAX)); + return rv; +} diff --git a/indra/llinventory/llsaleinfo.h b/indra/llinventory/llsaleinfo.h new file mode 100644 index 0000000000..2eceea87ef --- /dev/null +++ b/indra/llinventory/llsaleinfo.h @@ -0,0 +1,110 @@ +/** + * @file llsaleinfo.h + * @brief LLSaleInfo class header file. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSALEINFO_H +#define LL_LLSALEINFO_H + +#include +#include + +#include "llpermissionsflags.h" +#include "llsd.h" +#include "llxmlnode.h" + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLSaleInfo +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +// L$ default price for objects +const S32 DEFAULT_PRICE = 10; + +class LLMessageSystem; + +class LLSaleInfo +{ +public: + // use this to avoid temporary object creation + static const LLSaleInfo DEFAULT; + + enum EForSale + { + // item is not to be considered for transactions + FS_NOT = 0, + + // the origional is on sale + FS_ORIGINAL = 1, + + // A copy is for sale + FS_COPY = 2, + + // Valid only for tasks, the inventory is for sale + // at the price in this structure. + FS_CONTENTS = 3, + + FS_COUNT + }; + +protected: + EForSale mSaleType; + S32 mSalePrice; + +public: + // default constructor is fine usually + LLSaleInfo(); + LLSaleInfo(EForSale sale_type, S32 sale_price); + + // accessors + BOOL isForSale() const; + EForSale getSaleType() const { return mSaleType; } + S32 getSalePrice() const { return mSalePrice; } + U32 getCRC32() const; + + // mutators + void setSaleType(EForSale type) { mSaleType = type; } + void setSalePrice(S32 price); + //void setNextOwnerPermMask(U32 mask) { mNextOwnerPermMask = mask; } + + + // file serialization + BOOL exportFile(FILE* fp) const; + BOOL importFile(FILE* fp, BOOL& has_perm_mask, U32& perm_mask); + + BOOL exportLegacyStream(std::ostream& output_stream) const; + LLSD asLLSD() const; + operator LLSD() const { return asLLSD(); } + bool fromLLSD(LLSD& sd, BOOL& has_perm_mask, U32& perm_mask); + BOOL importLegacyStream(std::istream& input_stream, BOOL& has_perm_mask, U32& perm_mask); + + LLXMLNode *exportFileXML() const; + BOOL importXML(LLXMLNode* node); + + // message serialization + void packMessage(LLMessageSystem* msg) const; + void unpackMessage(LLMessageSystem* msg, const char* block); + void unpackMultiMessage(LLMessageSystem* msg, const char* block, + S32 block_num); + + // static functionality for determine for sale status. + static EForSale lookup(const char* name); + static const char* lookup(EForSale type); + + // Allow accumulation of sale info. The price of each is added, + // conflict in sale type results in FS_NOT, and the permissions + // are tightened. + void accumulate(const LLSaleInfo& sale_info); + + bool operator==(const LLSaleInfo &rhs) const; + bool operator!=(const LLSaleInfo &rhs) const; +}; + +// These functions convert between structured data and sale info as +// appropriate for serialization. +LLSD ll_create_sd_from_sale_info(const LLSaleInfo& sale); +LLSaleInfo ll_sale_info_from_sd(const LLSD& sd); + +#endif // LL_LLSALEINFO_H diff --git a/indra/llinventory/lltransactionflags.cpp b/indra/llinventory/lltransactionflags.cpp new file mode 100644 index 0000000000..3f1aa14959 --- /dev/null +++ b/indra/llinventory/lltransactionflags.cpp @@ -0,0 +1,43 @@ +/** + * @file lltransactionflags.cpp + * @brief Some exported symbols and functions for dealing with + * transaction flags. + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lltransactionflags.h" + +const U8 TRANSACTION_FLAGS_NONE = 0; +const U8 TRANSACTION_FLAG_SOURCE_GROUP = 1; +const U8 TRANSACTION_FLAG_DEST_GROUP = 2; +const U8 TRANSACTION_FLAG_OWNER_GROUP = 4; +const U8 TRANSACTION_FLAG_SIMULTANEOUS_CONTRIBUTION = 8; +const U8 TRANSACTION_FLAG_SIMULTANEOUS_CONTRIBUTION_REMOVAL = 16; + +U8 pack_transaction_flags(BOOL is_source_group, BOOL is_dest_group) +{ + U8 rv = 0; + if(is_source_group) rv |= TRANSACTION_FLAG_SOURCE_GROUP; + if(is_dest_group) rv |= TRANSACTION_FLAG_DEST_GROUP; + return rv; +} + +BOOL is_tf_source_group(TransactionFlags flags) +{ + return ((flags & TRANSACTION_FLAG_SOURCE_GROUP) == TRANSACTION_FLAG_SOURCE_GROUP); +} + +BOOL is_tf_dest_group(TransactionFlags flags) +{ + return ((flags & TRANSACTION_FLAG_DEST_GROUP) == TRANSACTION_FLAG_DEST_GROUP); +} + +BOOL is_tf_owner_group(TransactionFlags flags) +{ + return ((flags & TRANSACTION_FLAG_OWNER_GROUP) == TRANSACTION_FLAG_OWNER_GROUP); +} + diff --git a/indra/llinventory/lltransactionflags.h b/indra/llinventory/lltransactionflags.h new file mode 100644 index 0000000000..eaa138fef7 --- /dev/null +++ b/indra/llinventory/lltransactionflags.h @@ -0,0 +1,27 @@ +/** + * @file lltransactionflags.h + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTRANSACTIONFLAGS_H +#define LL_LLTRANSACTIONFLAGS_H + +typedef U8 TransactionFlags; + +// defined in common/llinventory/lltransactionflags.cpp +extern const TransactionFlags TRANSACTION_FLAGS_NONE; +extern const TransactionFlags TRANSACTION_FLAG_SOURCE_GROUP; +extern const TransactionFlags TRANSACTION_FLAG_DEST_GROUP; +extern const TransactionFlags TRANSACTION_FLAG_OWNER_GROUP; +extern const TransactionFlags TRANSACTION_FLAG_SIMULTANEOUS_CONTRIBUTION; +extern const TransactionFlags TRANSACTION_FLAG_SIMULTANEOUS_CONTRIBUTION_REMOVAL; + +// very simple helper functions +TransactionFlags pack_transaction_flags(BOOL is_source_group, BOOL is_dest_group); +BOOL is_tf_source_group(TransactionFlags flags); +BOOL is_tf_dest_group(TransactionFlags flags); +BOOL is_tf_owner_group(TransactionFlags flags); + +#endif // LL_LLTRANSACTIONFLAGS_H diff --git a/indra/llinventory/lltransactiontypes.h b/indra/llinventory/lltransactiontypes.h new file mode 100644 index 0000000000..d7894b2fdb --- /dev/null +++ b/indra/llinventory/lltransactiontypes.h @@ -0,0 +1,93 @@ +/** + * @file lltransactiontypes.h + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTRANSACTIONTYPES_H +#define LL_LLTRANSACTIONTYPES_H + +// *NOTE: The constants in this file are also in the +// transaction_description table in the database. If you add a +// constant here, please add it to the database. eg: +// +// insert into transaction_description +// set type = 1000, description = 'Object Claim'; +// +// Also add it to the various money string lookups on the dataserver +// in lldatamoney + +// Money transaction failure codes +const U8 TRANS_FAIL_SIMULATOR_TIMEOUT = 1; +const U8 TRANS_FAIL_DATASERVER_TIMEOUT = 2; + +// Codes up to 999 for error conditions +const S32 TRANS_NULL = 0; + +// Codes 1000-1999 reserved for one-time charges +const S32 TRANS_OBJECT_CLAIM = 1000; +const S32 TRANS_LAND_CLAIM = 1001; +const S32 TRANS_GROUP_CREATE = 1002; +const S32 TRANS_OBJECT_PUBLIC_CLAIM = 1003; +const S32 TRANS_GROUP_JOIN = 1004; // May be moved to group transactions eventually +const S32 TRANS_TELEPORT_CHARGE = 1100; // FF not sure why this jumps to 1100... +const S32 TRANS_UPLOAD_CHARGE = 1101; +const S32 TRANS_LAND_AUCTION = 1102; +const S32 TRANS_CLASSIFIED_CHARGE = 1103; + +// Codes 2000-2999 reserved for recurrent charges +const S32 TRANS_OBJECT_TAX = 2000; +const S32 TRANS_LAND_TAX = 2001; +const S32 TRANS_LIGHT_TAX = 2002; +const S32 TRANS_PARCEL_DIR_FEE = 2003; +const S32 TRANS_GROUP_TAX = 2004; // Taxes incurred as part of group membership +const S32 TRANS_CLASSIFIED_RENEW = 2005; + +// Codes 3000-3999 reserved for inventory transactions +const S32 TRANS_GIVE_INVENTORY = 3000; + +// Codes 5000-5999 reserved for transfers between users +const S32 TRANS_OBJECT_SALE = 5000; +const S32 TRANS_GIFT = 5001; +const S32 TRANS_LAND_SALE = 5002; +const S32 TRANS_REFER_BONUS = 5003; +const S32 TRANS_INVENTORY_SALE = 5004; +const S32 TRANS_REFUND_PURCHASE = 5005; +const S32 TRANS_LAND_PASS_SALE = 5006; +const S32 TRANS_DWELL_BONUS = 5007; +const S32 TRANS_PAY_OBJECT = 5008; +const S32 TRANS_OBJECT_PAYS = 5009; + +// Codes 6000-6999 reserved for group transactions +//const S32 TRANS_GROUP_JOIN = 6000; //reserved for future use +const S32 TRANS_GROUP_LAND_DEED = 6001; +const S32 TRANS_GROUP_OBJECT_DEED = 6002; +const S32 TRANS_GROUP_LIABILITY = 6003; +const S32 TRANS_GROUP_DIVIDEND = 6004; +const S32 TRANS_MEMBERSHIP_DUES = 6005; + +// Codes 8000-8999 reserved for one-type credits +const S32 TRANS_OBJECT_RELEASE = 8000; +const S32 TRANS_LAND_RELEASE = 8001; +const S32 TRANS_OBJECT_DELETE = 8002; +const S32 TRANS_OBJECT_PUBLIC_DECAY = 8003; +const S32 TRANS_OBJECT_PUBLIC_DELETE= 8004; + +// Code 9000-9099 reserved for usertool transactions +const S32 TRANS_LINDEN_ADJUSTMENT = 9000; +const S32 TRANS_LINDEN_GRANT = 9001; +const S32 TRANS_LINDEN_PENALTY = 9002; +const S32 TRANS_EVENT_FEE = 9003; +const S32 TRANS_EVENT_PRIZE = 9004; + +// These must match entries in money_stipend table in MySQL +// Codes 10000-10999 reserved for stipend credits +const S32 TRANS_STIPEND_BASIC = 10000; +const S32 TRANS_STIPEND_DEVELOPER = 10001; +const S32 TRANS_STIPEND_ALWAYS = 10002; +const S32 TRANS_STIPEND_DAILY = 10003; +const S32 TRANS_STIPEND_RATING = 10004; +const S32 TRANS_STIPEND_DELTA = 10005; + +#endif diff --git a/indra/llinventory/lluserrelations.cpp b/indra/llinventory/lluserrelations.cpp new file mode 100644 index 0000000000..c10f6f610a --- /dev/null +++ b/indra/llinventory/lluserrelations.cpp @@ -0,0 +1,89 @@ +/** + * @file lluserrealations.cpp + * @author Phoenix + * @date 2006-10-12 + * @brief Implementation of a simple cache of user relations. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "lluserrelations.h" + +// static +const U8 LLRelationship::GRANTED_VISIBLE_MASK = LLRelationship::GRANT_MODIFY_OBJECTS | LLRelationship::GRANT_MAP_LOCATION; +const LLRelationship LLRelationship::DEFAULT_RELATIONSHIP = LLRelationship(GRANT_ONLINE_STATUS, GRANT_ONLINE_STATUS, false); + +LLRelationship::LLRelationship() : + mGrantToAgent(0), + mGrantFromAgent(0), + mIsOnline(false) +{ +} + +LLRelationship::LLRelationship(S32 grant_to, S32 grant_from, bool is_online) : + mGrantToAgent(grant_to), + mGrantFromAgent(grant_from), + mIsOnline(is_online) +{ +} + +bool LLRelationship::isOnline() const +{ + return mIsOnline; +} + +void LLRelationship::online(bool is_online) +{ + mIsOnline = is_online; +} + +bool LLRelationship::isRightGrantedTo(S32 rights) const +{ + return ((mGrantToAgent & rights) == rights); +} + +bool LLRelationship::isRightGrantedFrom(S32 rights) const +{ + return ((mGrantFromAgent & rights) == rights); +} + +S32 LLRelationship::getRightsGrantedTo() const +{ + return mGrantToAgent; +} + +S32 LLRelationship::getRightsGrantedFrom() const +{ + return mGrantFromAgent; +} + +void LLRelationship::grantRights(S32 to_agent, S32 from_agent) +{ + mGrantToAgent |= to_agent; + mGrantFromAgent |= from_agent; +} + +void LLRelationship::revokeRights(S32 to_agent, S32 from_agent) +{ + mGrantToAgent &= ~to_agent; + mGrantFromAgent &= ~from_agent; +} + + + +/* +bool LLGrantedRights::getNextRights( + LLUUID& agent_id, + S32& to_agent, + S32& from_agent) const +{ + rights_map_t::const_iterator iter = mRights.upper_bound(agent_id); + if(iter == mRights.end()) return false; + agent_id = (*iter).first; + to_agent = (*iter).second.mToAgent; + from_agent = (*iter).second.mFromAgent; + return true; +} +*/ diff --git a/indra/llinventory/lluserrelations.h b/indra/llinventory/lluserrelations.h new file mode 100644 index 0000000000..7c24254339 --- /dev/null +++ b/indra/llinventory/lluserrelations.h @@ -0,0 +1,154 @@ +/** + * @file llluserrelations.h + * @author Phoenix + * @date 2006-10-12 + * @brief Declaration of a class for handling granted rights. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLUSERRELAIONS_H +#define LL_LLUSERRELAIONS_H + +#include +#include "lluuid.h" + +/** + * @class LLRelationship + * + * This class represents a relationship between two agents, where the + * related agent is stored and the other agent is the relationship is + * implicit by container ownership. + * This is merely a cache of this information used by the sim + * and viewer. + * + * You are expected to use this in a map or similar structure, eg: + * typedef std::map agent_relationship_map; + */ +class LLRelationship +{ +public: + /** + * @brief Constructors. + */ + LLRelationship(); + LLRelationship(S32 grant_to, S32 grant_from, bool is_online); + + static const LLRelationship DEFAULT_RELATIONSHIP; + + /** + * @name Status functionality + * + * I thought it would be keen to have a generic status interface, + * but the only thing we currently cache is online status. As this + * assumption changes, this API may evolve. + */ + //@{ + /** + * @brief Does this instance believe the related agent is currently + * online or available. + * + * NOTE: This API may be deprecated if there is any transient status + * other than online status, for example, away/busy/etc. + * + * This call does not check any kind of central store or make any + * deep information calls - it simply checks a cache of online + * status. + * @return Returns true if this relationship believes the agent is + * online. + */ + bool isOnline() const; + + /** + * @brief Set the online status. + * + * NOTE: This API may be deprecated if there is any transient status + * other than online status. + * @param is_online Se the online status + */ + void online(bool is_online); + //@} + + /* @name Granted rights + */ + //@{ + /** + * @brief Anonymous enumeration for specifying rights. + */ + enum + { + GRANT_NONE = 0x0, + GRANT_ONLINE_STATUS = 0x1, + GRANT_MAP_LOCATION = 0x2, + GRANT_MODIFY_OBJECTS = 0x4, + }; + + /** + * ??? + */ + static const U8 GRANTED_VISIBLE_MASK; + + /** + * @brief Check for a set of rights granted to agent. + * + * @param rights A bitfield to check for rights. + * @return Returns true if all rights have been granted. + */ + bool isRightGrantedTo(S32 rights) const; + + /** + * @brief Check for a set of rights granted from an agent. + * + * @param rights A bitfield to check for rights. + * @return Returns true if all rights have been granted. + */ + bool isRightGrantedFrom(S32 rights) const; + + /** + * @brief Get the rights granted to the other agent. + * + * @return Returns the bitmask of granted rights. + */ + S32 getRightsGrantedTo() const; + + /** + * @brief Get the rights granted from the other agent. + * + * @return Returns the bitmask of granted rights. + */ + S32 getRightsGrantedFrom() const; + + void setRightsTo(S32 to_agent) { mGrantToAgent = to_agent; } + void setRightsFrom(S32 from_agent) { mGrantFromAgent = from_agent; } + + /** + * @brief Grant a set of rights. + * + * Any bit which is set will grant that right if it is set in the + * instance. You can pass in LLGrantedRights::NONE to not change + * that field. + * @param to_agent The rights to grant to agent_id. + * @param from_agent The rights granted from agent_id. + */ + void grantRights(S32 to_agent, S32 from_agent); + + /** + * @brief Revoke a set of rights. + * + * Any bit which is set will revoke that right if it is set in the + * instance. You can pass in LLGrantedRights::NONE to not change + * that field. + * @param to_agent The rights to grant to agent_id. + * @param from_agent The rights granted from agent_id. + */ + void revokeRights(S32 to_agent, S32 from_agent); + //@} + +protected: + S32 mGrantToAgent; + S32 mGrantFromAgent; + bool mIsOnline; +}; + +#endif // LL_LLUSERRELAIONS_H diff --git a/indra/llmath/camera.h b/indra/llmath/camera.h new file mode 100644 index 0000000000..f130a036a1 --- /dev/null +++ b/indra/llmath/camera.h @@ -0,0 +1,9 @@ +/** + * @file camera.h + * @brief Legacy wrapper header. + * + * Copyright (c) 2000-$CurrentYear$ Linden Research, Inc. + * $License$ + */ + +#include "llcamera.h" diff --git a/indra/llmath/coordframe.h b/indra/llmath/coordframe.h new file mode 100644 index 0000000000..5efab4b63e --- /dev/null +++ b/indra/llmath/coordframe.h @@ -0,0 +1,9 @@ +/** + * @file coordframe.h + * @brief Legacy wrapper header. + * + * Copyright (c) 2000-$CurrentYear$ Linden Research, Inc. + * $License$ + */ + +#include "llcoordframe.h" diff --git a/indra/llmath/llbboxlocal.cpp b/indra/llmath/llbboxlocal.cpp new file mode 100644 index 0000000000..ba0d4f38ed --- /dev/null +++ b/indra/llmath/llbboxlocal.cpp @@ -0,0 +1,37 @@ +/** + * @file llbboxlocal.cpp + * @brief General purpose bounding box class (Not axis aligned). + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llbboxlocal.h" +#include "m4math.h" + +void LLBBoxLocal::addPoint(const LLVector3& p) +{ + mMin.mV[VX] = llmin( p.mV[VX], mMin.mV[VX] ); + mMin.mV[VY] = llmin( p.mV[VY], mMin.mV[VY] ); + mMin.mV[VZ] = llmin( p.mV[VZ], mMin.mV[VZ] ); + mMax.mV[VX] = llmax( p.mV[VX], mMax.mV[VX] ); + mMax.mV[VY] = llmax( p.mV[VY], mMax.mV[VY] ); + mMax.mV[VZ] = llmax( p.mV[VZ], mMax.mV[VZ] ); +} + +void LLBBoxLocal::expand( F32 delta ) +{ + mMin.mV[VX] -= delta; + mMin.mV[VY] -= delta; + mMin.mV[VZ] -= delta; + mMax.mV[VX] += delta; + mMax.mV[VY] += delta; + mMax.mV[VZ] += delta; +} + +LLBBoxLocal operator*(const LLBBoxLocal &a, const LLMatrix4 &b) +{ + return LLBBoxLocal( a.mMin * b, a.mMax * b ); +} diff --git a/indra/llmath/llbboxlocal.h b/indra/llmath/llbboxlocal.h new file mode 100644 index 0000000000..c8b7fbbbc8 --- /dev/null +++ b/indra/llmath/llbboxlocal.h @@ -0,0 +1,50 @@ +/** + * @file llbboxlocal.h + * @brief General purpose bounding box class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_BBOXLOCAL_H +#define LL_BBOXLOCAL_H + +#include "v3math.h" + +class LLMatrix4; + +class LLBBoxLocal +{ +public: + LLBBoxLocal() {} + LLBBoxLocal( const LLVector3& min, const LLVector3& max ) : mMin( min ), mMax( max ) {} + // Default copy constructor is OK. + + const LLVector3& getMin() const { return mMin; } + void setMin( const LLVector3& min ) { mMin = min; } + + const LLVector3& getMax() const { return mMax; } + void setMax( const LLVector3& max ) { mMax = max; } + + LLVector3 getCenter() const { return (mMax - mMin) * 0.5f + mMin; } + LLVector3 getExtent() const { return mMax - mMin; } + + BOOL containsPoint(const LLVector3& p) const; + BOOL intersects(const LLBBoxLocal& b) const; + + void addPoint(const LLVector3& p); + void addBBox(const LLBBoxLocal& b) { addPoint( b.mMin ); addPoint( b.mMax ); } + + void expand( F32 delta ); + + friend LLBBoxLocal operator*(const LLBBoxLocal& a, const LLMatrix4& b); + +private: + LLVector3 mMin; + LLVector3 mMax; +}; + +LLBBoxLocal operator*(const LLBBoxLocal &a, const LLMatrix4 &b); + + +#endif // LL_BBOXLOCAL_H diff --git a/indra/llmath/llcamera.cpp b/indra/llmath/llcamera.cpp new file mode 100644 index 0000000000..675659c68a --- /dev/null +++ b/indra/llmath/llcamera.cpp @@ -0,0 +1,591 @@ +/** + * @file llcamera.cpp + * @brief Implementation of the LLCamera class. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llmath.h" +#include "llcamera.h" + +// ---------------- Constructors and destructors ---------------- + +LLCamera::LLCamera() : + LLCoordFrame(), + mView(DEFAULT_FIELD_OF_VIEW), + mAspect(DEFAULT_ASPECT_RATIO), + mViewHeightInPixels( -1 ), // invalid height + mNearPlane(DEFAULT_NEAR_PLANE), + mFarPlane(DEFAULT_FAR_PLANE), + mFixedDistance(-1.f) +{ + calculateFrustumPlanes(); +} + + +LLCamera::LLCamera(F32 z_field_of_view, F32 aspect_ratio, S32 view_height_in_pixels, F32 near_plane, F32 far_plane) : + LLCoordFrame(), + mView(z_field_of_view), + mAspect(aspect_ratio), + mViewHeightInPixels(view_height_in_pixels), + mNearPlane(near_plane), + mFarPlane(far_plane), + mFixedDistance(-1.f) +{ + if (mView < MIN_FIELD_OF_VIEW) { mView = MIN_FIELD_OF_VIEW; } + else if (mView > MAX_FIELD_OF_VIEW) { mView = MAX_FIELD_OF_VIEW; } + + if (mAspect < MIN_ASPECT_RATIO) { mAspect = MIN_ASPECT_RATIO; } + else if (mAspect > MAX_ASPECT_RATIO) { mAspect = MAX_ASPECT_RATIO; } + + if (mNearPlane < MIN_NEAR_PLANE) { mNearPlane = MIN_NEAR_PLANE; } + else if (mNearPlane > MAX_NEAR_PLANE) { mNearPlane = MAX_NEAR_PLANE; } + + if (mFarPlane < 0) { mFarPlane = DEFAULT_FAR_PLANE; } + else if (mFarPlane < MIN_FAR_PLANE) { mFarPlane = MIN_FAR_PLANE; } + else if (mFarPlane > MAX_FAR_PLANE) { mFarPlane = MAX_FAR_PLANE; } + + calculateFrustumPlanes(); +} + + + +// ---------------- LLCamera::setFoo() member functions ---------------- + +void LLCamera::setView(F32 field_of_view) +{ + mView = field_of_view; + if (mView < MIN_FIELD_OF_VIEW) { mView = MIN_FIELD_OF_VIEW; } + else if (mView > MAX_FIELD_OF_VIEW) { mView = MAX_FIELD_OF_VIEW; } + calculateFrustumPlanes(); +} + +void LLCamera::setViewHeightInPixels(S32 height) +{ + mViewHeightInPixels = height; + + // Don't really need to do this, but update the pixel meter ratio with it. + calculateFrustumPlanes(); +} + +void LLCamera::setAspect(F32 aspect_ratio) +{ + mAspect = aspect_ratio; + if (mAspect < MIN_ASPECT_RATIO) { mAspect = MIN_ASPECT_RATIO; } + else if (mAspect > MAX_ASPECT_RATIO) { mAspect = MAX_ASPECT_RATIO; } + calculateFrustumPlanes(); +} + + +void LLCamera::setNear(F32 near_plane) +{ + mNearPlane = near_plane; + if (mNearPlane < MIN_NEAR_PLANE) { mNearPlane = MIN_NEAR_PLANE; } + else if (mNearPlane > MAX_NEAR_PLANE) { mNearPlane = MAX_NEAR_PLANE; } + calculateFrustumPlanes(); +} + + +void LLCamera::setFar(F32 far_plane) +{ + mFarPlane = far_plane; + if (mFarPlane < MIN_FAR_PLANE) { mFarPlane = MIN_FAR_PLANE; } + else if (mFarPlane > MAX_FAR_PLANE) { mFarPlane = MAX_FAR_PLANE; } + calculateFrustumPlanes(); +} + + +// ---------------- read/write to buffer ---------------- + +size_t LLCamera::writeFrustumToBuffer(char *buffer) const +{ + memcpy(buffer, &mView, sizeof(F32)); + buffer += sizeof(F32); + memcpy(buffer, &mAspect, sizeof(F32)); + buffer += sizeof(F32); + memcpy(buffer, &mNearPlane, sizeof(F32)); + buffer += sizeof(F32); + memcpy(buffer, &mFarPlane, sizeof(F32)); + return 4*sizeof(F32); +} + +size_t LLCamera::readFrustumFromBuffer(const char *buffer) +{ + memcpy(&mView, buffer, sizeof(F32)); + buffer += sizeof(F32); + memcpy(&mAspect, buffer, sizeof(F32)); + buffer += sizeof(F32); + memcpy(&mNearPlane, buffer, sizeof(F32)); + buffer += sizeof(F32); + memcpy(&mFarPlane, buffer, sizeof(F32)); + return 4*sizeof(F32); +} + + +// ---------------- test methods ---------------- + +int LLCamera::AABBInFrustum(const LLVector3 ¢er, const LLVector3& radius) +{ + static const LLVector3 scaler[] = { + LLVector3(-1,-1,-1), + LLVector3( 1,-1,-1), + LLVector3(-1, 1,-1), + LLVector3( 1, 1,-1), + LLVector3(-1,-1, 1), + LLVector3( 1,-1, 1), + LLVector3(-1, 1, 1), + LLVector3( 1, 1, 1) + }; + + U8 mask = 0; + S32 result = 2; + + for (int i = 0; i < 6; i++) + { + mask = mAgentPlaneMask[i]; + LLPlane p = mAgentPlanes[i]; + LLVector3 n = LLVector3(p); + float d = p.mV[3]; + LLVector3 rscale = radius.scaledVec(scaler[mask]); + + LLVector3 minp = center - rscale; + LLVector3 maxp = center + rscale; + + if (n * minp > -d) + { + return 0; + } + + if (n * maxp > -d) + { + result = 1; + } + } + + return result; +} + +int LLCamera::sphereInFrustumQuick(const LLVector3 &sphere_center, const F32 radius) +{ + LLVector3 dist = sphere_center-mFrustCenter; + float dsq = dist * dist; + float rsq = mFarPlane*0.5f + radius; + rsq *= rsq; + + if (dsq < rsq) + { + return 1; + } + + return 0; +} + +// HACK: This version is still around because the version below doesn't work +// unless the agent planes are initialized. +// Return 1 if sphere is in frustum, 2 if fully in frustum, otherwise 0. +// NOTE: 'center' is in absolute frame. +int LLCamera::sphereInFrustumOld(const LLVector3 &sphere_center, const F32 radius) const +{ + // Returns 1 if sphere is in frustum, 0 if not. + // modified so that default view frust is along X with Z vertical + F32 x, y, z, rightDist, leftDist, topDist, bottomDist; + + // Subtract the view position + //LLVector3 relative_center; + //relative_center = sphere_center - getOrigin(); + LLVector3 rel_center(sphere_center); + rel_center -= mOrigin; + + bool all_in = TRUE; + + // Transform relative_center.x to camera frame + x = mXAxis * rel_center; + if (x < MIN_NEAR_PLANE - radius) + { + return 0; + } + else if (x < MIN_NEAR_PLANE + radius) + { + all_in = FALSE; + } + + if (x > mFarPlane + radius) + { + return 0; + } + else if (x > mFarPlane - radius) + { + all_in = FALSE; + } + + // Transform relative_center.y to camera frame + y = mYAxis * rel_center; + + // distance to plane is the dot product of (x, y, 0) * plane_normal + rightDist = x * mLocalPlanes[PLANE_RIGHT][VX] + y * mLocalPlanes[PLANE_RIGHT][VY]; + if (rightDist < -radius) + { + return 0; + } + else if (rightDist < radius) + { + all_in = FALSE; + } + + leftDist = x * mLocalPlanes[PLANE_LEFT][VX] + y * mLocalPlanes[PLANE_LEFT][VY]; + if (leftDist < -radius) + { + return 0; + } + else if (leftDist < radius) + { + all_in = FALSE; + } + + // Transform relative_center.y to camera frame + z = mZAxis * rel_center; + + topDist = x * mLocalPlanes[PLANE_TOP][VX] + z * mLocalPlanes[PLANE_TOP][VZ]; + if (topDist < -radius) + { + return 0; + } + else if (topDist < radius) + { + all_in = FALSE; + } + + bottomDist = x * mLocalPlanes[PLANE_BOTTOM][VX] + z * mLocalPlanes[PLANE_BOTTOM][VZ]; + if (bottomDist < -radius) + { + return 0; + } + else if (bottomDist < radius) + { + all_in = FALSE; + } + + if (all_in) + { + return 2; + } + + return 1; +} + + +// HACK: This (presumably faster) version only currently works if you set up the +// frustum planes using GL. At some point we should get those planes through another +// mechanism, and then we can get rid of the "old" version above. + +// Return 1 if sphere is in frustum, 2 if fully in frustum, otherwise 0. +// NOTE: 'center' is in absolute frame. +int LLCamera::sphereInFrustum(const LLVector3 &sphere_center, const F32 radius) const +{ + // Returns 1 if sphere is in frustum, 0 if not. + int res = 2; + for (int i = 0; i < 6; i++) + { + float d = mAgentPlanes[i].dist(sphere_center); + + if (d > radius) + { + return 0; + } + + if (d > -radius) + { + res = 1; + } + } + + return res; +} + + +// return height of a sphere of given radius, located at center, in pixels +F32 LLCamera::heightInPixels(const LLVector3 ¢er, F32 radius ) const +{ + if (radius == 0.f) return 0.f; + + // If height initialized + if (mViewHeightInPixels > -1) + { + // Convert sphere to coord system with 0,0,0 at camera + LLVector3 vec = center - mOrigin; + + // Compute distance to sphere + F32 dist = vec.magVec(); + + // Calculate angle of whole object + F32 angle = 2.0f * (F32) atan2(radius, dist); + + // Calculate fraction of field of view + F32 fraction_of_fov = angle / mView; + + // Compute number of pixels tall, based on vertical field of view + return (fraction_of_fov * mViewHeightInPixels); + } + else + { + // return invalid height + return -1.0f; + } +} + +// If pos is visible, return the distance from pos to the camera. +// Use fudge distance to scale rad against top/bot/left/right planes +// Otherwise, return -distance +F32 LLCamera::visibleDistance(const LLVector3 &pos, F32 rad, F32 fudgedist, U32 planemask) const +{ + if (mFixedDistance > 0) + { + return mFixedDistance; + } + LLVector3 dvec = pos - mOrigin; + // Check visibility + F32 dist = dvec.magVec(); + if (dist > rad) + { + F32 dp,tdist; + dp = dvec * mXAxis; + if (dp < -rad) + return -dist; + + rad *= fudgedist; + LLVector3 tvec(pos); + for (int p=0; p rad) + return -dist; + } + } + return dist; +} + +// Like visibleDistance, except uses mHorizPlanes[], which are left and right +// planes perpindicular to (0,0,1) in world space +F32 LLCamera::visibleHorizDistance(const LLVector3 &pos, F32 rad, F32 fudgedist, U32 planemask) const +{ + if (mFixedDistance > 0) + { + return mFixedDistance; + } + LLVector3 dvec = pos - mOrigin; + // Check visibility + F32 dist = dvec.magVec(); + if (dist > rad) + { + rad *= fudgedist; + LLVector3 tvec(pos); + for (int p=0; p rad) + return -dist; + } + } + return dist; +} + +// ---------------- friends and operators ---------------- + +std::ostream& operator<<(std::ostream &s, const LLCamera &C) +{ + s << "{ \n"; + s << " Center = " << C.getOrigin() << "\n"; + s << " AtAxis = " << C.getXAxis() << "\n"; + s << " LeftAxis = " << C.getYAxis() << "\n"; + s << " UpAxis = " << C.getZAxis() << "\n"; + s << " View = " << C.getView() << "\n"; + s << " Aspect = " << C.getAspect() << "\n"; + s << " NearPlane = " << C.mNearPlane << "\n"; + s << " FarPlane = " << C.mFarPlane << "\n"; + s << " TopPlane = " << C.mLocalPlanes[LLCamera::PLANE_TOP][VX] << " " + << C.mLocalPlanes[LLCamera::PLANE_TOP][VY] << " " + << C.mLocalPlanes[LLCamera::PLANE_TOP][VZ] << "\n"; + s << " BottomPlane = " << C.mLocalPlanes[LLCamera::PLANE_BOTTOM][VX] << " " + << C.mLocalPlanes[LLCamera::PLANE_BOTTOM][VY] << " " + << C.mLocalPlanes[LLCamera::PLANE_BOTTOM][VZ] << "\n"; + s << " LeftPlane = " << C.mLocalPlanes[LLCamera::PLANE_LEFT][VX] << " " + << C.mLocalPlanes[LLCamera::PLANE_LEFT][VY] << " " + << C.mLocalPlanes[LLCamera::PLANE_LEFT][VZ] << "\n"; + s << " RightPlane = " << C.mLocalPlanes[LLCamera::PLANE_RIGHT][VX] << " " + << C.mLocalPlanes[LLCamera::PLANE_RIGHT][VY] << " " + << C.mLocalPlanes[LLCamera::PLANE_RIGHT][VZ] << "\n"; + s << "}"; + return s; +} + + + +// ---------------- private member functions ---------------- + +void LLCamera::calculateFrustumPlanes() +{ + // The planes only change when any of the frustum descriptions change. + // They are not affected by changes of the position of the Frustum + // because they are known in the view frame and the position merely + // provides information on how to get from the absolute frame to the + // view frame. + + F32 left,right,top,bottom; + top = mFarPlane * (F32)tanf(0.5f * mView); + bottom = -top; + left = top * mAspect; + right = -left; + + calculateFrustumPlanes(left, right, top, bottom); +} + +LLPlane planeFromPoints(LLVector3 p1, LLVector3 p2, LLVector3 p3) +{ + LLVector3 n = ((p2-p1)%(p3-p1)); + n.normVec(); + + return LLPlane(p1, n); +} + + +void LLCamera::calcAgentFrustumPlanes(LLVector3* frust) +{ + + for (int i = 0; i < 8; i++) + { + mAgentFrustum[i] = frust[i]; + } + + //frust contains the 8 points of the frustum, calculate 6 planes + + //order of planes is important, keep most likely to fail in the front of the list + + //near - frust[0], frust[1], frust[2] + mAgentPlanes[2] = planeFromPoints(frust[0], frust[1], frust[2]); + + //far + mAgentPlanes[5] = planeFromPoints(frust[5], frust[4], frust[6]); + + //left + mAgentPlanes[0] = planeFromPoints(frust[4], frust[0], frust[7]); + + //right + mAgentPlanes[1] = planeFromPoints(frust[1], frust[5], frust[6]); + + //top + mAgentPlanes[4] = planeFromPoints(frust[3], frust[2], frust[6]); + + //bottom + mAgentPlanes[3] = planeFromPoints(frust[1], frust[0], frust[4]); + + //cache plane octant facing mask for use in AABBInFrustum + for (int i = 0; i < 8; i++) + { + U8 mask = 0; + LLPlane p = mAgentPlanes[i]; + LLVector3 n = LLVector3(p); + + if (n.mV[0] >= 0) + { + mask |= 1; + } + if (n.mV[1] >= 0) + { + mask |= 2; + } + if (n.mV[2] >= 0) + { + mask |= 4; + } + mAgentPlaneMask[i] = mask; + } +} + +void LLCamera::calculateFrustumPlanes(F32 left, F32 right, F32 top, F32 bottom) +{ + LLVector3 a, b, c; + + // For each plane we need to define 3 points (LLVector3's) in camera view space. + // The order in which we pass the points to planeFromPoints() matters, because the + // plane normal has a degeneracy of 2; we want it pointing _into_ the frustum. + + a.setVec(0.0f, 0.0f, 0.0f); + b.setVec(mFarPlane, right, top); + c.setVec(mFarPlane, right, bottom); + mLocalPlanes[PLANE_RIGHT].setVec(a, b, c); + + c.setVec(mFarPlane, left, top); + mLocalPlanes[PLANE_TOP].setVec(a, c, b); + + b.setVec(mFarPlane, left, bottom); + mLocalPlanes[PLANE_LEFT].setVec(a, b, c); + + c.setVec(mFarPlane, right, bottom); + mLocalPlanes[PLANE_BOTTOM].setVec( a, c, b); + + //calculate center and radius squared of frustum in world absolute coordinates + mFrustCenter = X_AXIS*mFarPlane*0.5f; + mFrustCenter = transformToAbsolute(mFrustCenter); + mFrustRadiusSquared = mFarPlane*0.5f; + mFrustRadiusSquared *= mFrustRadiusSquared * 1.05f; //pad radius squared by 5% +} + +// x and y are in WINDOW space, so x = Y-Axis (left/right), y= Z-Axis(Up/Down) +void LLCamera::calculateFrustumPlanesFromWindow(F32 x1, F32 y1, F32 x2, F32 y2) +{ + F32 bottom, top, left, right; + F32 view_height = (F32)tanf(0.5f * mView) * mFarPlane; + F32 view_width = view_height * mAspect; + + left = x1 * -2.f * view_width; + right = x2 * -2.f * view_width; + bottom = y1 * 2.f * view_height; + top = y2 * 2.f * view_height; + + calculateFrustumPlanes(left, right, top, bottom); +} + +void LLCamera::calculateWorldFrustumPlanes() +{ + F32 d; + LLVector3 center = mOrigin - mXAxis*mNearPlane; + mWorldPlanePos = center; + for (int p=0; p<4; p++) + { + LLVector3 pnorm = LLVector3(mLocalPlanes[p]); + LLVector3 norm = rotateToAbsolute(pnorm); + norm.normVec(); + d = -(center * norm); + mWorldPlanes[p] = LLPlane(norm, d); + } + // horizontal planes, perpindicular to (0,0,1); + LLVector3 zaxis(0, 0, 1.0f); + F32 yaw = getYaw(); + { + LLVector3 tnorm = LLVector3(mLocalPlanes[PLANE_LEFT]); + tnorm.rotVec(yaw, zaxis); + d = -(mOrigin * tnorm); + mHorizPlanes[HORIZ_PLANE_LEFT] = LLPlane(tnorm, d); + } + { + LLVector3 tnorm = LLVector3(mLocalPlanes[PLANE_RIGHT]); + tnorm.rotVec(yaw, zaxis); + d = -(mOrigin * tnorm); + mHorizPlanes[HORIZ_PLANE_RIGHT] = LLPlane(tnorm, d); + } +} + +// NOTE: this is the OpenGL matrix that will transform the default OpenGL view +// (-Z=at, Y=up) to the default view of the LLCamera class (X=at, Z=up): +// +// F32 cfr_transform = { 0.f, 0.f, -1.f, 0.f, // -Z becomes X +// -1.f, 0.f, 0.f, 0.f, // -X becomes Y +// 0.f, 1.f, 0.f, 0.f, // Y becomes Z +// 0.f, 0.f, 0.f, 1.f }; diff --git a/indra/llmath/llcamera.h b/indra/llmath/llcamera.h new file mode 100644 index 0000000000..685f919ac4 --- /dev/null +++ b/indra/llmath/llcamera.h @@ -0,0 +1,169 @@ +/** + * @file llcamera.h + * @brief Header file for the LLCamera class. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_CAMERA_H +#define LL_CAMERA_H + + +#include "llmath.h" +#include "llcoordframe.h" +#include "llplane.h" + +const F32 DEFAULT_FIELD_OF_VIEW = 60.f * DEG_TO_RAD; +const F32 DEFAULT_ASPECT_RATIO = 640.f / 480.f; +const F32 DEFAULT_NEAR_PLANE = 0.25f; +const F32 DEFAULT_FAR_PLANE = 64.f; // far reaches across two horizontal, not diagonal, regions + +const F32 MAX_FIELD_OF_VIEW = F_PI; +const F32 MAX_ASPECT_RATIO = 50.0f; +const F32 MAX_NEAR_PLANE = 10.f; +const F32 MAX_FAR_PLANE = 100000.0f; //1000000.0f; // Max allowed. Not good Z precision though. +const F32 MAX_FAR_CLIP = 1024.0f; + +const F32 MIN_FIELD_OF_VIEW = 0.1f; +const F32 MIN_ASPECT_RATIO = 0.02f; +const F32 MIN_NEAR_PLANE = 0.1f; +const F32 MIN_FAR_PLANE = 0.2f; + +static const LLVector3 X_AXIS(1.f,0.f,0.f); +static const LLVector3 Y_AXIS(0.f,1.f,0.f); +static const LLVector3 Z_AXIS(0.f,0.f,1.f); + +static const LLVector3 NEG_X_AXIS(-1.f,0.f,0.f); +static const LLVector3 NEG_Y_AXIS(0.f,-1.f,0.f); +static const LLVector3 NEG_Z_AXIS(0.f,0.f,-1.f); + + +// An LLCamera is an LLCoorFrame with a view frustum. +// This means that it has several methods for moving it around +// that are inherited from the LLCoordFrame() class : +// +// setOrigin(), setAxes() +// translate(), rotate() +// roll(), pitch(), yaw() +// etc... + + +class LLCamera +: public LLCoordFrame +{ +public: + enum { + PLANE_LEFT = 0, + PLANE_RIGHT = 1, + PLANE_BOTTOM = 2, + PLANE_TOP = 3, + PLANE_NUM = 4 + }; + enum { + PLANE_LEFT_MASK = (1< +// (j)| 1 0 0 0 | | a d g 0 | | a d g 0 | +// | | 0 1 0 0 | * | b e h 0 | = | b e h 0 | +// V | 0 0 1 0 | | c f i 0 | | c f i 0 | +// |-x -y -z 1 | | 0 0 0 1 | |-(ax+by+cz) -(dx+ey+fz) -(gx+hy+iz) 1 | +// +// where {a,b,c} = x-axis +// {d,e,f} = y-axis +// {g,h,i} = z-axis +// {x,y,z} = origin + +void LLCoordFrame::getOpenGLTranslation(F32 *ogl_matrix) const +{ + *(ogl_matrix + 0) = 1.0f; + *(ogl_matrix + 1) = 0.0f; + *(ogl_matrix + 2) = 0.0f; + *(ogl_matrix + 3) = 0.0f; + + *(ogl_matrix + 4) = 0.0f; + *(ogl_matrix + 5) = 1.0f; + *(ogl_matrix + 6) = 0.0f; + *(ogl_matrix + 7) = 0.0f; + + *(ogl_matrix + 8) = 0.0f; + *(ogl_matrix + 9) = 0.0f; + *(ogl_matrix + 10) = 1.0f; + *(ogl_matrix + 11) = 0.0f; + + *(ogl_matrix + 12) = -mOrigin.mV[VX]; + *(ogl_matrix + 13) = -mOrigin.mV[VY]; + *(ogl_matrix + 14) = -mOrigin.mV[VZ]; + *(ogl_matrix + 15) = 1.0f; +} + + +void LLCoordFrame::getOpenGLRotation(F32 *ogl_matrix) const +{ + *(ogl_matrix + 0) = mXAxis.mV[VX]; + *(ogl_matrix + 4) = mXAxis.mV[VY]; + *(ogl_matrix + 8) = mXAxis.mV[VZ]; + + *(ogl_matrix + 1) = mYAxis.mV[VX]; + *(ogl_matrix + 5) = mYAxis.mV[VY]; + *(ogl_matrix + 9) = mYAxis.mV[VZ]; + + *(ogl_matrix + 2) = mZAxis.mV[VX]; + *(ogl_matrix + 6) = mZAxis.mV[VY]; + *(ogl_matrix + 10) = mZAxis.mV[VZ]; + + *(ogl_matrix + 3) = 0.0f; + *(ogl_matrix + 7) = 0.0f; + *(ogl_matrix + 11) = 0.0f; + + *(ogl_matrix + 12) = 0.0f; + *(ogl_matrix + 13) = 0.0f; + *(ogl_matrix + 14) = 0.0f; + *(ogl_matrix + 15) = 1.0f; +} + + +void LLCoordFrame::getOpenGLTransform(F32 *ogl_matrix) const +{ + *(ogl_matrix + 0) = mXAxis.mV[VX]; + *(ogl_matrix + 4) = mXAxis.mV[VY]; + *(ogl_matrix + 8) = mXAxis.mV[VZ]; + *(ogl_matrix + 12) = -mOrigin * mXAxis; + + *(ogl_matrix + 1) = mYAxis.mV[VX]; + *(ogl_matrix + 5) = mYAxis.mV[VY]; + *(ogl_matrix + 9) = mYAxis.mV[VZ]; + *(ogl_matrix + 13) = -mOrigin * mYAxis; + + *(ogl_matrix + 2) = mZAxis.mV[VX]; + *(ogl_matrix + 6) = mZAxis.mV[VY]; + *(ogl_matrix + 10) = mZAxis.mV[VZ]; + *(ogl_matrix + 14) = -mOrigin * mZAxis; + + *(ogl_matrix + 3) = 0.0f; + *(ogl_matrix + 7) = 0.0f; + *(ogl_matrix + 11) = 0.0f; + *(ogl_matrix + 15) = 1.0f; +} + + +// at and up_direction are presumed to be normalized +void LLCoordFrame::lookDir(const LLVector3 &at, const LLVector3 &up_direction) +{ + // Make sure 'at' and 'up_direction' are not parallel + // and that neither are zero-length vectors + LLVector3 left(up_direction % at); + if (left.isNull()) + { + //tweak lookat pos so we don't get a degenerate matrix + LLVector3 tempat(at[VX] + 0.01f, at[VY], at[VZ]); + tempat.normVec(); + left = (up_direction % tempat); + } + left.normVec(); + + LLVector3 up = at % left; + setAxes(at, left, up); +} + +void LLCoordFrame::lookDir(const LLVector3 &xuv) +{ + static LLVector3 up_direction(0.0f, 0.0f, 1.0f); + lookDir(xuv, up_direction); +} + +void LLCoordFrame::lookAt(const LLVector3 &origin, const LLVector3 &point_of_interest, const LLVector3 &up_direction) +{ + setOrigin(origin); + LLVector3 at(point_of_interest - origin); + at.normVec(); + lookDir(at, up_direction); +} + +void LLCoordFrame::lookAt(const LLVector3 &origin, const LLVector3 &point_of_interest) +{ + static LLVector3 up_direction(0.0f, 0.0f, 1.0f); + + setOrigin(origin); + LLVector3 at(point_of_interest - origin); + at.normVec(); + lookDir(at, up_direction); +} + + +// Operators and friends + +std::ostream& operator<<(std::ostream &s, const LLCoordFrame &C) +{ + s << "{ " + << " origin = " << C.mOrigin + << " x_axis = " << C.mXAxis + << " y_axis = " << C.mYAxis + << " z_axis = " << C.mZAxis + << " }"; + return s; +} + + + +// Private member functions + + +//EOF diff --git a/indra/llmath/llcoordframe.h b/indra/llmath/llcoordframe.h new file mode 100644 index 0000000000..ddd20c4491 --- /dev/null +++ b/indra/llmath/llcoordframe.h @@ -0,0 +1,156 @@ +/** + * @file llcoordframe.h + * @brief LLCoordFrame class header file. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_COORDFRAME_H +#define LL_COORDFRAME_H + +#include "v3math.h" +#include "v4math.h" +#include "llerror.h" + +// XXX : The constructors of the LLCoordFrame class assume that all vectors +// and quaternion being passed as arguments are normalized, and all matrix +// arguments are unitary. VERY BAD things will happen if these assumptions fail. +// Also, segfault hazzards exist in methods that accept F32* arguments. + + +class LLCoordFrame +{ +public: + LLCoordFrame(); // Inits at zero with identity rotation + explicit LLCoordFrame(const LLVector3 &origin); // Sets origin, and inits rotation = Identity + LLCoordFrame(const LLVector3 &x_axis, + const LLVector3 &y_axis, + const LLVector3 &z_axis); // Sets coordinate axes and inits origin at zero + LLCoordFrame(const LLVector3 &origin, + const LLVector3 &x_axis, + const LLVector3 &y_axis, + const LLVector3 &z_axis); // Sets the origin and coordinate axes + LLCoordFrame(const LLVector3 &origin, + const LLMatrix3 &rotation); // Sets axes to 3x3 matrix + LLCoordFrame(const LLVector3 &origin, + const LLVector3 &direction); // Sets origin and calls lookDir(direction) + explicit LLCoordFrame(const LLQuaternion &q); // Sets axes using q and inits mOrigin to zero + LLCoordFrame(const LLVector3 &origin, + const LLQuaternion &q); // Uses quaternion to init axes + explicit LLCoordFrame(const LLMatrix4 &mat); // Extracts frame from a 4x4 matrix + // The folowing two constructors are dangerous due to implicit casting and have been disabled - SJB + //LLCoordFrame(const F32 *origin, const F32 *rotation); // Assumes "origin" is 1x3 and "rotation" is 1x9 array + //LLCoordFrame(const F32 *origin_and_rotation); // Assumes "origin_and_rotation" is 1x12 array + + BOOL isFinite() { return mOrigin.isFinite() && mXAxis.isFinite() && mYAxis.isFinite() && mZAxis.isFinite(); } + + void reset(); + void resetAxes(); + + void setOrigin(F32 x, F32 y, F32 z); // Set mOrigin + void setOrigin(const LLVector3 &origin); + void setOrigin(const F32 *origin); + void setOrigin(const LLCoordFrame &frame); + + inline void setOriginX(F32 x) { mOrigin.mV[VX] = x; } + inline void setOriginY(F32 y) { mOrigin.mV[VY] = y; } + inline void setOriginZ(F32 z) { mOrigin.mV[VZ] = z; } + + void setAxes(const LLVector3 &x_axis, // Set axes + const LLVector3 &y_axis, + const LLVector3 &z_axis); + void setAxes(const LLMatrix3 &rotation_matrix); + void setAxes(const LLQuaternion &q); + void setAxes(const F32 *rotation_matrix); + void setAxes(const LLCoordFrame &frame); + + void translate(F32 x, F32 y, F32 z); // Move mOrgin + void translate(const LLVector3 &v); + void translate(const F32 *origin); + + void rotate(F32 angle, F32 x, F32 y, F32 z); // Move axes + void rotate(F32 angle, const LLVector3 &rotation_axis); + void rotate(const LLQuaternion &q); + void rotate(const LLMatrix3 &m); + + void orthonormalize(); // Makes sure axes are unitary and orthogonal. + + // These methods allow rotations in the LLCoordFrame's frame + void roll(F32 angle); // RH rotation about mXAxis, radians + void pitch(F32 angle); // RH rotation about mYAxis, radians + void yaw(F32 angle); // RH rotation about mZAxis, radians + + inline const LLVector3 &getOrigin() const { return mOrigin; } + + inline const LLVector3 &getXAxis() const { return mXAxis; } + inline const LLVector3 &getYAxis() const { return mYAxis; } + inline const LLVector3 &getZAxis() const { return mZAxis; } + + inline const LLVector3 &getAtAxis() const { return mXAxis; } + inline const LLVector3 &getLeftAxis() const { return mYAxis; } + inline const LLVector3 &getUpAxis() const { return mZAxis; } + + // These return representations of the rotation or orientation of the LLFrame + // it its absolute frame. That is, these rotations acting on the X-axis {1,0,0} + // will produce the mXAxis. + // LLMatrix3 getMatrix3() const; // Returns axes in 3x3 matrix + LLQuaternion getQuaternion() const; // Returns axes in quaternion form + + // Same as above, except it also includes the translation of the LLFrame + // LLMatrix4 getMatrix4() const; // Returns position and axes in 4x4 matrix + + // Returns matrix which expresses point in local frame in the parent frame + void getMatrixToParent(LLMatrix4 &mat) const; + // Returns matrix which expresses point in parent frame in the local frame + void getMatrixToLocal(LLMatrix4 &mat) const; // Returns matrix which expresses point in parent frame in the local frame + + void getRotMatrixToParent(LLMatrix4 &mat) const; + + // Copies mOrigin, then the three axes to buffer, returns number of bytes copied. + size_t writeOrientation(char *buffer) const; + + // Copies mOrigin, then the three axes from buffer, returns the number of bytes copied. + // Assumes the data in buffer is correct. + size_t readOrientation(const char *buffer); + + LLVector3 rotateToLocal(const LLVector3 &v) const; // Returns v' rotated to local + LLVector4 rotateToLocal(const LLVector4 &v) const; // Returns v' rotated to local + LLVector3 rotateToAbsolute(const LLVector3 &v) const; // Returns v' rotated to absolute + LLVector4 rotateToAbsolute(const LLVector4 &v) const; // Returns v' rotated to absolute + + LLVector3 transformToLocal(const LLVector3 &v) const; // Returns v' in local coord + LLVector4 transformToLocal(const LLVector4 &v) const; // Returns v' in local coord + LLVector3 transformToAbsolute(const LLVector3 &v) const; // Returns v' in absolute coord + LLVector4 transformToAbsolute(const LLVector4 &v) const; // Returns v' in absolute coord + + // Write coord frame orientation into provided array in OpenGL matrix format. + void getOpenGLTranslation(F32 *ogl_matrix) const; + void getOpenGLRotation(F32 *ogl_matrix) const; + void getOpenGLTransform(F32 *ogl_matrix) const; + + // lookDir orients to (xuv, presumed normalized) and does not affect origin + void lookDir(const LLVector3 &xuv, const LLVector3 &up); + void lookDir(const LLVector3 &xuv); // up = 0,0,1 + // lookAt orients to (point_of_interest - origin) and sets origin + void lookAt(const LLVector3 &origin, const LLVector3 &point_of_interest, const LLVector3 &up); + void lookAt(const LLVector3 &origin, const LLVector3 &point_of_interest); // up = 0,0,1 + + // deprecated + void setOriginAndLookAt(const LLVector3 &origin, const LLVector3 &up, const LLVector3 &point_of_interest) + { + lookAt(origin, point_of_interest, up); + } + + friend std::ostream& operator<<(std::ostream &s, const LLCoordFrame &C); + + // These vectors are in absolute frame + LLVector3 mOrigin; + LLVector3 mXAxis; + LLVector3 mYAxis; + LLVector3 mZAxis; +}; + + +#endif + diff --git a/indra/llmath/llinterp.h b/indra/llmath/llinterp.h new file mode 100644 index 0000000000..61bf1029fb --- /dev/null +++ b/indra/llmath/llinterp.h @@ -0,0 +1,400 @@ +/** + * @file llinterp.h + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLINTERP_H +#define LL_LLINTERP_H + +#include "math.h" + +// Class from which different types of interpolators can be derived + +class LLInterpVal +{ +public: + virtual ~LLInterpVal() {} + virtual void interp(LLInterpVal &target, const F32 frac); // Linear interpolation for each type +}; + +template +class LLInterp +{ +public: + LLInterp(); + virtual ~LLInterp() {} + + virtual void start(); + void update(const F32 time); + const Type &getCurVal() const; + + void setStartVal(const Type &start_val); + const Type &getStartVal() const; + + void setEndVal(const Type &target_val); + const Type &getEndVal() const; + + void setStartTime(const F32 time); + F32 getStartTime() const; + + void setEndTime(const F32 time); + F32 getEndTime() const; + + BOOL isActive() const; + BOOL isDone() const; + +protected: + F32 mStartTime; + F32 mEndTime; + F32 mDuration; + BOOL mActive; + BOOL mDone; + + Type mStartVal; + Type mEndVal; + + F32 mCurTime; + Type mCurVal; +}; + +template +class LLInterpLinear : public LLInterp +{ +public: + /*virtual*/ void start(); + void update(const F32 time); + F32 getCurFrac() const; +protected: + F32 mCurFrac; +}; + +template +class LLInterpExp : public LLInterpLinear +{ +public: + void update(const F32 time); +protected: +}; + +template +class LLInterpAttractor : public LLInterp +{ +public: + LLInterpAttractor(); + /*virtual*/ void start(); + void setStartVel(const Type &vel); + void setForce(const F32 force); + void update(const F32 time); +protected: + F32 mForce; + Type mStartVel; + Type mVelocity; +}; + +template +class LLInterpFunc : public LLInterp +{ +public: + LLInterpFunc(); + void update(const F32 time); + + void setFunc(Type (*)(const F32, void *data), void *data); +protected: + Type (*mFunc)(const F32 time, void *data); + void *mData; +}; + + +/////////////////////////////////// +// +// Implementation +// +// + +///////////////////////////////// +// +// LLInterp base class implementation +// + +template +LLInterp::LLInterp() +{ + mStartTime = 0.f; + mEndTime = 1.f; + mDuration = 1.f; + mCurTime = 0.f; + mDone = FALSE; + mActive = FALSE; +} + +template +void LLInterp::setStartVal(const Type &start_val) +{ + mStartVal = start_val; +} + +template +void LLInterp::start() +{ + mCurVal = mStartVal; + mCurTime = mStartTime; + mDone = FALSE; + mActive = FALSE; +} + +template +const Type &LLInterp::getStartVal() const +{ + return mStartVal; +} + +template +void LLInterp::setEndVal(const Type &end_val) +{ + mEndVal = end_val; +} + +template +const Type &LLInterp::getEndVal() const +{ + return mEndVal; +} + +template +const Type &LLInterp::getCurVal() const +{ + return mCurVal; +} + + +template +void LLInterp::setStartTime(const F32 start_time) +{ + mStartTime = start_time; + mDuration = mEndTime - mStartTime; +} + +template +F32 LLInterp::getStartTime() const +{ + return mStartTime; +} + + +template +void LLInterp::setEndTime(const F32 end_time) +{ + mEndTime = end_time; + mDuration = mEndTime - mStartTime; +} + + +template +F32 LLInterp::getEndTime() const +{ + return mEndTime; +} + + +template +BOOL LLInterp::isDone() const +{ + return mDone; +} + +template +BOOL LLInterp::isActive() const +{ + return mActive; +} + +////////////////////////////// +// +// LLInterpLinear derived class implementation. +// +template +void LLInterpLinear::start() +{ + LLInterp::start(); + mCurFrac = 0.f; +} + +template +void LLInterpLinear::update(const F32 time) +{ + F32 target_frac = (time - this->mStartTime) / this->mDuration; + F32 dfrac = target_frac - this->mCurFrac; + if (target_frac >= 0.f) + { + this->mActive = TRUE; + } + + if (target_frac > 1.f) + { + this->mCurVal = this->mEndVal; + this->mCurFrac = 1.f; + this->mCurTime = time; + this->mDone = TRUE; + return; + } + + target_frac = llmin(1.f, target_frac); + target_frac = llmax(0.f, target_frac); + + if (dfrac >= 0.f) + { + F32 total_frac = 1.f - this->mCurFrac; + F32 inc_frac = dfrac / total_frac; + this->mCurVal = inc_frac * this->mEndVal + (1.f - inc_frac) * this->mCurVal; + this->mCurTime = time; + } + else + { + F32 total_frac = this->mCurFrac - 1.f; + F32 inc_frac = dfrac / total_frac; + this->mCurVal = inc_frac * this->mStartVal + (1.f - inc_frac) * this->mCurVal; + this->mCurTime = time; + } + mCurFrac = target_frac; +} + +template +F32 LLInterpLinear::getCurFrac() const +{ + return mCurFrac; +} + + +////////////////////////////// +// +// LLInterpAttractor derived class implementation. +// + + +template +LLInterpAttractor::LLInterpAttractor() : LLInterp() +{ + mForce = 0.1f; + mVelocity *= 0.f; + mStartVel *= 0.f; +} + +template +void LLInterpAttractor::start() +{ + LLInterp::start(); + mVelocity = mStartVel; +} + + +template +void LLInterpAttractor::setStartVel(const Type &vel) +{ + mStartVel = vel; +} + +template +void LLInterpAttractor::setForce(const F32 force) +{ + mForce = force; +} + +template +void LLInterpAttractor::update(const F32 time) +{ + if (time > this->mStartTime) + { + this->mActive = TRUE; + } + else + { + return; + } + if (time > this->mEndTime) + { + this->mDone = TRUE; + return; + } + + F32 dt = time - this->mCurTime; + Type dist_val = this->mEndVal - this->mCurVal; + Type dv = 0.5*dt*dt*this->mForce*dist_val; + this->mVelocity += dv; + this->mCurVal += this->mVelocity * dt; + this->mCurTime = time; +} + + +////////////////////////////// +// +// LLInterpFucn derived class implementation. +// + + +template +LLInterpFunc::LLInterpFunc() : LLInterp() +{ + mFunc = NULL; + mData = NULL; +} + +template +void LLInterpFunc::setFunc(Type (*func)(const F32, void *data), void *data) +{ + mFunc = func; + mData = data; +} + +template +void LLInterpFunc::update(const F32 time) +{ + if (time > this->mStartTime) + { + this->mActive = TRUE; + } + else + { + return; + } + if (time > this->mEndTime) + { + this->mDone = TRUE; + return; + } + + this->mCurVal = (*mFunc)(time - this->mStartTime, mData); + this->mCurTime = time; +} + +////////////////////////////// +// +// LLInterpExp derived class implementation. +// + +template +void LLInterpExp::update(const F32 time) +{ + F32 target_frac = (time - this->mStartTime) / this->mDuration; + if (target_frac >= 0.f) + { + this->mActive = TRUE; + } + + if (target_frac > 1.f) + { + this->mCurVal = this->mEndVal; + this->mCurFrac = 1.f; + this->mCurTime = time; + this->mDone = TRUE; + return; + } + + this->mCurFrac = 1.f - (F32)(exp(-2.f*target_frac)); + this->mCurVal = this->mStartVal + this->mCurFrac * (this->mEndVal - this->mStartVal); + this->mCurTime = time; +} + +#endif // LL_LLINTERP_H + diff --git a/indra/llmath/llmath.h b/indra/llmath/llmath.h new file mode 100644 index 0000000000..ad8ced9e1a --- /dev/null +++ b/indra/llmath/llmath.h @@ -0,0 +1,402 @@ +/** + * @file llmath.h + * @brief Useful math constants and macros. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LLMATH_H +#define LLMATH_H + +#include +#include +#include + +#include "lldefs.h" + +// work around for Windows & older gcc non-standard function names. +#if LL_WINDOWS +#define llisnan(val) _isnan(val) +#define llfinite(val) _finite(val) +#elif (LL_LINUX && __GNUC__ <= 2) +#define llisnan(val) isnan(val) +#define llfinite(val) isfinite(val) +#else +#define llisnan(val) std::isnan(val) +#define llfinite(val) std::isfinite(val) +#endif + +// Single Precision Floating Point Routines +#ifndef fsqrtf +#define fsqrtf(x) ((F32)sqrt((F64)(x))) +#endif +#ifndef sqrtf +#define sqrtf(x) ((F32)sqrt((F64)(x))) +#endif + +#ifndef cosf +#define cosf(x) ((F32)cos((F64)(x))) +#endif +#ifndef sinf +#define sinf(x) ((F32)sin((F64)(x))) +#endif +#ifndef tanf +#define tanf(x) ((F32)tan((F64)(x))) +#endif +#ifndef acosf +#define acosf(x) ((F32)acos((F64)(x))) +#endif + +#ifndef powf +#define powf(x,y) ((F32)pow((F64)(x),(F64)(y))) +#endif + +const F32 GRAVITY = -9.8f; + +// mathematical constants +const F32 F_PI = 3.1415926535897932384626433832795f; +const F32 F_TWO_PI = 6.283185307179586476925286766559f; +const F32 F_PI_BY_TWO = 1.5707963267948966192313216916398f; +const F32 F_SQRT2 = 1.4142135623730950488016887242097f; +const F32 F_SQRT3 = 1.73205080756888288657986402541f; +const F32 OO_SQRT2 = 0.7071067811865475244008443621049f; +const F32 DEG_TO_RAD = 0.017453292519943295769236907684886f; +const F32 RAD_TO_DEG = 57.295779513082320876798154814105f; +const F32 F_APPROXIMATELY_ZERO = 0.00001f; +const F32 F_LN2 = 0.69314718056f; +const F32 OO_LN2 = 1.4426950408889634073599246810019f; + +// BUG: Eliminate in favor of F_APPROXIMATELY_ZERO above? +const F32 FP_MAG_THRESHOLD = 0.0000001f; + +// TODO: Replace with logic like is_approx_equal +inline BOOL is_approx_zero( F32 f ) { return (-F_APPROXIMATELY_ZERO < f) && (f < F_APPROXIMATELY_ZERO); } + +inline BOOL is_approx_equal(F32 x, F32 y) +{ + const S32 COMPARE_MANTISSA_UP_TO_BIT = 0x02; + return (abs((S32) ((U32&)x - (U32&)y) ) < COMPARE_MANTISSA_UP_TO_BIT); +} + +inline S32 llabs(const S32 a) +{ + return S32(labs(a)); +} + +inline F32 llabs(const F32 a) +{ + return F32(fabs(a)); +} + +inline F64 llabs(const F64 a) +{ + return F64(fabs(a)); +} + +inline S32 lltrunc( F32 f ) +{ +#if LL_WINDOWS && !defined( __INTEL_COMPILER ) + // Avoids changing the floating point control word. + // Add or subtract 0.5 - epsilon and then round + const static U32 zpfp[] = { 0xBEFFFFFF, 0x3EFFFFFF }; + S32 result; + __asm { + fld f + mov eax, f + shr eax, 29 + and eax, 4 + fadd dword ptr [zpfp + eax] + fistp result + } + return result; +#else + return (S32)f; +#endif +} + +inline S32 lltrunc( F64 f ) +{ + return (S32)f; +} + +inline S32 llfloor( F32 f ) +{ +#if LL_WINDOWS && !defined( __INTEL_COMPILER ) + // Avoids changing the floating point control word. + // Accurate (unlike Stereopsis version) for all values between S32_MIN and S32_MAX and slightly faster than Stereopsis version. + // Add -(0.5 - epsilon) and then round + const U32 zpfp = 0xBEFFFFFF; + S32 result; + __asm { + fld f + fadd dword ptr [zpfp] + fistp result + } + return result; +#else + return (S32)floor(f); +#endif +} + + +inline S32 llceil( F32 f ) +{ + // This could probably be optimized, but this works. + return (S32)ceil(f); +} + + +#ifndef BOGUS_ROUND +// Use this round. Does an arithmetic round (0.5 always rounds up) +inline S32 llround(const F32 val) +{ + return llfloor(val + 0.5f); +} + +#else // BOGUS_ROUND +// Old llround implementation - does banker's round (toward nearest even in the case of a 0.5. +// Not using this because we don't have a consistent implementation on both platforms, use +// llfloor(val + 0.5f), which is consistent on all platforms. +inline S32 llround(const F32 val) +{ + #if LL_WINDOWS + // Note: assumes that the floating point control word is set to rounding mode (the default) + S32 ret_val; + _asm fld val + _asm fistp ret_val; + return ret_val; + #elif LL_LINUX + // Note: assumes that the floating point control word is set + // to rounding mode (the default) + S32 ret_val; + __asm__ __volatile__( "flds %1 \n\t" + "fistpl %0 \n\t" + : "=m" (ret_val) + : "m" (val) ); + return ret_val; + #else + return llfloor(val + 0.5f); + #endif +} + +// A fast arithmentic round on intel, from Laurent de Soras http://ldesoras.free.fr +inline int round_int(double x) +{ + const float round_to_nearest = 0.5f; + int i; + __asm + { + fld x + fadd st, st (0) + fadd round_to_nearest + fistp i + sar i, 1 + } + return (i); +} +#endif // BOGUS_ROUND + +inline F32 llround( F32 val, F32 nearest ) +{ + return F32(floor(val * (1.0f / nearest) + 0.5f)) * nearest; +} + +inline F64 llround( F64 val, F64 nearest ) +{ + return F64(floor(val * (1.0 / nearest) + 0.5)) * nearest; +} + +// these provide minimum peak error +// +// avg error = -0.013049 +// peak error = -31.4 dB +// RMS error = -28.1 dB + +const F32 FAST_MAG_ALPHA = 0.960433870103f; +const F32 FAST_MAG_BETA = 0.397824734759f; + +// these provide minimum RMS error +// +// avg error = 0.000003 +// peak error = -32.6 dB +// RMS error = -25.7 dB +// +//const F32 FAST_MAG_ALPHA = 0.948059448969f; +//const F32 FAST_MAG_BETA = 0.392699081699f; + +inline F32 fastMagnitude(F32 a, F32 b) +{ + a = (a > 0) ? a : -a; + b = (b > 0) ? b : -b; + return(FAST_MAG_ALPHA * llmax(a,b) + FAST_MAG_BETA * llmin(a,b)); +} + + + +//////////////////// +// +// Fast F32/S32 conversions +// +// Culled from www.stereopsis.com/FPU.html + +const F64 LL_DOUBLE_TO_FIX_MAGIC = 68719476736.0*1.5; //2^36 * 1.5, (52-_shiftamt=36) uses limited precisicion to floor +const S32 LL_SHIFT_AMOUNT = 16; //16.16 fixed point representation, + +// Endian dependent code +#ifdef LL_LITTLE_ENDIAN + #define LL_EXP_INDEX 1 + #define LL_MAN_INDEX 0 +#else + #define LL_EXP_INDEX 0 + #define LL_MAN_INDEX 1 +#endif + +/* Deprecated: use llround(), lltrunc(), or llfloor() instead +// ================================================================================================ +// Real2Int +// ================================================================================================ +inline S32 F64toS32(F64 val) +{ + val = val + LL_DOUBLE_TO_FIX_MAGIC; + return ((S32*)&val)[LL_MAN_INDEX] >> LL_SHIFT_AMOUNT; +} + +// ================================================================================================ +// Real2Int +// ================================================================================================ +inline S32 F32toS32(F32 val) +{ + return F64toS32 ((F64)val); +} +*/ + +//////////////////////////////////////////////// +// +// Fast exp and log +// + +// Implementation of fast exp() approximation (from a paper by Nicol N. Schraudolph +// http://www.inf.ethz.ch/~schraudo/pubs/exp.pdf +static union +{ + double d; + struct + { +#ifdef LL_LITTLE_ENDIAN + S32 j, i; +#else + S32 i, j; +#endif + } n; +} LLECO; // not sure what the name means + +#define LL_EXP_A (1048576 * OO_LN2) // use 1512775 for integer +#define LL_EXP_C (60801) // this value of C good for -4 < y < 4 + +#define LL_FAST_EXP(y) (LLECO.n.i = llround(F32(LL_EXP_A*(y))) + (1072693248 - LL_EXP_C), LLECO.d) + + + +inline F32 llfastpow(const F32 x, const F32 y) +{ + return (F32)(LL_FAST_EXP(y * log(x))); +} + + +inline F32 snap_to_sig_figs(F32 foo, S32 sig_figs) +{ + // compute the power of ten + F32 bar = 1.f; + for (S32 i = 0; i < sig_figs; i++) + { + bar *= 10.f; + } + + foo = (F32)llround(foo * bar); + + // shift back + foo /= bar; + return foo; +} + +inline F32 lerp(F32 a, F32 b, F32 u) +{ + return a + ((b - a) * u); +} + +inline F32 lerp2d(F32 x00, F32 x01, F32 x10, F32 x11, F32 u, F32 v) +{ + F32 a = x00 + (x01-x00)*u; + F32 b = x10 + (x11-x10)*u; + F32 r = a + (b-a)*v; + return r; +} + +inline F32 ramp(F32 x, F32 a, F32 b) +{ + return (a == b) ? 0.0f : ((a - x) / (a - b)); +} + +inline F32 rescale(F32 x, F32 x1, F32 x2, F32 y1, F32 y2) +{ + return lerp(y1, y2, ramp(x, x1, x2)); +} + +inline F32 clamp_rescale(F32 x, F32 x1, F32 x2, F32 y1, F32 y2) +{ + if (y1 < y2) + { + return llclamp(rescale(x,x1,x2,y1,y2),y1,y2); + } + else + { + return llclamp(rescale(x,x1,x2,y1,y2),y2,y1); + } +} + + +inline F32 cubic_step( F32 x, F32 x0, F32 x1, F32 s0, F32 s1 ) +{ + if (x <= x0) + return s0; + + if (x >= x1) + return s1; + + F32 f = (x - x0) / (x1 - x0); + + return s0 + (s1 - s0) * (f * f) * (3.0f - 2.0f * f); +} + +inline F32 cubic_step( F32 x ) +{ + x = llclampf(x); + + return (x * x) * (3.0f - 2.0f * x); +} + +inline F32 quadratic_step( F32 x, F32 x0, F32 x1, F32 s0, F32 s1 ) +{ + if (x <= x0) + return s0; + + if (x >= x1) + return s1; + + F32 f = (x - x0) / (x1 - x0); + F32 f_squared = f * f; + + return (s0 * (1.f - f_squared)) + ((s1 - s0) * f_squared); +} + +inline F32 llsimple_angle(F32 angle) +{ + while(angle <= -F_PI) + angle += F_TWO_PI; + while(angle > F_PI) + angle -= F_TWO_PI; + return angle; +} + +#endif diff --git a/indra/llmath/lloctree.h b/indra/llmath/lloctree.h new file mode 100644 index 0000000000..69ff43452c --- /dev/null +++ b/indra/llmath/lloctree.h @@ -0,0 +1,693 @@ +/** + * @file lloctree.h + * @brief Octree declaration. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLOCTREE_H +#define LL_LLOCTREE_H + +#include "lltreenode.h" +#include "v3math.h" +#include +#include + +#ifdef LL_RELEASE_FOR_DOWNLOAD +#define OCT_ERRS llwarns +#else +#define OCT_ERRS llerrs +#endif + +#define LL_OCTREE_PARANOIA_CHECK 0 + +template class LLOctreeState; +template class LLOctreeNode; + +template +class LLOctreeListener: public LLTreeListener +{ +public: + typedef LLTreeListener BaseType; + typedef LLOctreeNode oct_node; + + virtual ~LLOctreeListener() { }; + virtual void handleChildAddition(const oct_node* parent, oct_node* child) = 0; + virtual void handleChildRemoval(const oct_node* parent, const oct_node* child) = 0; +}; + + +template +class LLOctreeNode : public LLTreeNode +{ +public: + + typedef LLTreeNode BaseType; + typedef LLTreeState tree_state; + typedef LLOctreeState oct_state; + typedef LLOctreeNode oct_node; + typedef LLOctreeListener oct_listener; + + static const U8 OCTANT_POSITIVE_X = 0x01; + static const U8 OCTANT_POSITIVE_Y = 0x02; + static const U8 OCTANT_POSITIVE_Z = 0x04; + + LLOctreeNode( LLVector3d center, + LLVector3d size, + tree_state* state, + BaseType* parent, + U8 octant = 255) + : BaseType(state), + mParent((oct_node*)parent), + mCenter(center), + mSize(size), + mOctant(octant) + { + updateMinMax(); + if ((mOctant == 255) && mParent) + { + mOctant = ((oct_node*) mParent)->getOctant(mCenter.mdV); + } + } + + virtual ~LLOctreeNode() { BaseType::destroyListeners(); delete this->mState; } + + virtual const BaseType* getParent() const { return mParent; } + virtual void setParent(BaseType* parent) { mParent = (oct_node*) parent; } + virtual const LLVector3d& getCenter() const { return mCenter; } + virtual const LLVector3d& getSize() const { return mSize; } + virtual void setCenter(LLVector3d center) { mCenter = center; } + virtual void setSize(LLVector3d size) { mSize = size; } + virtual bool balance() { return getOctState()->balance(); } + virtual void validate() { getOctState()->validate(); } + virtual U32 getChildCount() const { return getOctState()->getChildCount(); } + virtual oct_node* getChild(U32 index) { return getOctState()->getChild(index); } + virtual const oct_node* getChild(U32 index) const { return getOctState()->getChild(index); } + virtual U32 getElementCount() const { return getOctState()->getElementCount(); } + virtual void removeByAddress(T* data) { getOctState()->removeByAddress(data); } + virtual bool hasLeafState() const { return getOctState()->isLeaf(); } + virtual void destroy() { getOctState()->destroy(); } + virtual oct_node* getNodeAt(T* data) { return getOctState()->getNodeAt(data); } + virtual U8 getOctant() const { return mOctant; } + virtual void setOctant(U8 octant) { mOctant = octant; } + virtual const oct_state* getOctState() const { return (const oct_state*) BaseType::mState; } + virtual oct_state* getOctState() { return (oct_state*) BaseType::mState; } + virtual const oct_node* getOctParent() const { return (const oct_node*) getParent(); } + virtual oct_node* getOctParent() { return (oct_node*) getParent(); } + virtual void deleteChild(oct_node* child) { getOctState()->deleteChild(child); } + + virtual U8 getOctant(const F64 pos[]) const //get the octant pos is in + { + U8 ret = 0; + + if (pos[0] > mCenter.mdV[0]) + { + ret |= OCTANT_POSITIVE_X; + } + if (pos[1] > mCenter.mdV[1]) + { + ret |= OCTANT_POSITIVE_Y; + } + if (pos[2] > mCenter.mdV[2]) + { + ret |= OCTANT_POSITIVE_Z; + } + + return ret; + } + + virtual bool isInside(T* data) const + { + return data->getBinRadius() <= mSize.mdV[0]*2.0 && isInside(data->getPositionGroup()); + } + + virtual bool isInside(const LLVector3d& pos) const + { + const F64& x = pos.mdV[0]; + const F64& y = pos.mdV[1]; + const F64& z = pos.mdV[2]; + + if (x > mMax.mdV[0] || x <= mMin.mdV[0] || + y > mMax.mdV[1] || y <= mMin.mdV[1] || + z > mMax.mdV[2] || z <= mMin.mdV[2]) + { + return false; + } + + return true; + } + + virtual void updateMinMax() + { + for (U32 i = 0; i < 3; i++) + { + mMax.mdV[i] = mCenter.mdV[i] + mSize.mdV[i]; + mMin.mdV[i] = mCenter.mdV[i] - mSize.mdV[i]; + mCenter.mdV[i] = mCenter.mdV[i]; + } + } + + virtual oct_listener* getOctListener(U32 index) + { + return (oct_listener*) BaseType::getListener(index); + } + + bool contains(T* xform) + { + if (mParent == NULL) + { //root node contains nothing + return false; + } + + F64 size = mSize.mdV[0]; + F64 p_size = size * 2.0; + F64 radius = xform->getBinRadius(); + + return (radius <= 0.001 && size <= 0.001) || + (radius <= p_size && radius > size); + } + + static void pushCenter(LLVector3d ¢er, LLVector3d &size, T* data) + { + LLVector3 pos(data->getPositionGroup()); + F64 p[] = + { + (F64) pos.mV[0], + (F64) pos.mV[1], + (F64) pos.mV[2] + }; + + for (U32 i = 0; i < 3; i++) + { + if (p[i] > center.mdV[i]) + { + center.mdV[i] += size.mdV[i]; + } + else + { + center.mdV[i] -= size.mdV[i]; + } + } + } + +protected: + oct_node* mParent; + LLVector3d mCenter; + LLVector3d mSize; + LLVector3d mMax; + LLVector3d mMin; + U8 mOctant; +}; + +template +class LLOctreeTraveler : public LLTreeTraveler +{ +public: + virtual void traverse(const LLTreeNode* node); + virtual void visit(const LLTreeState* state) { } + virtual void visit(const LLOctreeState* branch) = 0; +}; + +//will pass requests to a child, might make a new child +template +class LLOctreeState : public LLTreeState +{ +public: + typedef LLTreeState BaseType; + typedef LLOctreeTraveler oct_traveler; + typedef LLOctreeNode oct_node; + typedef LLOctreeListener oct_listener; + typedef LLTreeTraveler tree_traveler; + typedef typename std::set > element_list; + typedef typename std::set >::iterator element_iter; + typedef typename std::set >::const_iterator const_element_iter; + typedef typename std::vector*>::iterator tree_listener_iter; + typedef typename std::vector* > child_list; + + LLOctreeState(oct_node* node = NULL): BaseType(node) { this->clearChildren(); } + virtual ~LLOctreeState() + { + for (U32 i = 0; i < getChildCount(); i++) + { + delete getChild(i); + } + } + + + virtual void accept(oct_traveler* visitor) { visitor->visit(this); } + virtual bool isLeaf() const { return mChild.empty(); } + + virtual U32 getElementCount() const { return mData.size(); } + virtual element_list& getData() { return mData; } + virtual const element_list& getData() const { return mData; } + + virtual U32 getChildCount() const { return mChild.size(); } + virtual oct_node* getChild(U32 index) { return mChild[index]; } + virtual const oct_node* getChild(U32 index) const { return mChild[index]; } + virtual child_list& getChildren() { return mChild; } + virtual const child_list& getChildren() const { return mChild; } + + virtual void accept(tree_traveler* visitor) const { visitor->visit(this); } + virtual void accept(oct_traveler* visitor) const { visitor->visit(this); } + const oct_node* getOctNode() const { return (const oct_node*) BaseType::getNode(); } + oct_node* getOctNode() { return (oct_node*) BaseType::getNode(); } + + virtual oct_node* getNodeAt(T* data) + { + const LLVector3d& pos = data->getPositionGroup(); + LLOctreeNode* node = getOctNode(); + + if (node->isInside(data)) + { + //do a quick search by octant + U8 octant = node->getOctant(pos.mdV); + BOOL keep_going = TRUE; + + //traverse the tree until we find a node that has no node + //at the appropriate octant or is smaller than the object. + //by definition, that node is the smallest node that contains + // the data + while (keep_going && node->getSize().mdV[0] >= data->getBinRadius()) + { + keep_going = FALSE; + for (U32 i = 0; i < node->getChildCount() && !keep_going; i++) + { + if (node->getChild(i)->getOctant() == octant) + { + node = node->getChild(i); + octant = node->getOctant(pos.mdV); + keep_going = TRUE; + } + } + } + } + else if (!node->contains(data) && node->getParent()) + { //if we got here, data does not exist in this node + return ((LLOctreeNode*) node->getParent())->getNodeAt(data); + } + + return node; + } + + virtual bool insert(T* data) + { + if (data == NULL) + { + OCT_ERRS << "!!! INVALID ELEMENT ADDED TO OCTREE BRANCH !!!" << llendl; + return false; + } + LLOctreeNode* node = getOctNode(); + + if (data->getBinRadius() <= node->getSize().mdV[0]) + { + oct_node* dest = getNodeAt(data); + + if (dest != node) + { + dest->insert(data); + return false; + } + } + + //no kid found, is it even here? + if (node->isInside(data)) + { + if (node->contains(data)) + { //it belongs here + if (data == NULL) + { + OCT_ERRS << "!!! INVALID ELEMENT ADDED TO OCTREE LEAF !!!" << llendl; + return false; + } + +#if LL_OCTREE_PARANOIA_CHECK + //if this is a redundant insertion, error out (should never happen) + if (mData.find(data) != mData.end()) + { + llwarns << "Redundant octree insertion detected. " << data << llendl; + return false; + } +#endif + + mData.insert(data); + return true; + } + else + { + //it's here, but no kids are in the right place, make a new kid + LLVector3d center(node->getCenter()); + LLVector3d size(node->getSize()*0.5); + + //push center in direction of data + LLOctreeNode::pushCenter(center, size, data); + +#if LL_OCTREE_PARANOIA_CHECK + if (getChildCount() == 8) + { + //this really isn't possible, something bad has happened + OCT_ERRS << "Octree detected floating point error and gave up." << llendl; + //bool check = node->isInside(data); + return false; + } + + //make sure no existing node matches this position + for (U32 i = 0; i < getChildCount(); i++) + { + if (mChild[i]->getCenter() == center) + { + OCT_ERRS << "Octree detected duplicate child center and gave up." << llendl; + //bool check = node->isInside(data); + //check = getChild(i)->isInside(data); + return false; + } + } +#endif + + //make the new kid + LLOctreeState* newstate = new LLOctreeState(); + oct_node* child = new LLOctreeNode(center, size, newstate, node); + addChild(child); + + child->insert(data); + } + } + else + { + //it's not in here, give it to the parent + node->getOctParent()->insert(data); + } + + return false; + } + + virtual bool remove(T* data) + { + oct_node* node = getOctNode(); + + if (mData.find(data) != mData.end()) + { //we have data + mData.erase(data); + node->notifyRemoval(data); + checkAlive(); + return true; + } + else if (node->isInside(data)) + { + oct_node* dest = getNodeAt(data); + + if (dest != node) + { + return dest->remove(data); + } + } + + //SHE'S GONE MISSING... + //none of the children have it, let's just brute force this bastard out + //starting with the root node (UGLY CODE COMETH!) + oct_node* parent = node->getOctParent(); + while (parent != NULL) + { + node = parent; + parent = node->getOctParent(); + } + + //node is now root + llwarns << "!!! OCTREE REMOVING FACE BY ADDRESS, SEVERE PERFORMANCE PENALTY |||" << llendl; + node->removeByAddress(data); + return true; + } + + virtual void removeByAddress(T* data) + { + if (mData.find(data) != mData.end()) + { + mData.erase(data); + getOctNode()->notifyRemoval(data); + llwarns << "FOUND!" << llendl; + checkAlive(); + return; + } + + for (U32 i = 0; i < getChildCount(); i++) + { //we don't contain data, so pass this guy down + LLOctreeNode* child = (LLOctreeNode*) getChild(i); + child->removeByAddress(data); + } + } + + virtual void clearChildren() + { + mChild.clear(); + } + + virtual void validate() + { +#if LL_OCTREE_PARANOIA_CHECK + LLOctreeNode* node = this->getOctNode(); + + for (U32 i = 0; i < getChildCount(); i++) + { + mChild[i]->validate(); + if (mChild[i]->getParent() != node) + { + llerrs << "Octree child has invalid parent." << llendl; + } + } +#endif + } + + virtual bool balance() + { + return false; + } + + virtual void destroy() + { + for (U32 i = 0; i < getChildCount(); i++) + { + mChild[i]->destroy(); + delete mChild[i]; + } + } + + virtual void addChild(oct_node* child, BOOL silent = FALSE) + { +#if LL_OCTREE_PARANOIA_CHECK + for (U32 i = 0; i < getChildCount(); i++) + { + if(mChild[i]->getSize() != child->getSize()) + { + OCT_ERRS <<"Invalid octree child size." << llendl; + } + if (mChild[i]->getCenter() == child->getCenter()) + { + OCT_ERRS <<"Duplicate octree child position." << llendl; + } + } + + if (mChild.size() >= 8) + { + OCT_ERRS <<"Octree node has too many children... why?" << llendl; + } +#endif + + mChild.push_back(child); + child->setParent(getOctNode()); + + if (!silent) + { + oct_node* node = getOctNode(); + + for (U32 i = 0; i < node->getListenerCount(); i++) + { + oct_listener* listener = node->getOctListener(i); + listener->handleChildAddition(node, child); + } + } + } + + virtual void removeChild(U8 index, BOOL destroy = FALSE) + { + oct_node* node = getOctNode(); + for (U32 i = 0; i < node->getListenerCount(); i++) + { + oct_listener* listener = node->getOctListener(i); + listener->handleChildRemoval(node, getChild(index)); + } + + if (destroy) + { + mChild[index]->destroy(); + delete mChild[index]; + } + mChild.erase(mChild.begin() + index); + + checkAlive(); + } + + virtual void checkAlive() + { + if (getChildCount() == 0 && getElementCount() == 0) + { + oct_node* node = getOctNode(); + oct_node* parent = node->getOctParent(); + if (parent) + { + parent->deleteChild(node); + } + } + } + + virtual void deleteChild(oct_node* node) + { + for (U32 i = 0; i < getChildCount(); i++) + { + if (getChild(i) == node) + { + removeChild(i, TRUE); + return; + } + } + + OCT_ERRS << "Octree failed to delete requested child." << llendl; + } + +protected: + child_list mChild; + element_list mData; +}; + +//just like a branch, except it might expand the node it points to +template +class LLOctreeRoot : public LLOctreeState +{ +public: + typedef LLOctreeState BaseType; + typedef LLOctreeNode oct_node; + + LLOctreeRoot(oct_node* node = NULL) : BaseType(node) { } + + oct_node* getOctNode() { return BaseType::getOctNode(); } + virtual bool isLeaf() { return false; } + + virtual bool balance() + { + //the cached node might be invalid, so don't reference it + if (this->getChildCount() == 1 && + !(this->mChild[0]->hasLeafState()) && + this->mChild[0]->getElementCount() == 0) + { //if we have only one child and that child is an empty branch, make that child the root + BaseType* state = this->mChild[0]->getOctState(); + oct_node* child = this->mChild[0]; + oct_node* root = getOctNode(); + + //make the root node look like the child + root->setCenter(this->mChild[0]->getCenter()); + root->setSize(this->mChild[0]->getSize()); + root->updateMinMax(); + + //reset root node child list + this->clearChildren(); + + //copy the child's children into the root node silently + //(don't notify listeners of addition) + for (U32 i = 0; i < state->getChildCount(); i++) + { + addChild(state->getChild(i), TRUE); + } + + //destroy child + state->clearChildren(); + delete child; + } + + return true; + } + + // LLOctreeRoot::insert + virtual bool insert(T* data) + { + if (data == NULL) + { + OCT_ERRS << "!!! INVALID ELEMENT ADDED TO OCTREE ROOT !!!" << llendl; + return false; + } + + if (data->getBinRadius() > 4096.0) + { + OCT_ERRS << "!!! ELEMENT EXCEDES MAXIMUM SIZE IN OCTREE ROOT !!!" << llendl; + } + + LLOctreeNode* node = getOctNode(); + if (node->getSize().mdV[0] > data->getBinRadius() && node->isInside(data->getPositionGroup())) + { + //we got it, just act like a branch + LLOctreeState::insert(data); + } + else if (this->getChildCount() == 0) + { + //first object being added, just wrap it up + while (!(node->getSize().mdV[0] > data->getBinRadius() && node->isInside(data->getPositionGroup()))) + { + LLVector3d center, size; + center = node->getCenter(); + size = node->getSize(); + LLOctreeNode::pushCenter(center, size, data); + node->setCenter(center); + node->setSize(size*2); + node->updateMinMax(); + } + LLOctreeState::insert(data); + } + else + { + //the data is outside the root node, we need to grow + LLVector3d center(node->getCenter()); + LLVector3d size(node->getSize()); + + //expand this node + LLVector3d newcenter(center); + LLOctreeNode::pushCenter(newcenter, size, data); + node->setCenter(newcenter); + node->setSize(size*2); + node->updateMinMax(); + + //copy our children to a new branch + LLOctreeState* newstate = new LLOctreeState(); + LLOctreeNode* newnode = new LLOctreeNode(center, size, newstate, node); + + for (U32 i = 0; i < this->getChildCount(); i++) + { + LLOctreeNode* child = this->getChild(i); + newstate->addChild(child); + } + + //clear our children and add the root copy + this->clearChildren(); + addChild(newnode); + + //insert the data + node->insert(data); + } + + return false; + } +}; + + +//======================== +// LLOctreeTraveler +//======================== +template +void LLOctreeTraveler::traverse(const LLTreeNode* node) +{ + const LLOctreeState* state = (const LLOctreeState*) node->getState(); + state->accept(this); + for (U32 i = 0; i < state->getChildCount(); i++) + { + traverse(state->getChild(i)); + } +} + +#endif diff --git a/indra/llmath/llperlin.cpp b/indra/llmath/llperlin.cpp new file mode 100644 index 0000000000..770d80a845 --- /dev/null +++ b/indra/llmath/llperlin.cpp @@ -0,0 +1,276 @@ +/** + * @file llperlin.cpp + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llmath.h" + +#include "llperlin.h" + +#define B 0x100 +#define BM 0xff +#define N 0x1000 +#define NF32 (4096.f) +#define NP 12 /* 2^N */ +#define NM 0xfff + +static S32 p[B + B + 2]; +static F32 g3[B + B + 2][3]; +static F32 g2[B + B + 2][2]; +static F32 g1[B + B + 2]; + +bool LLPerlinNoise::sInitialized = 0; + +static void normalize2(F32 v[2]) +{ + F32 s = 1.f/(F32)sqrt(v[0] * v[0] + v[1] * v[1]); + v[0] = v[0] * s; + v[1] = v[1] * s; +} + +static void normalize3(F32 v[3]) +{ + F32 s = 1.f/(F32)sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + v[0] = v[0] * s; + v[1] = v[1] * s; + v[2] = v[2] * s; +} + +static void fast_setup(F32 vec, U8 &b0, U8 &b1, F32 &r0, F32 &r1) +{ + S32 t_S32; + + r1 = vec + NF32; + t_S32 = lltrunc(r1); + b0 = (U8)t_S32; + b1 = b0 + 1; + r0 = r1 - t_S32; + r1 = r0 - 1.f; +} + + +void LLPerlinNoise::init(void) +{ + int i, j, k; + + for (i = 0 ; i < B ; i++) + { + p[i] = i; + + g1[i] = (F32)((rand() % (B + B)) - B) / B; + + for (j = 0 ; j < 2 ; j++) + g2[i][j] = (F32)((rand() % (B + B)) - B) / B; + normalize2(g2[i]); + + for (j = 0 ; j < 3 ; j++) + g3[i][j] = (F32)((rand() % (B + B)) - B) / B; + normalize3(g3[i]); + } + + while (--i) + { + k = p[i]; + p[i] = p[j = rand() % B]; + p[j] = k; + } + + for (i = 0 ; i < B + 2 ; i++) + { + p[B + i] = p[i]; + g1[B + i] = g1[i]; + for (j = 0 ; j < 2 ; j++) + g2[B + i][j] = g2[i][j]; + for (j = 0 ; j < 3 ; j++) + g3[B + i][j] = g3[i][j]; + } + + sInitialized = true; +} + + +//============================================================================ +// Noise functions + +#define s_curve(t) ( t * t * (3.f - 2.f * t) ) + +#define lerp_m(t, a, b) ( a + t * (b - a) ) + +F32 LLPerlinNoise::noise1(F32 x) +{ + int bx0, bx1; + F32 rx0, rx1, sx, t, u, v; + + if (!sInitialized) + init(); + + t = x + N; + bx0 = (lltrunc(t)) & BM; + bx1 = (bx0+1) & BM; + rx0 = t - lltrunc(t); + rx1 = rx0 - 1.f; + + sx = s_curve(rx0); + + u = rx0 * g1[ p[ bx0 ] ]; + v = rx1 * g1[ p[ bx1 ] ]; + + return lerp_m(sx, u, v); +} + +static F32 fast_at2(F32 rx, F32 ry, F32 *q) +{ + return rx * q[0] + ry * q[1]; +} + +F32 LLPerlinNoise::noise2(F32 x, F32 y) +{ + U8 bx0, bx1, by0, by1; + U32 b00, b10, b01, b11; + F32 rx0, rx1, ry0, ry1, *q, sx, sy, a, b, u, v; + S32 i, j; + + if (!sInitialized) + init(); + + fast_setup(x, bx0, bx1, rx0, rx1); + fast_setup(y, by0, by1, ry0, ry1); + + i = *(p + bx0); + j = *(p + bx1); + + b00 = *(p + i + by0); + b10 = *(p + j + by0); + b01 = *(p + i + by1); + b11 = *(p + j + by1); + + sx = s_curve(rx0); + sy = s_curve(ry0); + + + q = *(g2 + b00); + u = fast_at2(rx0, ry0, q); + q = *(g2 + b10); + v = fast_at2(rx1, ry0, q); + a = lerp_m(sx, u, v); + + q = *(g2 + b01); + u = fast_at2(rx0,ry1,q); + q = *(g2 + b11); + v = fast_at2(rx1,ry1,q); + b = lerp_m(sx, u, v); + + return lerp_m(sy, a, b); +} + +static F32 fast_at3(F32 rx, F32 ry, F32 rz, F32 *q) +{ + return rx * q[0] + ry * q[1] + rz * q[2]; +} + +F32 LLPerlinNoise::noise3(F32 x, F32 y, F32 z) +{ + U8 bx0, bx1, by0, by1, bz0, bz1; + S32 b00, b10, b01, b11; + F32 rx0, rx1, ry0, ry1, rz0, rz1, *q, sy, sz, a, b, c, d, t, u, v; + S32 i, j; + + if (!sInitialized) + init(); + + fast_setup(x, bx0,bx1, rx0,rx1); + fast_setup(y, by0,by1, ry0,ry1); + fast_setup(z, bz0,bz1, rz0,rz1); + + i = p[ bx0 ]; + j = p[ bx1 ]; + + b00 = p[ i + by0 ]; + b10 = p[ j + by0 ]; + b01 = p[ i + by1 ]; + b11 = p[ j + by1 ]; + + t = s_curve(rx0); + sy = s_curve(ry0); + sz = s_curve(rz0); + + q = g3[ b00 + bz0 ]; + u = fast_at3(rx0,ry0,rz0,q); + q = g3[ b10 + bz0 ]; + v = fast_at3(rx1,ry0,rz0,q); + a = lerp_m(t, u, v); + + q = g3[ b01 + bz0 ]; + u = fast_at3(rx0,ry1,rz0,q); + q = g3[ b11 + bz0 ]; + v = fast_at3(rx1,ry1,rz0,q); + b = lerp_m(t, u, v); + + c = lerp_m(sy, a, b); + + q = g3[ b00 + bz1 ]; + u = fast_at3(rx0,ry0,rz1,q); + q = g3[ b10 + bz1 ]; + v = fast_at3(rx1,ry0,rz1,q); + a = lerp_m(t, u, v); + + q = g3[ b01 + bz1 ]; + u = fast_at3(rx0,ry1,rz1,q); + q = g3[ b11 + bz1 ]; + v = fast_at3(rx1,ry1,rz1,q); + b = lerp_m(t, u, v); + + d = lerp_m(sy, a, b); + + return lerp_m(sz, c, d); +} + +F32 LLPerlinNoise::turbulence2(F32 x, F32 y, F32 freq) +{ + F32 t, lx, ly; + + for (t = 0.f ; freq >= 1.f ; freq *= 0.5f) + { + lx = freq * x; + ly = freq * y; + t += noise2(lx, ly)/freq; + } + return t; +} + +F32 LLPerlinNoise::turbulence3(F32 x, F32 y, F32 z, F32 freq) +{ + F32 t, lx, ly, lz; + + for (t = 0.f ; freq >= 1.f ; freq *= 0.5f) + { + lx = freq * x; + ly = freq * y; + lz = freq * z; + t += noise3(lx,ly,lz)/freq; +// t += fabs(noise3(lx,ly,lz)) / freq; // Like snow - bubbly at low frequencies +// t += sqrt(fabs(noise3(lx,ly,lz))) / freq; // Better at low freq +// t += (noise3(lx,ly,lz)*noise3(lx,ly,lz)) / freq; + } + return t; +} + +F32 LLPerlinNoise::clouds3(F32 x, F32 y, F32 z, F32 freq) +{ + F32 t, lx, ly, lz; + + for (t = 0.f ; freq >= 1.f ; freq *= 0.5f) + { + lx = freq * x; + ly = freq * y; + lz = freq * z; +// t += noise3(lx,ly,lz)/freq; +// t += fabs(noise3(lx,ly,lz)) / freq; // Like snow - bubbly at low frequencies +// t += sqrt(fabs(noise3(lx,ly,lz))) / freq; // Better at low freq + t += (noise3(lx,ly,lz)*noise3(lx,ly,lz)) / freq; + } + return t; +} diff --git a/indra/llmath/llperlin.h b/indra/llmath/llperlin.h new file mode 100644 index 0000000000..75e38bb8b6 --- /dev/null +++ b/indra/llmath/llperlin.h @@ -0,0 +1,28 @@ +/** + * @file llperlin.h + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_PERLIN_H +#define LL_PERLIN_H + +#include "stdtypes.h" + +// namespace wrapper +class LLPerlinNoise +{ +public: + static F32 noise1(F32 x); + static F32 noise2(F32 x, F32 y); + static F32 noise3(F32 x, F32 y, F32 z); + static F32 turbulence2(F32 x, F32 y, F32 freq); + static F32 turbulence3(F32 x, F32 y, F32 z, F32 freq); + static F32 clouds3(F32 x, F32 y, F32 z, F32 freq); +private: + static bool sInitialized; + static void init(void); +}; + +#endif // LL_PERLIN_ diff --git a/indra/llmath/llplane.h b/indra/llmath/llplane.h new file mode 100644 index 0000000000..0db1d31d80 --- /dev/null +++ b/indra/llmath/llplane.h @@ -0,0 +1,49 @@ +/** + * @file llplane.h + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLPLANE_H +#define LL_LLPLANE_H + +#include "v3math.h" +#include "v4math.h" + +// A simple way to specify a plane is to give its normal, +// and it's nearest approach to the origin. +// +// Given the equation for a plane : A*x + B*y + C*z + D = 0 +// The plane normal = [A, B, C] +// The closest approach = D / sqrt(A*A + B*B + C*C) + +class LLPlane : public LLVector4 +{ +public: + LLPlane() {}; // no default constructor + LLPlane(const LLVector3 &p0, F32 d) { setVec(p0, d); } + LLPlane(const LLVector3 &p0, const LLVector3 &n) { setVec(p0, n); } + void setVec(const LLVector3 &p0, F32 d) { LLVector4::setVec(p0[0], p0[1], p0[2], d); } + void setVec(const LLVector3 &p0, const LLVector3 &n) + { + F32 d = -(p0 * n); + setVec(n, d); + } + void setVec(const LLVector3 &p0, const LLVector3 &p1, const LLVector3 &p2) + { + LLVector3 u, v, w; + u = p1 - p0; + v = p2 - p0; + w = u % v; + w.normVec(); + F32 d = -(w * p0); + setVec(w, d); + } + LLPlane& operator=(const LLVector4& v2) { LLVector4::setVec(v2[0],v2[1],v2[2],v2[3]); return *this;} + F32 dist(const LLVector3 &v2) const { return mV[0]*v2[0] + mV[1]*v2[1] + mV[2]*v2[2] + mV[3]; } +}; + + + +#endif // LL_LLPLANE_H diff --git a/indra/llmath/llquantize.h b/indra/llmath/llquantize.h new file mode 100644 index 0000000000..8aa03628f2 --- /dev/null +++ b/indra/llmath/llquantize.h @@ -0,0 +1,103 @@ +/** + * @file llquantize.h + * @brief useful routines for quantizing floats to various length ints + * and back out again + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLQUANTIZE_H +#define LL_LLQUANTIZE_H + +const U16 U16MAX = 65535; +const F32 OOU16MAX = 1.f/(F32)(U16MAX); + +const U8 U8MAX = 255; +const F32 OOU8MAX = 1.f/(F32)(U8MAX); + +const U8 FIRSTVALIDCHAR = 54; +const U8 MAXSTRINGVAL = U8MAX - FIRSTVALIDCHAR; //we don't allow newline or null + + +inline U16 F32_to_U16(F32 val, F32 lower, F32 upper) +{ + val = llclamp(val, lower, upper); + // make sure that the value is positive and normalized to <0, 1> + val -= lower; + val /= (upper - lower); + + // return the U16 + return (U16)(llfloor(val*U16MAX)); +} + +inline F32 U16_to_F32(U16 ival, F32 lower, F32 upper) +{ + F32 val = ival*OOU16MAX; + F32 delta = (upper - lower); + val *= delta; + val += lower; + + F32 max_error = delta*OOU16MAX; + + // make sure that zero's come through as zero + if (fabsf(val) < max_error) + val = 0.f; + + return val; +} + +inline U8 F32_to_U8(F32 val, F32 lower, F32 upper) +{ + val = llclamp(val, lower, upper); + // make sure that the value is positive and normalized to <0, 1> + val -= lower; + val /= (upper - lower); + + // return the U8 + return (U8)(llfloor(val*U8MAX)); +} + +inline F32 U8_to_F32(U8 ival, F32 lower, F32 upper) +{ + F32 val = ival*OOU8MAX; + F32 delta = (upper - lower); + val *= delta; + val += lower; + + F32 max_error = delta*OOU8MAX; + + // make sure that zero's come through as zero + if (fabsf(val) < max_error) + val = 0.f; + + return val; +} + +inline U8 F32_TO_STRING(F32 val, F32 lower, F32 upper) +{ + val = llclamp(val, lower, upper); //[lower, upper] + // make sure that the value is positive and normalized to <0, 1> + val -= lower; //[0, upper-lower] + val /= (upper - lower); //[0,1] + val = val * MAXSTRINGVAL; //[0, MAXSTRINGVAL] + val = floor(val + 0.5f); //[0, MAXSTRINGVAL] + + U8 stringVal = (U8)(val) + FIRSTVALIDCHAR; //[FIRSTVALIDCHAR, MAXSTRINGVAL + FIRSTVALIDCHAR] + return stringVal; +} + +inline F32 STRING_TO_F32(U8 ival, F32 lower, F32 upper) +{ + // remove empty space left for NULL, newline, etc. + ival -= FIRSTVALIDCHAR; //[0, MAXSTRINGVAL] + + F32 val = (F32)ival * (1.f / (F32)MAXSTRINGVAL); //[0, 1] + F32 delta = (upper - lower); + val *= delta; //[0, upper - lower] + val += lower; //[lower, upper] + + return val; +} + +#endif diff --git a/indra/llmath/llquaternion.cpp b/indra/llmath/llquaternion.cpp new file mode 100644 index 0000000000..56a3830bb3 --- /dev/null +++ b/indra/llmath/llquaternion.cpp @@ -0,0 +1,830 @@ +/** + * @file qmath.cpp + * @brief LLQuaternion class implementation. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llquaternion.h" + +#include "llmath.h" // for F_PI +//#include "vmath.h" +#include "v3math.h" +#include "v3dmath.h" +#include "v4math.h" +#include "m4math.h" +#include "m3math.h" +#include "llquantize.h" + +// WARNING: Don't use this for global const definitions! using this +// at the top of a *.cpp file might not give you what you think. +const LLQuaternion LLQuaternion::DEFAULT; + +// Constructors + +LLQuaternion::LLQuaternion(const LLMatrix4 &mat) +{ + *this = mat.quaternion(); + normQuat(); +} + +LLQuaternion::LLQuaternion(const LLMatrix3 &mat) +{ + *this = mat.quaternion(); + normQuat(); +} + +LLQuaternion::LLQuaternion(F32 angle, const LLVector4 &vec) +{ + LLVector3 v(vec.mV[VX], vec.mV[VY], vec.mV[VZ]); + v.normVec(); + + F32 c, s; + c = cosf(angle*0.5f); + s = sinf(angle*0.5f); + + mQ[VX] = v.mV[VX] * s; + mQ[VY] = v.mV[VY] * s; + mQ[VZ] = v.mV[VZ] * s; + mQ[VW] = c; + normQuat(); +} + +LLQuaternion::LLQuaternion(F32 angle, const LLVector3 &vec) +{ + LLVector3 v(vec); + v.normVec(); + + F32 c, s; + c = cosf(angle*0.5f); + s = sinf(angle*0.5f); + + mQ[VX] = v.mV[VX] * s; + mQ[VY] = v.mV[VY] * s; + mQ[VZ] = v.mV[VZ] * s; + mQ[VW] = c; + normQuat(); +} + +LLQuaternion::LLQuaternion(const LLVector3 &x_axis, + const LLVector3 &y_axis, + const LLVector3 &z_axis) +{ + LLMatrix3 mat; + mat.setRows(x_axis, y_axis, z_axis); + *this = mat.quaternion(); + normQuat(); +} + +// Quatizations +void LLQuaternion::quantize16(F32 lower, F32 upper) +{ + F32 x = mQ[VX]; + F32 y = mQ[VY]; + F32 z = mQ[VZ]; + F32 s = mQ[VS]; + + x = U16_to_F32(F32_to_U16(x, lower, upper), lower, upper); + y = U16_to_F32(F32_to_U16(y, lower, upper), lower, upper); + z = U16_to_F32(F32_to_U16(z, lower, upper), lower, upper); + s = U16_to_F32(F32_to_U16(s, lower, upper), lower, upper); + + mQ[VX] = x; + mQ[VY] = y; + mQ[VZ] = z; + mQ[VS] = s; +} + +void LLQuaternion::quantize8(F32 lower, F32 upper) +{ + mQ[VX] = U8_to_F32(F32_to_U8(mQ[VX], lower, upper), lower, upper); + mQ[VY] = U8_to_F32(F32_to_U8(mQ[VY], lower, upper), lower, upper); + mQ[VZ] = U8_to_F32(F32_to_U8(mQ[VZ], lower, upper), lower, upper); + mQ[VS] = U8_to_F32(F32_to_U8(mQ[VS], lower, upper), lower, upper); +} + +// LLVector3 Magnitude and Normalization Functions + + +// Set LLQuaternion routines + +const LLQuaternion& LLQuaternion::setQuat(F32 angle, F32 x, F32 y, F32 z) +{ + LLVector3 vec(x, y, z); + vec.normVec(); + + angle *= 0.5f; + F32 c, s; + c = cosf(angle); + s = sinf(angle); + + mQ[VX] = vec.mV[VX]*s; + mQ[VY] = vec.mV[VY]*s; + mQ[VZ] = vec.mV[VZ]*s; + mQ[VW] = c; + + normQuat(); + return (*this); +} + +const LLQuaternion& LLQuaternion::setQuat(F32 angle, const LLVector3 &vec) +{ + LLVector3 v(vec); + v.normVec(); + + angle *= 0.5f; + F32 c, s; + c = cosf(angle); + s = sinf(angle); + + mQ[VX] = v.mV[VX]*s; + mQ[VY] = v.mV[VY]*s; + mQ[VZ] = v.mV[VZ]*s; + mQ[VW] = c; + + normQuat(); + return (*this); +} + +const LLQuaternion& LLQuaternion::setQuat(F32 angle, const LLVector4 &vec) +{ + LLVector3 v(vec.mV[VX], vec.mV[VY], vec.mV[VZ]); + v.normVec(); + + F32 c, s; + c = cosf(angle*0.5f); + s = sinf(angle*0.5f); + + mQ[VX] = v.mV[VX]*s; + mQ[VY] = v.mV[VY]*s; + mQ[VZ] = v.mV[VZ]*s; + mQ[VW] = c; + + normQuat(); + return (*this); +} + +const LLQuaternion& LLQuaternion::setQuat(F32 roll, F32 pitch, F32 yaw) +{ + LLMatrix3 rot_mat(roll, pitch, yaw); + rot_mat.orthogonalize(); + *this = rot_mat.quaternion(); + + normQuat(); + return (*this); +//#if 1 +// // NOTE: LLQuaternion's are actually inverted with respect to +// // the matrices, so this code also assumes inverted quaternions +// // (-x, -y, -z, w). The result is that roll,pitch,yaw are applied +// // in reverse order (yaw,pitch,roll). +// F64 cosX = cos(roll); +// F64 cosY = cos(pitch); +// F64 cosZ = cos(yaw); +// +// F64 sinX = sin(roll); +// F64 sinY = sin(pitch); +// F64 sinZ = sin(yaw); +// +// mQ[VW] = (F32)sqrt(cosY*cosZ - sinX*sinY*sinZ + cosX*cosZ + cosX*cosY + 1.0)*.5; +// if (fabs(mQ[VW]) < F_APPROXIMATELY_ZERO) +// { +// // null rotation, any axis will do +// mQ[VX] = 0.0f; +// mQ[VY] = 1.0f; +// mQ[VZ] = 0.0f; +// } +// else +// { +// F32 inv_s = 1.0f / (4.0f * mQ[VW]); +// mQ[VX] = (F32)-(-sinX*cosY - cosX*sinY*sinZ - sinX*cosZ) * inv_s; +// mQ[VY] = (F32)-(-cosX*sinY*cosZ + sinX*sinZ - sinY) * inv_s; +// mQ[VZ] = (F32)-(-cosY*sinZ - sinX*sinY*cosZ - cosX*sinZ) * inv_s; +// } +// +//#else // This only works on a certain subset of roll/pitch/yaw +// +// F64 cosX = cosf(roll/2.0); +// F64 cosY = cosf(pitch/2.0); +// F64 cosZ = cosf(yaw/2.0); +// +// F64 sinX = sinf(roll/2.0); +// F64 sinY = sinf(pitch/2.0); +// F64 sinZ = sinf(yaw/2.0); +// +// mQ[VW] = (F32)(cosX*cosY*cosZ + sinX*sinY*sinZ); +// mQ[VX] = (F32)(sinX*cosY*cosZ - cosX*sinY*sinZ); +// mQ[VY] = (F32)(cosX*sinY*cosZ + sinX*cosY*sinZ); +// mQ[VZ] = (F32)(cosX*cosY*sinZ - sinX*sinY*cosZ); +//#endif +// +// normQuat(); +// return (*this); +} + +// SJB: This code is correct for a logicly stored (non-transposed) matrix; +// Our matrices are stored transposed, OpenGL style, so this generates the +// INVERSE matrix, or the CORRECT matrix form an INVERSE quaternion. +// Because we use similar logic in LLMatrix3::quaternion(), +// we are internally consistant so everything works OK :) +LLMatrix3 LLQuaternion::getMatrix3(void) const +{ + LLMatrix3 mat; + F32 xx, xy, xz, xw, yy, yz, yw, zz, zw; + + xx = mQ[VX] * mQ[VX]; + xy = mQ[VX] * mQ[VY]; + xz = mQ[VX] * mQ[VZ]; + xw = mQ[VX] * mQ[VW]; + + yy = mQ[VY] * mQ[VY]; + yz = mQ[VY] * mQ[VZ]; + yw = mQ[VY] * mQ[VW]; + + zz = mQ[VZ] * mQ[VZ]; + zw = mQ[VZ] * mQ[VW]; + + mat.mMatrix[0][0] = 1.f - 2.f * ( yy + zz ); + mat.mMatrix[0][1] = 2.f * ( xy + zw ); + mat.mMatrix[0][2] = 2.f * ( xz - yw ); + + mat.mMatrix[1][0] = 2.f * ( xy - zw ); + mat.mMatrix[1][1] = 1.f - 2.f * ( xx + zz ); + mat.mMatrix[1][2] = 2.f * ( yz + xw ); + + mat.mMatrix[2][0] = 2.f * ( xz + yw ); + mat.mMatrix[2][1] = 2.f * ( yz - xw ); + mat.mMatrix[2][2] = 1.f - 2.f * ( xx + yy ); + + return mat; +} + +LLMatrix4 LLQuaternion::getMatrix4(void) const +{ + LLMatrix4 mat; + F32 xx, xy, xz, xw, yy, yz, yw, zz, zw; + + xx = mQ[VX] * mQ[VX]; + xy = mQ[VX] * mQ[VY]; + xz = mQ[VX] * mQ[VZ]; + xw = mQ[VX] * mQ[VW]; + + yy = mQ[VY] * mQ[VY]; + yz = mQ[VY] * mQ[VZ]; + yw = mQ[VY] * mQ[VW]; + + zz = mQ[VZ] * mQ[VZ]; + zw = mQ[VZ] * mQ[VW]; + + mat.mMatrix[0][0] = 1.f - 2.f * ( yy + zz ); + mat.mMatrix[0][1] = 2.f * ( xy + zw ); + mat.mMatrix[0][2] = 2.f * ( xz - yw ); + + mat.mMatrix[1][0] = 2.f * ( xy - zw ); + mat.mMatrix[1][1] = 1.f - 2.f * ( xx + zz ); + mat.mMatrix[1][2] = 2.f * ( yz + xw ); + + mat.mMatrix[2][0] = 2.f * ( xz + yw ); + mat.mMatrix[2][1] = 2.f * ( yz - xw ); + mat.mMatrix[2][2] = 1.f - 2.f * ( xx + yy ); + + // TODO -- should we set the translation portion to zero? + + return mat; +} + + + + +// Other useful methods + + +// calculate the shortest rotation from a to b +void LLQuaternion::shortestArc(const LLVector3 &a, const LLVector3 &b) +{ + // Make a local copy of both vectors. + LLVector3 vec_a = a; + LLVector3 vec_b = b; + + // Make sure neither vector is zero length. Also normalize + // the vectors while we are at it. + F32 vec_a_mag = vec_a.normVec(); + F32 vec_b_mag = vec_b.normVec(); + if (vec_a_mag < F_APPROXIMATELY_ZERO || + vec_b_mag < F_APPROXIMATELY_ZERO) + { + // Can't calculate a rotation from this. + // Just return ZERO_ROTATION instead. + loadIdentity(); + return; + } + + // Create an axis to rotate around, and the cos of the angle to rotate. + LLVector3 axis = vec_a % vec_b; + F32 cos_theta = vec_a * vec_b; + + // Check the angle between the vectors to see if they are parallel or anti-parallel. + if (cos_theta > 1.0 - F_APPROXIMATELY_ZERO) + { + // a and b are parallel. No rotation is necessary. + loadIdentity(); + } + else if (cos_theta < -1.0 + F_APPROXIMATELY_ZERO) + { + // a and b are anti-parallel. + // Rotate 180 degrees around some orthogonal axis. + // Find the projection of the x-axis onto a, and try + // using the vector between the projection and the x-axis + // as the orthogonal axis. + LLVector3 proj = vec_a.mV[VX] / (vec_a * vec_a) * vec_a; + LLVector3 ortho_axis(1.f, 0.f, 0.f); + ortho_axis -= proj; + + // Turn this into an orthonormal axis. + F32 ortho_length = ortho_axis.normVec(); + // If the axis' length is 0, then our guess at an orthogonal axis + // was wrong (a is parallel to the x-axis). + if (ortho_length < F_APPROXIMATELY_ZERO) + { + // Use the z-axis instead. + ortho_axis.setVec(0.f, 0.f, 1.f); + } + + // Construct a quaternion from this orthonormal axis. + mQ[VX] = ortho_axis.mV[VX]; + mQ[VY] = ortho_axis.mV[VY]; + mQ[VZ] = ortho_axis.mV[VZ]; + mQ[VW] = 0.f; + } + else + { + // a and b are NOT parallel or anti-parallel. + // Return the rotation between these vectors. + F32 theta = (F32)acos(cos_theta); + + setQuat(theta, axis); + } +} + +// constrains rotation to a cone angle specified in radians +const LLQuaternion &LLQuaternion::constrain(F32 radians) +{ + const F32 cos_angle_lim = cosf( radians/2 ); // mQ[VW] limit + const F32 sin_angle_lim = sinf( radians/2 ); // rotation axis length limit + + if (mQ[VW] < 0.f) + { + mQ[VX] *= -1.f; + mQ[VY] *= -1.f; + mQ[VZ] *= -1.f; + mQ[VW] *= -1.f; + } + + // if rotation angle is greater than limit (cos is less than limit) + if( mQ[VW] < cos_angle_lim ) + { + mQ[VW] = cos_angle_lim; + F32 axis_len = sqrtf( mQ[VX]*mQ[VX] + mQ[VY]*mQ[VY] + mQ[VZ]*mQ[VZ] ); // sin(theta/2) + F32 axis_mult_fact = sin_angle_lim / axis_len; + mQ[VX] *= axis_mult_fact; + mQ[VY] *= axis_mult_fact; + mQ[VZ] *= axis_mult_fact; + } + + return *this; +} + +// Operators + +std::ostream& operator<<(std::ostream &s, const LLQuaternion &a) +{ + s << "{ " + << a.mQ[VX] << ", " << a.mQ[VY] << ", " << a.mQ[VZ] << ", " << a.mQ[VW] + << " }"; + return s; +} + + +// Does NOT renormalize the result +LLQuaternion operator*(const LLQuaternion &a, const LLQuaternion &b) +{ +// LLQuaternion::mMultCount++; + + LLQuaternion q( + b.mQ[3] * a.mQ[0] + b.mQ[0] * a.mQ[3] + b.mQ[1] * a.mQ[2] - b.mQ[2] * a.mQ[1], + b.mQ[3] * a.mQ[1] + b.mQ[1] * a.mQ[3] + b.mQ[2] * a.mQ[0] - b.mQ[0] * a.mQ[2], + b.mQ[3] * a.mQ[2] + b.mQ[2] * a.mQ[3] + b.mQ[0] * a.mQ[1] - b.mQ[1] * a.mQ[0], + b.mQ[3] * a.mQ[3] - b.mQ[0] * a.mQ[0] - b.mQ[1] * a.mQ[1] - b.mQ[2] * a.mQ[2] + ); + return q; +} + +/* +LLMatrix4 operator*(const LLMatrix4 &m, const LLQuaternion &q) +{ + LLMatrix4 qmat(q); + return (m*qmat); +} +*/ + + + +LLVector4 operator*(const LLVector4 &a, const LLQuaternion &rot) +{ + F32 rw = - rot.mQ[VX] * a.mV[VX] - rot.mQ[VY] * a.mV[VY] - rot.mQ[VZ] * a.mV[VZ]; + F32 rx = rot.mQ[VW] * a.mV[VX] + rot.mQ[VY] * a.mV[VZ] - rot.mQ[VZ] * a.mV[VY]; + F32 ry = rot.mQ[VW] * a.mV[VY] + rot.mQ[VZ] * a.mV[VX] - rot.mQ[VX] * a.mV[VZ]; + F32 rz = rot.mQ[VW] * a.mV[VZ] + rot.mQ[VX] * a.mV[VY] - rot.mQ[VY] * a.mV[VX]; + + F32 nx = - rw * rot.mQ[VX] + rx * rot.mQ[VW] - ry * rot.mQ[VZ] + rz * rot.mQ[VY]; + F32 ny = - rw * rot.mQ[VY] + ry * rot.mQ[VW] - rz * rot.mQ[VX] + rx * rot.mQ[VZ]; + F32 nz = - rw * rot.mQ[VZ] + rz * rot.mQ[VW] - rx * rot.mQ[VY] + ry * rot.mQ[VX]; + + return LLVector4(nx, ny, nz, a.mV[VW]); +} + +LLVector3 operator*(const LLVector3 &a, const LLQuaternion &rot) +{ + F32 rw = - rot.mQ[VX] * a.mV[VX] - rot.mQ[VY] * a.mV[VY] - rot.mQ[VZ] * a.mV[VZ]; + F32 rx = rot.mQ[VW] * a.mV[VX] + rot.mQ[VY] * a.mV[VZ] - rot.mQ[VZ] * a.mV[VY]; + F32 ry = rot.mQ[VW] * a.mV[VY] + rot.mQ[VZ] * a.mV[VX] - rot.mQ[VX] * a.mV[VZ]; + F32 rz = rot.mQ[VW] * a.mV[VZ] + rot.mQ[VX] * a.mV[VY] - rot.mQ[VY] * a.mV[VX]; + + F32 nx = - rw * rot.mQ[VX] + rx * rot.mQ[VW] - ry * rot.mQ[VZ] + rz * rot.mQ[VY]; + F32 ny = - rw * rot.mQ[VY] + ry * rot.mQ[VW] - rz * rot.mQ[VX] + rx * rot.mQ[VZ]; + F32 nz = - rw * rot.mQ[VZ] + rz * rot.mQ[VW] - rx * rot.mQ[VY] + ry * rot.mQ[VX]; + + return LLVector3(nx, ny, nz); +} + +LLVector3d operator*(const LLVector3d &a, const LLQuaternion &rot) +{ + F64 rw = - rot.mQ[VX] * a.mdV[VX] - rot.mQ[VY] * a.mdV[VY] - rot.mQ[VZ] * a.mdV[VZ]; + F64 rx = rot.mQ[VW] * a.mdV[VX] + rot.mQ[VY] * a.mdV[VZ] - rot.mQ[VZ] * a.mdV[VY]; + F64 ry = rot.mQ[VW] * a.mdV[VY] + rot.mQ[VZ] * a.mdV[VX] - rot.mQ[VX] * a.mdV[VZ]; + F64 rz = rot.mQ[VW] * a.mdV[VZ] + rot.mQ[VX] * a.mdV[VY] - rot.mQ[VY] * a.mdV[VX]; + + F64 nx = - rw * rot.mQ[VX] + rx * rot.mQ[VW] - ry * rot.mQ[VZ] + rz * rot.mQ[VY]; + F64 ny = - rw * rot.mQ[VY] + ry * rot.mQ[VW] - rz * rot.mQ[VX] + rx * rot.mQ[VZ]; + F64 nz = - rw * rot.mQ[VZ] + rz * rot.mQ[VW] - rx * rot.mQ[VY] + ry * rot.mQ[VX]; + + return LLVector3d(nx, ny, nz); +} + +F32 dot(const LLQuaternion &a, const LLQuaternion &b) +{ + return a.mQ[VX] * b.mQ[VX] + + a.mQ[VY] * b.mQ[VY] + + a.mQ[VZ] * b.mQ[VZ] + + a.mQ[VW] * b.mQ[VW]; +} + +// DEMO HACK: This lerp is probably inocrrect now due intermediate normalization +// it should look more like the lerp below +#if 0 +// linear interpolation +LLQuaternion lerp(F32 t, const LLQuaternion &p, const LLQuaternion &q) +{ + LLQuaternion r; + r = t * (q - p) + p; + r.normQuat(); + return r; +} +#endif + +// lerp from identity to q +LLQuaternion lerp(F32 t, const LLQuaternion &q) +{ + LLQuaternion r; + r.mQ[VX] = t * q.mQ[VX]; + r.mQ[VY] = t * q.mQ[VY]; + r.mQ[VZ] = t * q.mQ[VZ]; + r.mQ[VW] = t * (q.mQ[VZ] - 1.f) + 1.f; + r.normQuat(); + return r; +} + +LLQuaternion lerp(F32 t, const LLQuaternion &p, const LLQuaternion &q) +{ + LLQuaternion r; + F32 inv_t; + + inv_t = 1.f - t; + + r.mQ[VX] = t * q.mQ[VX] + (inv_t * p.mQ[VX]); + r.mQ[VY] = t * q.mQ[VY] + (inv_t * p.mQ[VY]); + r.mQ[VZ] = t * q.mQ[VZ] + (inv_t * p.mQ[VZ]); + r.mQ[VW] = t * q.mQ[VW] + (inv_t * p.mQ[VW]); + r.normQuat(); + return r; +} + + +// spherical linear interpolation +LLQuaternion slerp( F32 u, const LLQuaternion &a, const LLQuaternion &b ) +{ + // cosine theta = dot product of a and b + F32 cos_t = a.mQ[0]*b.mQ[0] + a.mQ[1]*b.mQ[1] + a.mQ[2]*b.mQ[2] + a.mQ[3]*b.mQ[3]; + + // if b is on opposite hemisphere from a, use -a instead + int bflip; + if (cos_t < 0.0f) + { + cos_t = -cos_t; + bflip = TRUE; + } + else + bflip = FALSE; + + // if B is (within precision limits) the same as A, + // just linear interpolate between A and B. + F32 alpha; // interpolant + F32 beta; // 1 - interpolant + if (1.0f - cos_t < 0.00001f) + { + beta = 1.0f - u; + alpha = u; + } + else + { + F32 theta = acosf(cos_t); + F32 sin_t = sinf(theta); + beta = sinf(theta - u*theta) / sin_t; + alpha = sinf(u*theta) / sin_t; + } + + if (bflip) + beta = -beta; + + // interpolate + LLQuaternion ret; + ret.mQ[0] = beta*a.mQ[0] + alpha*b.mQ[0]; + ret.mQ[1] = beta*a.mQ[1] + alpha*b.mQ[1]; + ret.mQ[2] = beta*a.mQ[2] + alpha*b.mQ[2]; + ret.mQ[3] = beta*a.mQ[3] + alpha*b.mQ[3]; + + return ret; +} + +// lerp whenever possible +LLQuaternion nlerp(F32 t, const LLQuaternion &a, const LLQuaternion &b) +{ + if (dot(a, b) < 0.f) + { + return slerp(t, a, b); + } + else + { + return lerp(t, a, b); + } +} + +LLQuaternion nlerp(F32 t, const LLQuaternion &q) +{ + if (q.mQ[VW] < 0.f) + { + return slerp(t, q); + } + else + { + return lerp(t, q); + } +} + +// slerp from identity quaternion to another quaternion +LLQuaternion slerp(F32 t, const LLQuaternion &q) +{ + F32 c = q.mQ[VW]; + if (1.0f == t || 1.0f == c) + { + // the trivial cases + return q; + } + + LLQuaternion r; + F32 s, angle, stq, stp; + + s = (F32) sqrt(1.f - c*c); + + if (c < 0.0f) + { + // when c < 0.0 then theta > PI/2 + // since quat and -quat are the same rotation we invert one of + // p or q to reduce unecessary spins + // A equivalent way to do it is to convert acos(c) as if it had been negative, + // and to negate stp + angle = (F32) acos(-c); + stp = -(F32) sin(angle * (1.f - t)); + stq = (F32) sin(angle * t); + } + else + { + angle = (F32) acos(c); + stp = (F32) sin(angle * (1.f - t)); + stq = (F32) sin(angle * t); + } + + r.mQ[VX] = (q.mQ[VX] * stq) / s; + r.mQ[VY] = (q.mQ[VY] * stq) / s; + r.mQ[VZ] = (q.mQ[VZ] * stq) / s; + r.mQ[VW] = (stp + q.mQ[VW] * stq) / s; + + return r; +} + +LLQuaternion mayaQ(F32 xRot, F32 yRot, F32 zRot, LLQuaternion::Order order) +{ + LLQuaternion xQ( xRot*DEG_TO_RAD, LLVector3(1.0f, 0.0f, 0.0f) ); + LLQuaternion yQ( yRot*DEG_TO_RAD, LLVector3(0.0f, 1.0f, 0.0f) ); + LLQuaternion zQ( zRot*DEG_TO_RAD, LLVector3(0.0f, 0.0f, 1.0f) ); + LLQuaternion ret; + switch( order ) + { + case LLQuaternion::XYZ: + ret = xQ * yQ * zQ; + break; + case LLQuaternion::YZX: + ret = yQ * zQ * xQ; + break; + case LLQuaternion::ZXY: + ret = zQ * xQ * yQ; + break; + case LLQuaternion::XZY: + ret = xQ * zQ * yQ; + break; + case LLQuaternion::YXZ: + ret = yQ * xQ * zQ; + break; + case LLQuaternion::ZYX: + ret = zQ * yQ * xQ; + break; + } + return ret; +} + +const char *OrderToString( const LLQuaternion::Order order ) +{ + char *p = NULL; + switch( order ) + { + default: + case LLQuaternion::XYZ: + p = "XYZ"; + break; + case LLQuaternion::YZX: + p = "YZX"; + break; + case LLQuaternion::ZXY: + p = "ZXY"; + break; + case LLQuaternion::XZY: + p = "XZY"; + break; + case LLQuaternion::YXZ: + p = "YXZ"; + break; + case LLQuaternion::ZYX: + p = "ZYX"; + break; + } + return p; +} + +LLQuaternion::Order StringToOrder( const char *str ) +{ + if (strncmp(str, "XYZ", 3)==0 || strncmp(str, "xyz", 3)==0) + return LLQuaternion::XYZ; + + if (strncmp(str, "YZX", 3)==0 || strncmp(str, "yzx", 3)==0) + return LLQuaternion::YZX; + + if (strncmp(str, "ZXY", 3)==0 || strncmp(str, "zxy", 3)==0) + return LLQuaternion::ZXY; + + if (strncmp(str, "XZY", 3)==0 || strncmp(str, "xzy", 3)==0) + return LLQuaternion::XZY; + + if (strncmp(str, "YXZ", 3)==0 || strncmp(str, "yxz", 3)==0) + return LLQuaternion::YXZ; + + if (strncmp(str, "ZYX", 3)==0 || strncmp(str, "zyx", 3)==0) + return LLQuaternion::ZYX; + + return LLQuaternion::XYZ; +} + +const LLQuaternion& LLQuaternion::setQuat(const LLMatrix3 &mat) +{ + *this = mat.quaternion(); + normQuat(); + return (*this); +} + +const LLQuaternion& LLQuaternion::setQuat(const LLMatrix4 &mat) +{ + *this = mat.quaternion(); + normQuat(); + return (*this); +} + +void LLQuaternion::getAngleAxis(F32* angle, LLVector3 &vec) const +{ + F32 cos_a = mQ[VW]; + if (cos_a > 1.0f) cos_a = 1.0f; + if (cos_a < -1.0f) cos_a = -1.0f; + + F32 sin_a = (F32) sqrt( 1.0f - cos_a * cos_a ); + + if ( fabs( sin_a ) < 0.0005f ) + sin_a = 1.0f; + else + sin_a = 1.f/sin_a; + + *angle = 2.0f * (F32) acos( cos_a ); + vec.mV[VX] = mQ[VX] * sin_a; + vec.mV[VY] = mQ[VY] * sin_a; + vec.mV[VZ] = mQ[VZ] * sin_a; +} + + +// quaternion does not need to be normalized +void LLQuaternion::getEulerAngles(F32 *roll, F32 *pitch, F32 *yaw) const +{ + LLMatrix3 rot_mat(*this); + rot_mat.orthogonalize(); + rot_mat.getEulerAngles(roll, pitch, yaw); + +// // NOTE: LLQuaternion's are actually inverted with respect to +// // the matrices, so this code also assumes inverted quaternions +// // (-x, -y, -z, w). The result is that roll,pitch,yaw are applied +// // in reverse order (yaw,pitch,roll). +// F32 x = -mQ[VX], y = -mQ[VY], z = -mQ[VZ], w = mQ[VW]; +// F64 m20 = 2.0*(x*z-y*w); +// if (1.0f - fabsf(m20) < F_APPROXIMATELY_ZERO) +// { +// *roll = 0.0f; +// *pitch = (F32)asin(m20); +// *yaw = (F32)atan2(2.0*(x*y-z*w), 1.0 - 2.0*(x*x+z*z)); +// } +// else +// { +// *roll = (F32)atan2(-2.0*(y*z+x*w), 1.0-2.0*(x*x+y*y)); +// *pitch = (F32)asin(m20); +// *yaw = (F32)atan2(-2.0*(x*y+z*w), 1.0-2.0*(y*y+z*z)); +// } +} + +// Saves space by using the fact that our quaternions are normalized +LLVector3 LLQuaternion::packToVector3() const +{ + if( mQ[VW] >= 0 ) + { + return LLVector3( mQ[VX], mQ[VY], mQ[VZ] ); + } + else + { + return LLVector3( -mQ[VX], -mQ[VY], -mQ[VZ] ); + } +} + +// Saves space by using the fact that our quaternions are normalized +void LLQuaternion::unpackFromVector3( const LLVector3& vec ) +{ + mQ[VX] = vec.mV[VX]; + mQ[VY] = vec.mV[VY]; + mQ[VZ] = vec.mV[VZ]; + F32 t = 1.f - vec.magVecSquared(); + if( t > 0 ) + { + mQ[VW] = sqrt( t ); + } + else + { + // Need this to avoid trying to find the square root of a negative number due + // to floating point error. + mQ[VW] = 0; + } +} + +BOOL LLQuaternion::parseQuat(const char* buf, LLQuaternion* value) +{ + if( buf == NULL || buf[0] == '\0' || value == NULL) + { + return FALSE; + } + + LLQuaternion quat; + S32 count = sscanf( buf, "%f %f %f %f", quat.mQ + 0, quat.mQ + 1, quat.mQ + 2, quat.mQ + 3 ); + if( 4 == count ) + { + value->setQuat( quat ); + return TRUE; + } + + return FALSE; +} + + +// End diff --git a/indra/llmath/llquaternion.h b/indra/llmath/llquaternion.h new file mode 100644 index 0000000000..558eeec341 --- /dev/null +++ b/indra/llmath/llquaternion.h @@ -0,0 +1,442 @@ +/** + * @file llquaternion.h + * @brief LLQuaternion class header file. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LLQUATERNION_H +#define LLQUATERNION_H + +#include "llmath.h" + +class LLVector4; +class LLVector3; +class LLVector3d; +class LLMatrix4; +class LLMatrix3; + +// NOTA BENE: Quaternion code is written assuming Unit Quaternions!!!! +// Moreover, it is written assuming that all vectors and matricies +// passed as arguments are normalized and unitary respectively. +// VERY VERY VERY VERY BAD THINGS will happen if these assumptions fail. + +static const U32 LENGTHOFQUAT = 4; + +class LLQuaternion +{ +public: + F32 mQ[LENGTHOFQUAT]; + + static const LLQuaternion DEFAULT; + + LLQuaternion(); // Initializes Quaternion to (0,0,0,1) + explicit LLQuaternion(const LLMatrix4 &mat); // Initializes Quaternion from Matrix4 + explicit LLQuaternion(const LLMatrix3 &mat); // Initializes Quaternion from Matrix3 + LLQuaternion(F32 x, F32 y, F32 z, F32 w); // Initializes Quaternion to normQuat(x, y, z, w) + LLQuaternion(F32 angle, const LLVector4 &vec); // Initializes Quaternion to axis_angle2quat(angle, vec) + LLQuaternion(F32 angle, const LLVector3 &vec); // Initializes Quaternion to axis_angle2quat(angle, vec) + LLQuaternion(const F32 *q); // Initializes Quaternion to normQuat(x, y, z, w) + LLQuaternion(const LLVector3 &x_axis, + const LLVector3 &y_axis, + const LLVector3 &z_axis); // Initializes Quaternion from Matrix3 = [x_axis ; y_axis ; z_axis] + + BOOL isIdentity() const; + BOOL isNotIdentity() const; + BOOL isFinite() const; // checks to see if all values of LLQuaternion are finite + void quantize16(F32 lower, F32 upper); // changes the vector to reflect quatization + void quantize8(F32 lower, F32 upper); // changes the vector to reflect quatization + void loadIdentity(); // Loads the quaternion that represents the identity rotation + const LLQuaternion& setQuatInit(F32 x, F32 y, F32 z, F32 w); // Sets Quaternion to normQuat(x, y, z, w) + const LLQuaternion& setQuat(const LLQuaternion &quat); // Copies Quaternion + const LLQuaternion& setQuat(const F32 *q); // Sets Quaternion to normQuat(quat[VX], quat[VY], quat[VZ], quat[VW]) + const LLQuaternion& setQuat(const LLMatrix3 &mat); // Sets Quaternion to mat2quat(mat) + const LLQuaternion& setQuat(const LLMatrix4 &mat); // Sets Quaternion to mat2quat(mat) + const LLQuaternion& setQuat(F32 angle, F32 x, F32 y, F32 z); // Sets Quaternion to axis_angle2quat(angle, x, y, z) + const LLQuaternion& setQuat(F32 angle, const LLVector3 &vec); // Sets Quaternion to axis_angle2quat(angle, vec) + const LLQuaternion& setQuat(F32 angle, const LLVector4 &vec); // Sets Quaternion to axis_angle2quat(angle, vec) + const LLQuaternion& setQuat(F32 roll, F32 pitch, F32 yaw); // Sets Quaternion to euler2quat(pitch, yaw, roll) + + LLMatrix4 getMatrix4(void) const; // Returns the Matrix4 equivalent of Quaternion + LLMatrix3 getMatrix3(void) const; // Returns the Matrix3 equivalent of Quaternion + void getAngleAxis(F32* angle, F32* x, F32* y, F32* z) const; // returns rotation in radians about axis x,y,z + void getAngleAxis(F32* angle, LLVector3 &vec) const; + void getEulerAngles(F32 *roll, F32* pitch, F32 *yaw) const; + + F32 normQuat(); // Normalizes Quaternion and returns magnitude + const LLQuaternion& conjQuat(void); // Conjugates Quaternion and returns result + + // Other useful methods + const LLQuaternion& transQuat(); // Transpose + void shortestArc(const LLVector3 &a, const LLVector3 &b); // shortest rotation from a to b + const LLQuaternion& constrain(F32 radians); // constrains rotation to a cone angle specified in radians + + // Standard operators + friend std::ostream& operator<<(std::ostream &s, const LLQuaternion &a); // Prints a + friend LLQuaternion operator+(const LLQuaternion &a, const LLQuaternion &b); // Addition + friend LLQuaternion operator-(const LLQuaternion &a, const LLQuaternion &b); // Subtraction + friend LLQuaternion operator-(const LLQuaternion &a); // Negation + friend LLQuaternion operator*(F32 a, const LLQuaternion &q); // Scale + friend LLQuaternion operator*(const LLQuaternion &q, F32 b); // Scale + friend LLQuaternion operator*(const LLQuaternion &a, const LLQuaternion &b); // Returns a * b + friend LLQuaternion operator~(const LLQuaternion &a); // Returns a* (Conjugate of a) + bool operator==(const LLQuaternion &b) const; // Returns a == b + bool operator!=(const LLQuaternion &b) const; // Returns a != b + + friend const LLQuaternion& operator*=(LLQuaternion &a, const LLQuaternion &b); // Returns a * b + + friend LLVector4 operator*(const LLVector4 &a, const LLQuaternion &rot); // Rotates a by rot + friend LLVector3 operator*(const LLVector3 &a, const LLQuaternion &rot); // Rotates a by rot + friend LLVector3d operator*(const LLVector3d &a, const LLQuaternion &rot); // Rotates a by rot + + // Non-standard operators + friend F32 dot(const LLQuaternion &a, const LLQuaternion &b); + friend LLQuaternion lerp(F32 t, const LLQuaternion &p, const LLQuaternion &q); // linear interpolation (t = 0 to 1) from p to q + friend LLQuaternion lerp(F32 t, const LLQuaternion &q); // linear interpolation (t = 0 to 1) from identity to q + friend LLQuaternion slerp(F32 t, const LLQuaternion &p, const LLQuaternion &q); // spherical linear interpolation from p to q + friend LLQuaternion slerp(F32 t, const LLQuaternion &q); // spherical linear interpolation from identity to q + friend LLQuaternion nlerp(F32 t, const LLQuaternion &p, const LLQuaternion &q); // normalized linear interpolation from p to q + friend LLQuaternion nlerp(F32 t, const LLQuaternion &q); // normalized linear interpolation from p to q + + LLVector3 packToVector3() const; // Saves space by using the fact that our quaternions are normalized + void unpackFromVector3(const LLVector3& vec); // Saves space by using the fact that our quaternions are normalized + + enum Order { + XYZ = 0, + YZX = 1, + ZXY = 2, + XZY = 3, + YXZ = 4, + ZYX = 5 + }; + // Creates a quaternions from maya's rotation representation, + // which is 3 rotations (in DEGREES) in the specified order + friend LLQuaternion mayaQ(F32 x, F32 y, F32 z, Order order); + + // Conversions between Order and strings like "xyz" or "ZYX" + friend const char *OrderToString( const Order order ); + friend Order StringToOrder( const char *str ); + + static BOOL parseQuat(const char* buf, LLQuaternion* value); + + // For debugging, only + //static U32 mMultCount; +}; + +// checker +inline BOOL LLQuaternion::isFinite() const +{ + return (llfinite(mQ[VX]) && llfinite(mQ[VY]) && llfinite(mQ[VZ]) && llfinite(mQ[VS])); +} + +inline BOOL LLQuaternion::isIdentity() const +{ + return + ( mQ[VX] == 0.f ) && + ( mQ[VY] == 0.f ) && + ( mQ[VZ] == 0.f ) && + ( mQ[VS] == 1.f ); +} + +inline BOOL LLQuaternion::isNotIdentity() const +{ + return + ( mQ[VX] != 0.f ) || + ( mQ[VY] != 0.f ) || + ( mQ[VZ] != 0.f ) || + ( mQ[VS] != 1.f ); +} + + + +inline LLQuaternion::LLQuaternion(void) +{ + mQ[VX] = 0.f; + mQ[VY] = 0.f; + mQ[VZ] = 0.f; + mQ[VS] = 1.f; +} + +inline LLQuaternion::LLQuaternion(F32 x, F32 y, F32 z, F32 w) +{ + mQ[VX] = x; + mQ[VY] = y; + mQ[VZ] = z; + mQ[VS] = w; + + //RN: don't normalize this case as its used mainly for temporaries during calculations + //normQuat(); + /* + F32 mag = sqrtf(mQ[VX]*mQ[VX] + mQ[VY]*mQ[VY] + mQ[VZ]*mQ[VZ] + mQ[VS]*mQ[VS]); + mag -= 1.f; + mag = fabs(mag); + llassert(mag < 10.f*FP_MAG_THRESHOLD); + */ +} + +inline LLQuaternion::LLQuaternion(const F32 *q) +{ + mQ[VX] = q[VX]; + mQ[VY] = q[VY]; + mQ[VZ] = q[VZ]; + mQ[VS] = q[VW]; + + normQuat(); + /* + F32 mag = sqrtf(mQ[VX]*mQ[VX] + mQ[VY]*mQ[VY] + mQ[VZ]*mQ[VZ] + mQ[VS]*mQ[VS]); + mag -= 1.f; + mag = fabs(mag); + llassert(mag < FP_MAG_THRESHOLD); + */ +} + + +inline void LLQuaternion::loadIdentity() +{ + mQ[VX] = 0.0f; + mQ[VY] = 0.0f; + mQ[VZ] = 0.0f; + mQ[VW] = 1.0f; +} + + +inline const LLQuaternion& LLQuaternion::setQuatInit(F32 x, F32 y, F32 z, F32 w) +{ + mQ[VX] = x; + mQ[VY] = y; + mQ[VZ] = z; + mQ[VS] = w; + normQuat(); + return (*this); +} + +inline const LLQuaternion& LLQuaternion::setQuat(const LLQuaternion &quat) +{ + mQ[VX] = quat.mQ[VX]; + mQ[VY] = quat.mQ[VY]; + mQ[VZ] = quat.mQ[VZ]; + mQ[VW] = quat.mQ[VW]; + normQuat(); + return (*this); +} + +inline const LLQuaternion& LLQuaternion::setQuat(const F32 *q) +{ + mQ[VX] = q[VX]; + mQ[VY] = q[VY]; + mQ[VZ] = q[VZ]; + mQ[VS] = q[VW]; + normQuat(); + return (*this); +} + +// There may be a cheaper way that avoids the sqrt. +// Does sin_a = VX*VX + VY*VY + VZ*VZ? +// Copied from Matrix and Quaternion FAQ 1.12 +inline void LLQuaternion::getAngleAxis(F32* angle, F32* x, F32* y, F32* z) const +{ + F32 cos_a = mQ[VW]; + if (cos_a > 1.0f) cos_a = 1.0f; + if (cos_a < -1.0f) cos_a = -1.0f; + + F32 sin_a = (F32) sqrt( 1.0f - cos_a * cos_a ); + + if ( fabs( sin_a ) < 0.0005f ) + sin_a = 1.0f; + else + sin_a = 1.f/sin_a; + + *angle = 2.0f * (F32) acos( cos_a ); + *x = mQ[VX] * sin_a; + *y = mQ[VY] * sin_a; + *z = mQ[VZ] * sin_a; +} + +inline const LLQuaternion& LLQuaternion::conjQuat() +{ + mQ[VX] *= -1.f; + mQ[VY] *= -1.f; + mQ[VZ] *= -1.f; + return (*this); +} + +// Transpose +inline const LLQuaternion& LLQuaternion::transQuat() +{ + mQ[VX] = -mQ[VX]; + mQ[VY] = -mQ[VY]; + mQ[VZ] = -mQ[VZ]; + return *this; +} + + +inline LLQuaternion operator+(const LLQuaternion &a, const LLQuaternion &b) +{ + return LLQuaternion( + a.mQ[VX] + b.mQ[VX], + a.mQ[VY] + b.mQ[VY], + a.mQ[VZ] + b.mQ[VZ], + a.mQ[VW] + b.mQ[VW] ); +} + + +inline LLQuaternion operator-(const LLQuaternion &a, const LLQuaternion &b) +{ + return LLQuaternion( + a.mQ[VX] - b.mQ[VX], + a.mQ[VY] - b.mQ[VY], + a.mQ[VZ] - b.mQ[VZ], + a.mQ[VW] - b.mQ[VW] ); +} + + +inline LLQuaternion operator-(const LLQuaternion &a) +{ + return LLQuaternion( + -a.mQ[VX], + -a.mQ[VY], + -a.mQ[VZ], + -a.mQ[VW] ); +} + + +inline LLQuaternion operator*(F32 a, const LLQuaternion &q) +{ + return LLQuaternion( + a * q.mQ[VX], + a * q.mQ[VY], + a * q.mQ[VZ], + a * q.mQ[VW] ); +} + + +inline LLQuaternion operator*(const LLQuaternion &q, F32 a) +{ + return LLQuaternion( + a * q.mQ[VX], + a * q.mQ[VY], + a * q.mQ[VZ], + a * q.mQ[VW] ); +} + +inline LLQuaternion operator~(const LLQuaternion &a) +{ + LLQuaternion q(a); + q.conjQuat(); + return q; +} + +inline bool LLQuaternion::operator==(const LLQuaternion &b) const +{ + return ( (mQ[VX] == b.mQ[VX]) + &&(mQ[VY] == b.mQ[VY]) + &&(mQ[VZ] == b.mQ[VZ]) + &&(mQ[VS] == b.mQ[VS])); +} + +inline bool LLQuaternion::operator!=(const LLQuaternion &b) const +{ + return ( (mQ[VX] != b.mQ[VX]) + ||(mQ[VY] != b.mQ[VY]) + ||(mQ[VZ] != b.mQ[VZ]) + ||(mQ[VS] != b.mQ[VS])); +} + +inline const LLQuaternion& operator*=(LLQuaternion &a, const LLQuaternion &b) +{ +#if 1 + LLQuaternion q( + b.mQ[3] * a.mQ[0] + b.mQ[0] * a.mQ[3] + b.mQ[1] * a.mQ[2] - b.mQ[2] * a.mQ[1], + b.mQ[3] * a.mQ[1] + b.mQ[1] * a.mQ[3] + b.mQ[2] * a.mQ[0] - b.mQ[0] * a.mQ[2], + b.mQ[3] * a.mQ[2] + b.mQ[2] * a.mQ[3] + b.mQ[0] * a.mQ[1] - b.mQ[1] * a.mQ[0], + b.mQ[3] * a.mQ[3] - b.mQ[0] * a.mQ[0] - b.mQ[1] * a.mQ[1] - b.mQ[2] * a.mQ[2] + ); + a = q; +#else + a = a * b; +#endif + return a; +} + +inline F32 LLQuaternion::normQuat() +{ + F32 mag = sqrtf(mQ[VX]*mQ[VX] + mQ[VY]*mQ[VY] + mQ[VZ]*mQ[VZ] + mQ[VS]*mQ[VS]); + + if (mag > FP_MAG_THRESHOLD) + { + F32 oomag = 1.f/mag; + mQ[VX] *= oomag; + mQ[VY] *= oomag; + mQ[VZ] *= oomag; + mQ[VS] *= oomag; + } + else + { + mQ[VX] = 0.f; + mQ[VY] = 0.f; + mQ[VZ] = 0.f; + mQ[VS] = 1.f; + } + + return mag; +} + +LLQuaternion::Order StringToOrder( const char *str ); + +// Some notes about Quaternions + +// What is a Quaternion? +// --------------------- +// A quaternion is a point in 4-dimensional complex space. +// Q = { Qx, Qy, Qz, Qw } +// +// +// Why Quaternions? +// ---------------- +// The set of quaternions that make up the the 4-D unit sphere +// can be mapped to the set of all rotations in 3-D space. Sometimes +// it is easier to describe/manipulate rotations in quaternion space +// than rotation-matrix space. +// +// +// How Quaternions? +// ---------------- +// In order to take advantage of quaternions we need to know how to +// go from rotation-matricies to quaternions and back. We also have +// to agree what variety of rotations we're generating. +// +// Consider the equation... v' = v * R +// +// There are two ways to think about rotations of vectors. +// 1) v' is the same vector in a different reference frame +// 2) v' is a new vector in the same reference frame +// +// bookmark -- which way are we using? +// +// +// Quaternion from Angle-Axis: +// --------------------------- +// Suppose we wanted to represent a rotation of some angle (theta) +// about some axis ({Ax, Ay, Az})... +// +// axis of rotation = {Ax, Ay, Az} +// angle_of_rotation = theta +// +// s = sin(0.5 * theta) +// c = cos(0.5 * theta) +// Q = { s * Ax, s * Ay, s * Az, c } +// +// +// 3x3 Matrix from Quaternion +// -------------------------- +// +// | | +// | 1 - 2 * (y^2 + z^2) 2 * (x * y + z * w) 2 * (y * w - x * z) | +// | | +// M = | 2 * (x * y - z * w) 1 - 2 * (x^2 + z^2) 2 * (y * z + x * w) | +// | | +// | 2 * (x * z + y * w) 2 * (y * z - x * w) 1 - 2 * (x^2 + y^2) | +// | | + +#endif diff --git a/indra/llmath/llrect.cpp b/indra/llmath/llrect.cpp new file mode 100644 index 0000000000..60280536eb --- /dev/null +++ b/indra/llmath/llrect.cpp @@ -0,0 +1,10 @@ +/** + * @file llrect.cpp + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llrect.h" diff --git a/indra/llmath/llrect.h b/indra/llmath/llrect.h new file mode 100644 index 0000000000..92e415155b --- /dev/null +++ b/indra/llmath/llrect.h @@ -0,0 +1,270 @@ +/** + * @file llrect.h + * @brief A rectangle in GL coordinates, with bottom,left = 0,0 + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + + +#ifndef LL_LLRECT_H +#define LL_LLRECT_H + +#include +#include "llmath.h" +#include "llsd.h" + +// Top > Bottom due to GL coords +template class LLRectBase +{ +public: + Type mLeft; + Type mTop; + Type mRight; + Type mBottom; + + // Note: follows GL_QUAD conventions: the top and right edges are not considered part of the rect + Type getWidth() const { return mRight - mLeft; } + Type getHeight() const { return mTop - mBottom; } + Type getCenterX() const { return (mLeft + mRight) / 2; } + Type getCenterY() const { return (mTop + mBottom) / 2; } + + LLRectBase(): mLeft(0), mTop(0), mRight(0), mBottom(0) + {} + + LLRectBase(const LLRectBase &r): + mLeft(r.mLeft), mTop(r.mTop), mRight(r.mRight), mBottom(r.mBottom) + {} + + LLRectBase(Type left, Type top, Type right, Type bottom): + mLeft(left), mTop(top), mRight(right), mBottom(bottom) + {} + + LLRectBase(const LLSD& sd) + { + setValue(sd); + } + + const LLRectBase& operator=(const LLSD& sd) + { + setValue(sd); + return *this; + } + + void setValue(const LLSD& sd) + { + mLeft = sd[0].asInteger(); + mTop = sd[1].asInteger(); + mRight = sd[2].asInteger(); + mBottom = sd[3].asInteger(); + } + + LLSD getValue() const + { + LLSD ret; + ret[0] = mLeft; + ret[1] = mTop; + ret[2] = mRight; + ret[3] = mBottom; + return ret; + } + + // Note: follows GL_QUAD conventions: the top and right edges are not considered part of the rect + BOOL pointInRect(const Type x, const Type y) const + { + return mLeft <= x && x < mRight && + mBottom <= y && y < mTop; + } + + //// Note: follows GL_QUAD conventions: the top and right edges are not considered part of the rect + BOOL localPointInRect(const Type x, const Type y) const + { + return 0 <= x && x < getWidth() && + 0 <= y && y < getHeight(); + } + + void clampPointToRect(Type& x, Type& y) + { + x = llclamp(x, mLeft, mRight); + y = llclamp(y, mBottom, mTop); + } + + void clipPointToRect(const Type start_x, const Type start_y, Type& end_x, Type& end_y) + { + if (!pointInRect(start_x, start_y)) + { + return; + } + Type clip_x = 0; + Type clip_y = 0; + Type delta_x = end_x - start_x; + Type delta_y = end_y - start_y; + if (end_x > mRight) clip_x = end_x - mRight; + if (end_x < mLeft) clip_x = end_x - mLeft; + if (end_y > mTop) clip_y = end_y - mTop; + if (end_y < mBottom) clip_y = end_y - mBottom; + // clip_? and delta_? should have same sign, since starting point is in rect + // so ratios will be positive + F32 ratio_x = ((F32)clip_x / (F32)delta_x); + F32 ratio_y = ((F32)clip_y / (F32)delta_y); + if (ratio_x > ratio_y) + { + // clip along x direction + end_x -= (Type)(clip_x); + end_y -= (Type)(delta_y * ratio_x); + } + else + { + // clip along y direction + end_x -= (Type)(delta_x * ratio_y); + end_y -= (Type)clip_y; + } + } + + // Note: Does NOT follow GL_QUAD conventions: the top and right edges ARE considered part of the rect + // returns TRUE if any part of rect is is inside this LLRect + BOOL rectInRect(const LLRectBase* rect) const + { + return mLeft <= rect->mRight && rect->mLeft <= mRight && + mBottom <= rect->mTop && rect->mBottom <= mTop ; + } + + void set(Type left, Type top, Type right, Type bottom) + { + mLeft = left; + mTop = top; + mRight = right; + mBottom = bottom; + } + + // Note: follows GL_QUAD conventions: the top and right edges are not considered part of the rect + void setOriginAndSize( Type left, Type bottom, Type width, Type height) + { + mLeft = left; + mTop = bottom + height; + mRight = left + width; + mBottom = bottom; + } + + // Note: follows GL_QUAD conventions: the top and right edges are not considered part of the rect + void setLeftTopAndSize( Type left, Type top, Type width, Type height) + { + mLeft = left; + mTop = top; + mRight = left + width; + mBottom = top - height; + } + + void setCenterAndSize(Type x, Type y, Type width, Type height) + { + mLeft = x - width/2; + mTop = y + height/2; + mRight = x + width/2; + mBottom = y - height/2; + } + + + void translate(Type horiz, Type vertical) + { + mLeft += horiz; + mRight += horiz; + mTop += vertical; + mBottom += vertical; + } + + void stretch( Type dx, Type dy) + { + mLeft -= dx; + mRight += dx; + mTop += dy; + mBottom -= dy; + makeValid(); + } + + void stretch( Type delta ) + { + stretch(delta, delta); + + } + + void makeValid() + { + mLeft = llmin(mLeft, mRight); + mBottom = llmin(mBottom, mTop); + } + + friend const LLRectBase& operator|=(LLRectBase &a, const LLRectBase &b) // Return rect including a & b + { + a.mLeft = llmin(a.mLeft, b.mLeft); + a.mRight = llmax(a.mRight, b.mRight); + a.mBottom = llmin(a.mBottom, b.mBottom); + a.mTop = llmax(a.mTop, b.mTop); + return a; + } + + friend LLRectBase operator|(const LLRectBase &a, const LLRectBase &b) // Return rect including a & b + { + LLRectBase result; + result.mLeft = llmin(a.mLeft, b.mLeft); + result.mRight = llmax(a.mRight, b.mRight); + result.mBottom = llmin(a.mBottom, b.mBottom); + result.mTop = llmax(a.mTop, b.mTop); + return result; + } + + friend const LLRectBase& operator&=(LLRectBase &a, const LLRectBase &b) // set a to rect where a intersects b + { + a.mLeft = llmax(a.mLeft, b.mLeft); + a.mRight = llmin(a.mRight, b.mRight); + a.mBottom = llmax(a.mBottom, b.mBottom); + a.mTop = llmin(a.mTop, b.mTop); + if (a.mLeft > a.mRight) + { + a.mLeft = a.mRight; + } + if (a.mBottom > a.mTop) + { + a.mBottom = a.mTop; + } + return a; + } + + friend LLRectBase operator&(const LLRectBase &a, const LLRectBase &b) // Return rect where a intersects b + { + LLRectBase result = a; + result &= b; + return result; + } + + friend std::ostream &operator<<(std::ostream &s, const LLRectBase &rect) + { + s << "{ L " << rect.mLeft << " B " << rect.mBottom + << " W " << rect.getWidth() << " H " << rect.getHeight() << " }"; + return s; + } + + bool operator==(const LLRectBase &b) + { + return ((mLeft == b.mLeft) && + (mTop == b.mTop) && + (mRight == b.mRight) && + (mBottom == b.mBottom)); + } + + bool operator!=(const LLRectBase &b) + { + return ((mLeft != b.mLeft) || + (mTop != b.mTop) || + (mRight != b.mRight) || + (mBottom != b.mBottom)); + } + + static LLRectBase null; +}; + +template LLRectBase LLRectBase::null(0,0,0,0); + +typedef LLRectBase LLRect; +typedef LLRectBase LLRectf; + +#endif diff --git a/indra/llmath/lltreenode.h b/indra/llmath/lltreenode.h new file mode 100644 index 0000000000..dd0c73c00c --- /dev/null +++ b/indra/llmath/lltreenode.h @@ -0,0 +1,161 @@ +/** + * @file lltreenode.h + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTREENODE_H +#define LL_LLTREENODE_H + +#include "stdtypes.h" +#include "xform.h" +#include + +template class LLTreeNode; +template class LLTreeTraveler; +template class LLTreeListener; + +template +class LLTreeState +{ +public: + LLTreeState(LLTreeNode* node) { setNode(node); } + virtual ~LLTreeState() { }; + + virtual bool insert(T* data) = 0; + virtual bool remove(T* data) = 0; + virtual void setNode(LLTreeNode* node); + virtual const LLTreeNode* getNode() const { return mNode; } + virtual LLTreeNode* getNode() { return mNode; } + virtual void accept(LLTreeTraveler* traveler) const = 0; + virtual LLTreeListener* getListener(U32 index) const; +private: + LLTreeNode* mNode; +}; + +template +class LLTreeListener +{ +public: + virtual ~LLTreeListener() { }; + virtual void handleInsertion(const LLTreeNode* node, T* data) = 0; + virtual void handleRemoval(const LLTreeNode* node, T* data) = 0; + virtual void handleDestruction(const LLTreeNode* node) = 0; + virtual void handleStateChange(const LLTreeNode* node) = 0; +}; + +template +class LLTreeNode +{ +public: + LLTreeNode(LLTreeState* state) { setState(state); } + virtual ~LLTreeNode(); + virtual LLTreeState* getState() { return mState; } + virtual const LLTreeState* getState() const { return mState; } + + virtual void setState(LLTreeState* state); + virtual void insert(T* data); + virtual bool remove(T* data); + virtual void notifyRemoval(T* data); + virtual U32 getListenerCount() { return mListeners.size(); } + virtual LLTreeListener* getListener(U32 index) const { return mListeners[index]; } + virtual void addListener(LLTreeListener* listener) { mListeners.push_back(listener); } + virtual void removeListener(U32 index) { mListeners.erase(mListeners.begin()+index); } + +protected: + void destroyListeners() + { + for (U32 i = 0; i < mListeners.size(); i++) + { + mListeners[i]->handleDestruction(this); + } + mListeners.clear(); + } + + LLTreeState* mState; +public: + std::vector*> mListeners; +}; + +template +class LLTreeTraveler +{ +public: + virtual ~LLTreeTraveler() { }; + virtual void traverse(const LLTreeNode* node) = 0; + virtual void visit(const LLTreeState* state) = 0; +}; + +template +LLTreeNode::~LLTreeNode() +{ + destroyListeners(); +}; + +template +void LLTreeNode::insert(T* data) +{ + if (mState->insert(data)) + { + for (U32 i = 0; i < mListeners.size(); i++) + { + mListeners[i]->handleInsertion(this, data); + } + } +}; + +template +bool LLTreeNode::remove(T* data) +{ + if (mState->remove(data)) + { + return true; + } + return false; +}; + +template +void LLTreeNode::notifyRemoval(T* data) +{ + for (U32 i = 0; i < mListeners.size(); i++) + { + mListeners[i]->handleRemoval(this, data); + } +} + +template +void LLTreeNode::setState(LLTreeState* state) +{ + mState = state; + if (state) + { + if (state->getNode() != this) + { + state->setNode(this); + } + + for (U32 i = 0; i < mListeners.size(); i++) + { + mListeners[i]->handleStateChange(this); + } + } +}; + +template +void LLTreeState::setNode(LLTreeNode* node) +{ + mNode = node; + if (node && node->getState() != this) + { + node->setState(this); + } +}; + +template +LLTreeListener* LLTreeState::getListener(U32 index) const +{ + return mNode->getListener(index); +} + +#endif diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp new file mode 100644 index 0000000000..41e01b5193 --- /dev/null +++ b/indra/llmath/llvolume.cpp @@ -0,0 +1,4557 @@ +/** + * @file llvolume.cpp + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llmath.h" + +#include + +#include "llerror.h" + +#include "llvolumemgr.h" +#include "v2math.h" +#include "v3math.h" +#include "v4math.h" +#include "m4math.h" +#include "m3math.h" +#include "lldarray.h" +#include "llvolume.h" +#include "llstl.h" + +#define DEBUG_SILHOUETTE_BINORMALS 0 +#define DEBUG_SILHOUETTE_NORMALS 0 // TomY: Use this to display normals using the silhouette +#define DEBUG_SILHOUETTE_EDGE_MAP 0 // DaveP: Use this to display edge map using the silhouette + +const F32 CUT_MIN = 0.f; +const F32 CUT_MAX = 1.f; +const F32 MIN_CUT_DELTA = 0.02f; + +const F32 HOLLOW_MIN = 0.f; +const F32 HOLLOW_MAX = 0.95f; +const F32 HOLLOW_MAX_SQUARE = 0.7f; + +const F32 TWIST_MIN = -1.f; +const F32 TWIST_MAX = 1.f; + +const F32 RATIO_MIN = 0.f; +const F32 RATIO_MAX = 2.f; // Tom Y: Inverted sense here: 0 = top taper, 2 = bottom taper + +const F32 HOLE_X_MIN= 0.05f; +const F32 HOLE_X_MAX= 1.0f; + +const F32 HOLE_Y_MIN= 0.05f; +const F32 HOLE_Y_MAX= 0.5f; + +const F32 SHEAR_MIN = -0.5f; +const F32 SHEAR_MAX = 0.5f; + +const F32 REV_MIN = 1.f; +const F32 REV_MAX = 4.f; + +const F32 TAPER_MIN = -1.f; +const F32 TAPER_MAX = 1.f; + +const F32 SKEW_MIN = -0.95f; +const F32 SKEW_MAX = 0.95f; + +BOOL check_same_clock_dir( const LLVector3& pt1, const LLVector3& pt2, const LLVector3& pt3, const LLVector3& norm) +{ + LLVector3 test = (pt2-pt1)%(pt3-pt2); + + //answer + if(test * norm < 0) + { + return FALSE; + } + else + { + return TRUE; + } +} + +// intersect test between triangle pt1,pt2,pt3 and line from linept to linept+vect +//returns TRUE if intersecting and moves linept to the point of intersection +BOOL LLTriangleLineSegmentIntersect( const LLVector3& pt1, const LLVector3& pt2, const LLVector3& pt3, LLVector3& linept, const LLVector3& vect) +{ + LLVector3 V1 = pt2-pt1; + LLVector3 V2 = pt3-pt2; + + LLVector3 norm = V1 % V2; + + F32 dotprod = norm * vect; + + if(dotprod < 0) + { + //Find point of intersect to triangle plane. + //find t to intersect point + F32 t = -(norm * (linept-pt1))/dotprod; + + // if ds is neg line started past triangle so can't hit triangle. + if (t > 0) + { + return FALSE; + } + + LLVector3 pt_int = linept + (vect*t); + + if(check_same_clock_dir(pt1, pt2, pt_int, norm)) + { + if(check_same_clock_dir(pt2, pt3, pt_int, norm)) + { + if(check_same_clock_dir(pt3, pt1, pt_int, norm)) + { + // answer in pt_int is insde triangle + linept.setVec(pt_int); + return TRUE; + } + } + } + } + + return FALSE; +} + + +//------------------------------------------------------------------- +// statics +//------------------------------------------------------------------- + + +//---------------------------------------------------- + +LLProfile::Face* LLProfile::addCap(S16 faceID) +{ + Face *face = vector_append(mFaces, 1); + + face->mIndex = 0; + face->mCount = mTotal; + face->mScaleU= 1.0f; + face->mCap = TRUE; + face->mFaceID = faceID; + return face; +} + +LLProfile::Face* LLProfile::addFace(S32 i, S32 count, F32 scaleU, S16 faceID, BOOL flat) +{ + Face *face = vector_append(mFaces, 1); + + face->mIndex = i; + face->mCount = count; + face->mScaleU= scaleU; + + face->mFlat = flat; + face->mCap = FALSE; + face->mFaceID = faceID; + return face; +} + +// What is the bevel parameter used for? - DJS 04/05/02 +// Bevel parameter is currently unused but presumedly would support +// filleted and chamfered corners +void LLProfile::genNGon(S32 sides, F32 offset, F32 bevel, F32 ang_scale, S32 split) +{ + // Generate an n-sided "circular" path. + // 0 is (1,0), and we go counter-clockwise along a circular path from there. + const F32 tableScale[] = { 1, 1, 1, 0.5f, 0.707107f, 0.53f, 0.525f, 0.5f }; + F32 scale = 0.5f; + F32 t, t_step, t_first, t_fraction, ang, ang_step; + LLVector3 pt1,pt2; + + mMaxX = 0.f; + mMinX = 0.f; + + F32 begin = mParams.getBegin(); + F32 end = mParams.getEnd(); + + t_step = 1.0f / sides; + ang_step = 2.0f*F_PI*t_step*ang_scale; + + // Scale to have size "match" scale. Compensates to get object to generally fill bounding box. + + S32 total_sides = llround(sides / ang_scale); // Total number of sides all around + + if (total_sides < 8) + { + scale = tableScale[total_sides]; + } + + t_first = floor(begin * sides) / (F32)sides; + + // pt1 is the first point on the fractional face. + // Starting t and ang values for the first face + t = t_first; + ang = 2.0f*F_PI*(t*ang_scale + offset); + pt1.setVec(cos(ang)*scale,sin(ang)*scale, t); + + // Increment to the next point. + // pt2 is the end point on the fractional face + t += t_step; + ang += ang_step; + pt2.setVec(cos(ang)*scale,sin(ang)*scale,t); + + t_fraction = (begin - t_first)*sides; + + // Only use if it's not almost exactly on an edge. + if (t_fraction < 0.99f) + { + LLVector3 new_pt = lerp(pt1, pt2, t_fraction); + F32 pt_x = new_pt.mV[VX]; + if (pt_x < mMinX) + { + mMinX = pt_x; + } + else if (pt_x > mMaxX) + { + mMaxX = pt_x; + } + mProfile.push_back(new_pt); + } + + // There's lots of potential here for floating point error to generate unneeded extra points - DJS 04/05/02 + while (t < end) + { + // Iterate through all the integer steps of t. + pt1.setVec(cos(ang)*scale,sin(ang)*scale,t); + + F32 pt_x = pt1.mV[VX]; + if (pt_x < mMinX) + { + mMinX = pt_x; + } + else if (pt_x > mMaxX) + { + mMaxX = pt_x; + } + + if (mProfile.size() > 0) { + LLVector3 p = mProfile[mProfile.size()-1]; + for (S32 i = 0; i < split && mProfile.size() > 0; i++) { + mProfile.push_back(p+(pt1-p) * 1.0f/(float)(split+1) * (float)(i+1)); + } + } + mProfile.push_back(pt1); + + t += t_step; + ang += ang_step; + } + + t_fraction = (end - (t - t_step))*sides; + + // pt1 is the first point on the fractional face + // pt2 is the end point on the fractional face + pt2.setVec(cos(ang)*scale,sin(ang)*scale,t); + + // Find the fraction that we need to add to the end point. + t_fraction = (end - (t - t_step))*sides; + if (t_fraction > 0.01f) + { + LLVector3 new_pt = lerp(pt1, pt2, t_fraction); + F32 pt_x = new_pt.mV[VX]; + if (pt_x < mMinX) + { + mMinX = pt_x; + } + else if (pt_x > mMaxX) + { + mMaxX = pt_x; + } + + if (mProfile.size() > 0) { + LLVector3 p = mProfile[mProfile.size()-1]; + for (S32 i = 0; i < split && mProfile.size() > 0; i++) { + mProfile.push_back(p+(new_pt-p) * 1.0f/(float)(split+1) * (float)(i+1)); + } + } + mProfile.push_back(new_pt); + } + + // If we're sliced, the profile is open. + if ((end - begin)*ang_scale < 0.99f) + { + if ((end - begin)*ang_scale > 0.5f) + { + mConcave = TRUE; + } + else + { + mConcave = FALSE; + } + mOpen = TRUE; + if (!isHollow()) + { + // put center point if not hollow. + mProfile.push_back(LLVector3(0,0,0)); + } + } + else + { + // The profile isn't open. + mOpen = FALSE; + mConcave = FALSE; + } + + mTotal = mProfile.size(); +} + +void LLProfile::genNormals() +{ + S32 count = mProfile.size(); + + S32 outer_count; + if (mTotalOut) + { + outer_count = mTotalOut; + } + else + { + outer_count = mTotal / 2; + } + + mEdgeNormals.resize(count * 2); + mEdgeCenters.resize(count * 2); + mNormals.resize(count); + + LLVector2 pt0,pt1; + + BOOL hollow; + hollow = isHollow(); + + S32 i0, i1, i2, i3, i4; + + // Parametrically generate normal + for (i2 = 0; i2 < count; i2++) + { + mNormals[i2].mV[0] = mProfile[i2].mV[0]; + mNormals[i2].mV[1] = mProfile[i2].mV[1]; + if (hollow && (i2 >= outer_count)) + { + mNormals[i2] *= -1.f; + } + if (mNormals[i2].magVec() < 0.001) + { + // Special case for point at center, get adjacent points. + i1 = (i2 - 1) >= 0 ? i2 - 1 : count - 1; + i0 = (i1 - 1) >= 0 ? i1 - 1 : count - 1; + i3 = (i2 + 1) < count ? i2 + 1 : 0; + i4 = (i3 + 1) < count ? i3 + 1 : 0; + + pt0.setVec(mProfile[i1].mV[VX] + mProfile[i1].mV[VX] - mProfile[i0].mV[VX], + mProfile[i1].mV[VY] + mProfile[i1].mV[VY] - mProfile[i0].mV[VY]); + pt1.setVec(mProfile[i3].mV[VX] + mProfile[i3].mV[VX] - mProfile[i4].mV[VX], + mProfile[i3].mV[VY] + mProfile[i3].mV[VY] - mProfile[i4].mV[VY]); + + mNormals[i2] = pt0 + pt1; + mNormals[i2] *= 0.5f; + } + mNormals[i2].normVec(); + } + + S32 num_normal_sets = isConcave() ? 2 : 1; + for (S32 normal_set = 0; normal_set < num_normal_sets; normal_set++) + { + S32 point_num; + for (point_num = 0; point_num < mTotal; point_num++) + { + LLVector3 point_1 = mProfile[point_num]; + point_1.mV[VZ] = 0.f; + + LLVector3 point_2; + + if (isConcave() && normal_set == 0 && point_num == (mTotal - 1) / 2) + { + point_2 = mProfile[mTotal - 1]; + } + else if (isConcave() && normal_set == 1 && point_num == mTotal - 1) + { + point_2 = mProfile[(mTotal - 1) / 2]; + } + else + { + LLVector3 delta_pos; + S32 neighbor_point = (point_num + 1) % mTotal; + while(delta_pos.magVecSquared() < 0.01f * 0.01f) + { + point_2 = mProfile[neighbor_point]; + delta_pos = point_2 - point_1; + neighbor_point = (neighbor_point + 1) % mTotal; + if (neighbor_point == point_num) + { + break; + } + } + } + + point_2.mV[VZ] = 0.f; + LLVector3 face_normal = (point_2 - point_1) % LLVector3::z_axis; + face_normal.normVec(); + mEdgeNormals[normal_set * count + point_num] = face_normal; + mEdgeCenters[normal_set * count + point_num] = lerp(point_1, point_2, 0.5f); + } + } +} + + +// Hollow is percent of the original bounding box, not of this particular +// profile's geometry. Thus, a swept triangle needs lower hollow values than +// a swept square. +LLProfile::Face* LLProfile::addHole(BOOL flat, F32 sides, F32 offset, F32 box_hollow, F32 ang_scale, S32 split) +{ + // Note that addHole will NOT work for non-"circular" profiles, if we ever decide to use them. + + // Total add has number of vertices on outside. + mTotalOut = mTotal; + + // Why is the "bevel" parameter -1? DJS 04/05/02 + genNGon(llfloor(sides),offset,-1, ang_scale, split); + + Face *face = addFace(mTotalOut, mTotal-mTotalOut,0,LL_FACE_INNER_SIDE, flat); + + LLVector3 pt[128]; + + for (S32 i=mTotalOut;i end - 0.01f) + { + llwarns << "LLProfile::generate() assertion failed (begin >= end)" << llendl; + return FALSE; + } + + S32 face_num = 0; + + switch (mParams.getCurveType() & LL_PCODE_PROFILE_MASK) + { + case LL_PCODE_PROFILE_SQUARE: + { + genNGon(4,-0.375, 0, 1, split); + if (path_open) + { + addCap (LL_FACE_PATH_BEGIN); + } + + for (i = llfloor(begin * 4.f); i < llfloor(end * 4.f + .999f); i++) + { + addFace((face_num++) * (split +1), split+2, 1, LL_FACE_OUTER_SIDE_0 << i, TRUE); + } + + for (i = 0; i <(S32) mProfile.size(); i++) + { + // Scale by 4 to generate proper tex coords. + mProfile[i].mV[2] *= 4.f; + } + + if (hollow) + { + switch (mParams.getCurveType() & LL_PCODE_HOLE_MASK) + { + case LL_PCODE_HOLE_TRIANGLE: + // This offset is not correct, but we can't change it now... DK 11/17/04 + addHole(TRUE, 3, -0.375f, hollow, 1.f, split); + break; + case LL_PCODE_HOLE_CIRCLE: + // TODO: Compute actual detail levels for cubes + addHole(FALSE, MIN_DETAIL_FACES * detail, -0.375f, hollow, 1.f); + break; + case LL_PCODE_HOLE_SAME: + case LL_PCODE_HOLE_SQUARE: + default: + addHole(TRUE, 4, -0.375f, hollow, 1.f, split); + break; + } + } + + if (path_open) { + mFaces[0].mCount = mTotal; + } + } + break; + case LL_PCODE_PROFILE_ISOTRI: + case LL_PCODE_PROFILE_RIGHTTRI: + case LL_PCODE_PROFILE_EQUALTRI: + { + genNGon(3,0, 0, 1, split); + for (i = 0; i <(S32) mProfile.size(); i++) + { + // Scale by 3 to generate proper tex coords. + mProfile[i].mV[2] *= 3.f; + } + + if (path_open) + { + addCap(LL_FACE_PATH_BEGIN); + } + + for (i = llfloor(begin * 3.f); i < llfloor(end * 3.f + .999f); i++) + { + addFace((face_num++) * (split +1), split+2, 1, LL_FACE_OUTER_SIDE_0 << i, TRUE); + } + if (hollow) + { + // Swept triangles need smaller hollowness values, + // because the triangle doesn't fill the bounding box. + F32 triangle_hollow = hollow / 2.f; + + switch (mParams.getCurveType() & LL_PCODE_HOLE_MASK) + { + case LL_PCODE_HOLE_CIRCLE: + // TODO: Actually generate level of detail for triangles + addHole(FALSE, MIN_DETAIL_FACES * detail, 0, triangle_hollow, 1.f); + break; + case LL_PCODE_HOLE_SQUARE: + addHole(TRUE, 4, 0, triangle_hollow, 1.f, split); + break; + case LL_PCODE_HOLE_SAME: + case LL_PCODE_HOLE_TRIANGLE: + default: + addHole(TRUE, 3, 0, triangle_hollow, 1.f, split); + break; + } + } + } + break; + case LL_PCODE_PROFILE_CIRCLE: + { + // If this has a square hollow, we should adjust the + // number of faces a bit so that the geometry lines up. + U8 hole_type=0; + F32 circle_detail = MIN_DETAIL_FACES * detail; + if (hollow) + { + hole_type = mParams.getCurveType() & LL_PCODE_HOLE_MASK; + if (hole_type == LL_PCODE_HOLE_SQUARE) + { + // Snap to the next multiple of four sides, + // so that corners line up. + circle_detail = llceil(circle_detail / 4.0f) * 4.0f; + } + } + + //llinfos << "(CIRCLE) detail: " << detail << "; genNGon(" + // << llfloor(circle_detail) << ")" << llendl; + genNGon(llfloor(circle_detail)); + if (path_open) + { + addCap (LL_FACE_PATH_BEGIN); + } + + if (mOpen && !hollow) + { + addFace(0,mTotal-1,0,LL_FACE_OUTER_SIDE_0, FALSE); + } + else + { + addFace(0,mTotal,0,LL_FACE_OUTER_SIDE_0, FALSE); + } + + if (hollow) + { + switch (hole_type) + { + case LL_PCODE_HOLE_SQUARE: + addHole(TRUE, 4, 0, hollow, 1.f, split); + break; + case LL_PCODE_HOLE_TRIANGLE: + addHole(TRUE, 3, 0, hollow, 1.f, split); + break; + case LL_PCODE_HOLE_CIRCLE: + case LL_PCODE_HOLE_SAME: + default: + addHole(FALSE, circle_detail, 0, hollow, 1.f); + break; + } + } + } + break; + case LL_PCODE_PROFILE_CIRCLE_HALF: + { + // If this has a square hollow, we should adjust the + // number of faces a bit so that the geometry lines up. + U8 hole_type=0; + // Number of faces is cut in half because it's only a half-circle. + F32 circle_detail = MIN_DETAIL_FACES * detail * 0.5f; + if (hollow) + { + hole_type = mParams.getCurveType() & LL_PCODE_HOLE_MASK; + if (hole_type == LL_PCODE_HOLE_SQUARE) + { + // Snap to the next multiple of four sides (div 2), + // so that corners line up. + circle_detail = llceil(circle_detail / 2.0f) * 2.0f; + } + } + genNGon(llfloor(circle_detail), 0.5f, 0.f, 0.5f); + if (path_open) + { + addCap(LL_FACE_PATH_BEGIN); + } + if (mOpen && !mParams.getHollow()) + { + addFace(0,mTotal-1,0,LL_FACE_OUTER_SIDE_0, FALSE); + } + else + { + addFace(0,mTotal,0,LL_FACE_OUTER_SIDE_0, FALSE); + } + + if (hollow) + { + switch (hole_type) + { + case LL_PCODE_HOLE_SQUARE: + addHole(TRUE, 2, 0.5f, hollow, 0.5f, split); + break; + case LL_PCODE_HOLE_TRIANGLE: + addHole(TRUE, 3, 0.5f, hollow, 0.5f, split); + break; + case LL_PCODE_HOLE_CIRCLE: + case LL_PCODE_HOLE_SAME: + default: + addHole(FALSE, circle_detail, 0.5f, hollow, 0.5f); + break; + } + } + + // Special case for openness of sphere + if ((mParams.getEnd() - mParams.getBegin()) < 1.f) + { + mOpen = TRUE; + } + else if (!hollow) + { + mOpen = FALSE; + mProfile.push_back(mProfile[0]); + mTotal++; + } + } + break; + default: + llerrs << "Unknown profile: getCurveType()=" << mParams.getCurveType() << llendl; + break; + }; + + if (path_open) + { + addCap(LL_FACE_PATH_END); // bottom + } + + if ( mOpen) // interior edge caps + { + addFace(mTotal-1, 2,0.5,LL_FACE_PROFILE_BEGIN, TRUE); + + if (hollow) + { + addFace(mTotalOut-1, 2,0.5,LL_FACE_PROFILE_END, TRUE); + } + else + { + addFace(mTotal-2, 2,0.5,LL_FACE_PROFILE_END, TRUE); + } + } + + //genNormals(); + + return TRUE; +} + + + +BOOL LLProfileParams::importFile(FILE *fp) +{ + const S32 BUFSIZE = 16384; + char buffer[BUFSIZE]; + char keyword[256]; + char valuestr[256]; + keyword[0] = 0; + valuestr[0] = 0; + F32 tempF32; + U32 tempU32; + + while (!feof(fp)) + { + fgets(buffer, BUFSIZE, fp); + sscanf(buffer, " %s %s", keyword, valuestr); + if (!keyword) + { + continue; + } + if (!strcmp("{", keyword)) + { + continue; + } + if (!strcmp("}",keyword)) + { + break; + } + else if (!strcmp("curve", keyword)) + { + sscanf(valuestr,"%d",&tempU32); + setCurveType((U8) tempU32); + } + else if (!strcmp("begin",keyword)) + { + sscanf(valuestr,"%g",&tempF32); + setBegin(tempF32); + } + else if (!strcmp("end",keyword)) + { + sscanf(valuestr,"%g",&tempF32); + setEnd(tempF32); + } + else if (!strcmp("hollow",keyword)) + { + sscanf(valuestr,"%g",&tempF32); + setHollow(tempF32); + } + else + { + llwarns << "unknown keyword " << keyword << " in profile import" << llendl; + } + } + + return TRUE; +} + + +BOOL LLProfileParams::exportFile(FILE *fp) const +{ + fprintf(fp,"\t\tprofile 0\n"); + fprintf(fp,"\t\t{\n"); + fprintf(fp,"\t\t\tcurve\t%d\n", getCurveType()); + fprintf(fp,"\t\t\tbegin\t%g\n", getBegin()); + fprintf(fp,"\t\t\tend\t%g\n", getEnd()); + fprintf(fp,"\t\t\thollow\t%g\n", getHollow()); + fprintf(fp, "\t\t}\n"); + return TRUE; +} + + +BOOL LLProfileParams::importLegacyStream(std::istream& input_stream) +{ + const S32 BUFSIZE = 16384; + char buffer[BUFSIZE]; + char keyword[256]; + char valuestr[256]; + keyword[0] = 0; + valuestr[0] = 0; + F32 tempF32; + U32 tempU32; + + while (input_stream.good()) + { + input_stream.getline(buffer, BUFSIZE); + sscanf(buffer, " %s %s", keyword, valuestr); + if (!keyword) + { + continue; + } + if (!strcmp("{", keyword)) + { + continue; + } + if (!strcmp("}",keyword)) + { + break; + } + else if (!strcmp("curve", keyword)) + { + sscanf(valuestr,"%d",&tempU32); + setCurveType((U8) tempU32); + } + else if (!strcmp("begin",keyword)) + { + sscanf(valuestr,"%g",&tempF32); + setBegin(tempF32); + } + else if (!strcmp("end",keyword)) + { + sscanf(valuestr,"%g",&tempF32); + setEnd(tempF32); + } + else if (!strcmp("hollow",keyword)) + { + sscanf(valuestr,"%g",&tempF32); + setHollow(tempF32); + } + else + { + llwarns << "unknown keyword " << keyword << " in profile import" << llendl; + } + } + + return TRUE; +} + + +BOOL LLProfileParams::exportLegacyStream(std::ostream& output_stream) const +{ + output_stream <<"\t\tprofile 0\n"; + output_stream <<"\t\t{\n"; + output_stream <<"\t\t\tcurve\t" << (S32) getCurveType() << "\n"; + output_stream <<"\t\t\tbegin\t" << getBegin() << "\n"; + output_stream <<"\t\t\tend\t" << getEnd() << "\n"; + output_stream <<"\t\t\thollow\t" << getHollow() << "\n"; + output_stream << "\t\t}\n"; + return TRUE; +} + +LLSD LLProfileParams::asLLSD() const +{ + LLSD sd; + + sd["curve"] = getCurveType(); + sd["begin"] = getBegin(); + sd["end"] = getEnd(); + sd["hollow"] = getHollow(); + return sd; +} + +bool LLProfileParams::fromLLSD(LLSD& sd) +{ + setCurveType(sd["curve"].asInteger()); + setBegin((F32)sd["begin"].asReal()); + setEnd((F32)sd["end"].asReal()); + setHollow((F32)sd["hollow"].asReal()); + return true; +} + +void LLProfileParams::copyParams(const LLProfileParams ¶ms) +{ + setCurveType(params.getCurveType()); + setBegin(params.getBegin()); + setEnd(params.getEnd()); + setHollow(params.getHollow()); +} + + +LLPath::~LLPath() +{ +} + +void LLPath::genNGon(S32 sides, F32 startOff, F32 end_scale, F32 twist_scale) +{ + // Generates a circular path, starting at (1, 0, 0), counterclockwise along the xz plane. + const F32 tableScale[] = { 1, 1, 1, 0.5f, 0.707107f, 0.53f, 0.525f, 0.5f }; + + F32 revolutions = mParams.getRevolutions(); + F32 skew = mParams.getSkew(); + F32 skew_mag = fabs(skew); + F32 hole_x = mParams.getScaleX() * (1.0f - skew_mag); + F32 hole_y = mParams.getScaleY(); + + // Calculate taper begin/end for x,y (Negative means taper the beginning) + F32 taper_x_begin = 1.0f; + F32 taper_x_end = 1.0f - mParams.getTaperX(); + F32 taper_y_begin = 1.0f; + F32 taper_y_end = 1.0f - mParams.getTaperY(); + + if ( taper_x_end > 1.0f ) + { + // Flip tapering. + taper_x_begin = 2.0f - taper_x_end; + taper_x_end = 1.0f; + } + if ( taper_y_end > 1.0f ) + { + // Flip tapering. + taper_y_begin = 2.0f - taper_y_end; + taper_y_end = 1.0f; + } + + // For spheres, the radius is usually zero. + F32 radius_start = 0.5f; + if (sides < 8) + { + radius_start = tableScale[sides]; + } + + // Scale the radius to take the hole size into account. + radius_start *= 1.0f - hole_y; + + // Now check the radius offset to calculate the start,end radius. (Negative means + // decrease the start radius instead). + F32 radius_end = radius_start; + F32 radius_offset = mParams.getRadiusOffset(); + if (radius_offset < 0.f) + { + radius_start *= 1.f + radius_offset; + } + else + { + radius_end *= 1.f - radius_offset; + } + + // Is the path NOT a closed loop? + mOpen = ( (mParams.getEnd()*end_scale - mParams.getBegin() < 1.0f) || + (skew_mag > 0.001f) || + (fabs(taper_x_end - taper_x_begin) > 0.001f) || + (fabs(taper_y_end - taper_y_begin) > 0.001f) || + (fabs(radius_end - radius_start) > 0.001f) ); + + F32 ang, c, s; + LLQuaternion twist, qang; + PathPt *pt; + LLVector3 path_axis (1.f, 0.f, 0.f); + //LLVector3 twist_axis(0.f, 0.f, 1.f); + F32 twist_begin = mParams.getTwistBegin() * twist_scale; + F32 twist_end = mParams.getTwist() * twist_scale; + + // We run through this once before the main loop, to make sure + // the path begins at the correct cut. + F32 step= 1.0f / sides; + F32 t = mParams.getBegin(); + pt = vector_append(mPath, 1); + ang = 2.0f*F_PI*revolutions * t; + s = sin(ang)*lerp(radius_start, radius_end, t); + c = cos(ang)*lerp(radius_start, radius_end, t); + + + pt->mPos.setVec(0 + lerp(0,mParams.getShear().mV[0],s) + + lerp(-skew ,skew, t) * 0.5f, + c + lerp(0,mParams.getShear().mV[1],s), + s); + pt->mScale.mV[VX] = hole_x * lerp(taper_x_begin, taper_x_end, t); + pt->mScale.mV[VY] = hole_y * lerp(taper_y_begin, taper_y_end, t); + pt->mTexT = t; + + // Twist rotates the path along the x,y plane (I think) - DJS 04/05/02 + twist.setQuat (lerp(twist_begin,twist_end,t) * 2.f * F_PI - F_PI,0,0,1); + // Rotate the point around the circle's center. + qang.setQuat (ang,path_axis); + pt->mRot = twist * qang; + + t+=step; + + // Snap to a quantized parameter, so that cut does not + // affect most sample points. + t = ((S32)(t * sides)) / (F32)sides; + + // Run through the non-cut dependent points. + while (t < mParams.getEnd()) + { + pt = vector_append(mPath, 1); + + ang = 2.0f*F_PI*revolutions * t; + c = cos(ang)*lerp(radius_start, radius_end, t); + s = sin(ang)*lerp(radius_start, radius_end, t); + + pt->mPos.setVec(0 + lerp(0,mParams.getShear().mV[0],s) + + lerp(-skew ,skew, t) * 0.5f, + c + lerp(0,mParams.getShear().mV[1],s), + s); + + pt->mScale.mV[VX] = hole_x * lerp(taper_x_begin, taper_x_end, t); + pt->mScale.mV[VY] = hole_y * lerp(taper_y_begin, taper_y_end, t); + pt->mTexT = t; + + // Twist rotates the path along the x,y plane (I think) - DJS 04/05/02 + twist.setQuat (lerp(twist_begin,twist_end,t) * 2.f * F_PI - F_PI,0,0,1); + // Rotate the point around the circle's center. + qang.setQuat (ang,path_axis); + pt->mRot = twist * qang; + + t+=step; + } + + // Make one final pass for the end cut. + t = mParams.getEnd(); + pt = vector_append(mPath, 1); + ang = 2.0f*F_PI*revolutions * t; + c = cos(ang)*lerp(radius_start, radius_end, t); + s = sin(ang)*lerp(radius_start, radius_end, t); + + pt->mPos.setVec(0 + lerp(0,mParams.getShear().mV[0],s) + + lerp(-skew ,skew, t) * 0.5f, + c + lerp(0,mParams.getShear().mV[1],s), + s); + pt->mScale.mV[VX] = hole_x * lerp(taper_x_begin, taper_x_end, t); + pt->mScale.mV[VY] = hole_y * lerp(taper_y_begin, taper_y_end, t); + pt->mTexT = t; + + // Twist rotates the path along the x,y plane (I think) - DJS 04/05/02 + twist.setQuat (lerp(twist_begin,twist_end,t) * 2.f * F_PI - F_PI,0,0,1); + // Rotate the point around the circle's center. + qang.setQuat (ang,path_axis); + pt->mRot = twist * qang; + + mTotal = mPath.size(); +} + +const LLVector2 LLPathParams::getBeginScale() const +{ + LLVector2 begin_scale(1.f, 1.f); + if (getScaleX() > 1) + { + begin_scale.mV[0] = 2-getScaleX(); + } + if (getScaleY() > 1) + { + begin_scale.mV[1] = 2-getScaleY(); + } + return begin_scale; +} + +const LLVector2 LLPathParams::getEndScale() const +{ + LLVector2 end_scale(1.f, 1.f); + if (getScaleX() < 1) + { + end_scale.mV[0] = getScaleX(); + } + if (getScaleY() < 1) + { + end_scale.mV[1] = getScaleY(); + } + return end_scale; +} + +BOOL LLPath::generate(F32 detail, S32 split) +{ + if (!mDirty) + { + return FALSE; + } + + if (detail < MIN_LOD) + { + llinfos << "Generating path with LOD < MIN! Clamping to 1" << llendl; + detail = MIN_LOD; + } + + mDirty = FALSE; + S32 np = 2; // hardcode for line + + mPath.clear(); + mOpen = TRUE; + + // Is this 0xf0 mask really necessary? DK 03/02/05 + switch (mParams.getCurveType() & 0xf0) + { + default: + case LL_PCODE_PATH_LINE: + { + // Take the begin/end twist into account for detail. + np = llfloor(fabs(mParams.getTwistBegin() - mParams.getTwist()) * 3.5f * (detail-0.5f)) + 2; + if (np < split+2) + { + np = split+2; + } + + mStep = 1.0f / (np-1); + + mPath.resize(np); + + LLVector2 start_scale = mParams.getBeginScale(); + LLVector2 end_scale = mParams.getEndScale(); + + for (S32 i=0;i= 0.99f && + mParams.getScaleX() >= .99f) + { + mOpen = FALSE; + } + + //genNGon(llfloor(MIN_DETAIL_FACES * detail), 4.f, 0.f); + genNGon(llfloor(MIN_DETAIL_FACES * detail)); + + F32 t = 0.f; + F32 tStep = 1.0f / mPath.size(); + + F32 toggle = 0.5f; + for (S32 i=0;i<(S32)mPath.size();i++) + { + mPath[i].mPos.mV[0] = toggle; + if (toggle == 0.5f) + toggle = -0.5f; + else + toggle = 0.5f; + t += tStep; + } + } + + break; + + case LL_PCODE_PATH_TEST: + + np = 5; + mStep = 1.0f / (np-1); + + mPath.resize(np); + + for (S32 i=0;imParams.getCurveType() == LL_PCODE_PATH_LINE && + (mPathp->mParams.getScale().mV[0] != 1.0f || + mPathp->mParams.getScale().mV[1] != 1.0f) && + (mProfilep->mParams.getCurveType() == LL_PCODE_PROFILE_SQUARE || + mProfilep->mParams.getCurveType() == LL_PCODE_PROFILE_ISOTRI || + mProfilep->mParams.getCurveType() == LL_PCODE_PROFILE_EQUALTRI || + mProfilep->mParams.getCurveType() == LL_PCODE_PROFILE_RIGHTTRI)) + { + split = 0; + } + + mLODScaleBias.setVec(0.5f, 0.5f, 0.5f); + + F32 profile_detail = mDetail; + F32 path_detail = mDetail; + + U8 path_type = mPathp->mParams.getCurveType(); + U8 profile_type = mProfilep->mParams.getCurveType(); + + if (path_type == LL_PCODE_PATH_LINE && profile_type == LL_PCODE_PROFILE_CIRCLE) + { //cylinders don't care about Z-Axis + mLODScaleBias.setVec(0.6f, 0.6f, 0.0f); + } + else if (path_type == LL_PCODE_PATH_CIRCLE) + { + mLODScaleBias.setVec(0.6f, 0.6f, 0.6f); + } + + BOOL regenPath = mPathp->generate(path_detail, split); + BOOL regenProf = mProfilep->generate(mPathp->isOpen(),profile_detail, split); + + if (regenPath || regenProf ) + { + mNumMeshPoints -= mMesh.size(); + mMesh.resize(mProfilep->mProfile.size() * mPathp->mPath.size()); + mNumMeshPoints += mMesh.size(); + + S32 s = 0, t=0; + S32 sizeS = mPathp->mPath.size(); + S32 sizeT = mProfilep->mProfile.size(); + S32 line = 0; + + //generate vertex positions + + // Run along the path. + while (s < sizeS) + { + LLVector2 scale = mPathp->mPath[s].mScale; + LLQuaternion rot = mPathp->mPath[s].mRot; + + t = 0; + // Run along the profile. + while (t < sizeT) + { + S32 i = t + line; + Point& pt = mMesh[i]; + + pt.mPos.mV[0] = mProfilep->mProfile[t].mV[0] * scale.mV[0]; + pt.mPos.mV[1] = mProfilep->mProfile[t].mV[1] * scale.mV[1]; + pt.mPos.mV[2] = 0.0f; + pt.mPos = pt.mPos * rot; + pt.mPos += mPathp->mPath[s].mPos; + t++; + } + line += sizeT; + s++; + } + + for (S32 i = 0; i < (S32)mProfilep->mFaces.size(); i++) + { + mFaceMask |= mProfilep->mFaces[i].mFaceID; + } + return TRUE; + } + return FALSE; +} + + +void LLVolume::createVolumeFaces() +{ + S32 i; + + if (mVolumeFaces != NULL) + { + delete[] mVolumeFaces; + mVolumeFaces = NULL; + } + + if (mGenerateSingleFace) + { + mNumVolumeFaces = 0; + } + else + { + S32 num_faces = getNumFaces(); + mNumVolumeFaces = num_faces; + mVolumeFaces = new LLVolumeFace[num_faces]; + // Initialize volume faces with parameter data + for (i = 0; i < num_faces; i++) + { + LLVolumeFace &vf = mVolumeFaces[i]; + LLProfile::Face &face = mProfilep->mFaces[i]; + vf.mVolumep = this; + vf.mBeginS = face.mIndex; + vf.mNumS = face.mCount; + vf.mBeginT = 0; + vf.mNumT= getPath().mPath.size(); + vf.mID = i; + + // Set the type mask bits correctly + if (mProfilep->isHollow()) + { + vf.mTypeMask |= LLVolumeFace::HOLLOW_MASK; + } + if (mProfilep->isOpen()) + { + vf.mTypeMask |= LLVolumeFace::OPEN_MASK; + } + if (face.mCap) + { + vf.mTypeMask |= LLVolumeFace::CAP_MASK; + if (face.mFaceID == LL_FACE_PATH_BEGIN) + { + vf.mTypeMask |= LLVolumeFace::TOP_MASK; + } + else + { + llassert(face.mFaceID == LL_FACE_PATH_END); + vf.mTypeMask |= LLVolumeFace::BOTTOM_MASK; + } + } + else if (face.mFaceID & (LL_FACE_PROFILE_BEGIN | LL_FACE_PROFILE_END)) + { + vf.mTypeMask |= LLVolumeFace::FLAT_MASK | LLVolumeFace::END_MASK; + } + else + { + vf.mTypeMask |= LLVolumeFace::SIDE_MASK; + if (face.mFlat) + { + vf.mTypeMask |= LLVolumeFace::FLAT_MASK; + } + if (face.mFaceID & LL_FACE_INNER_SIDE) + { + vf.mTypeMask |= LLVolumeFace::INNER_MASK; + if (face.mFlat && vf.mNumS > 2) + { //flat inner faces have to copy vert normals + vf.mNumS = vf.mNumS*2; + } + } + else + { + vf.mTypeMask |= LLVolumeFace::OUTER_MASK; + } + } + } + + for (i = 0; i < mNumVolumeFaces; i++) + { + mVolumeFaces[i].create(); + } + } + + mBounds[1] = LLVector3(0,0,0); + mBounds[0] = LLVector3(512,512,512); +} + + +BOOL LLVolume::isCap(S32 face) +{ + return mProfilep->mFaces[face].mCap; +} + +BOOL LLVolume::isFlat(S32 face) +{ + return mProfilep->mFaces[face].mFlat; +} + + +bool LLVolumeParams::operator==(const LLVolumeParams ¶ms) const +{ + return (getPathParams() == params.getPathParams()) && + (getProfileParams() == params.getProfileParams()); +} + +bool LLVolumeParams::operator!=(const LLVolumeParams ¶ms) const +{ + return (getPathParams() != params.getPathParams()) || + (getProfileParams() != params.getProfileParams()); +} + +bool LLVolumeParams::operator<(const LLVolumeParams ¶ms) const +{ + if( getPathParams() != params.getPathParams() ) + { + return getPathParams() < params.getPathParams(); + } + else + { + return getProfileParams() < params.getProfileParams(); + } +} + +void LLVolumeParams::copyParams(const LLVolumeParams ¶ms) +{ + mProfileParams.copyParams(params.mProfileParams); + mPathParams.copyParams(params.mPathParams); +} + +// return true if in range (or nearly so) +static bool limit_range(F32& v, F32 min, F32 max) +{ + F32 min_delta = v - min; + if (min_delta < 0.f) + { + v = min; + if (!is_approx_zero(min_delta)) + return false; + } + F32 max_delta = max - v; + if (max_delta < 0.f) + { + v = max; + if (!is_approx_zero(max_delta)) + return false; + } + return true; +} + +bool LLVolumeParams::setBeginAndEndS(const F32 b, const F32 e) +{ + bool valid = true; + + // First, clamp to valid ranges. + F32 begin = b; + valid &= limit_range(begin, 0.f, 1.f - MIN_CUT_DELTA); + + F32 end = e; + valid &= limit_range(end, MIN_CUT_DELTA, 1.f); + + valid &= limit_range(begin, 0.f, end - MIN_CUT_DELTA); + + // Now set them. + mProfileParams.setBegin(begin); + mProfileParams.setEnd(end); + + return valid; +} + +bool LLVolumeParams::setBeginAndEndT(const F32 b, const F32 e) +{ + bool valid = true; + + // First, clamp to valid ranges. + F32 begin = b; + valid &= limit_range(begin, 0.f, 1.f - MIN_CUT_DELTA); + + F32 end = e; + valid &= limit_range(end, MIN_CUT_DELTA, 1.f); + + valid &= limit_range(begin, 0.f, end - MIN_CUT_DELTA); + + // Now set them. + mPathParams.setBegin(begin); + mPathParams.setEnd(end); + + return valid; +} + +bool LLVolumeParams::setHollow(const F32 h) +{ + // Validate the hollow based on path and profile. + U8 profile = mProfileParams.getCurveType() & LL_PCODE_PROFILE_MASK; + U8 hole_type = mProfileParams.getCurveType() & LL_PCODE_HOLE_MASK; + + F32 max_hollow = HOLLOW_MAX; + + // Only square holes have trouble. + if (LL_PCODE_HOLE_SQUARE == hole_type) + { + switch(profile) + { + case LL_PCODE_PROFILE_CIRCLE: + case LL_PCODE_PROFILE_CIRCLE_HALF: + case LL_PCODE_PROFILE_EQUALTRI: + max_hollow = HOLLOW_MAX_SQUARE; + } + } + + F32 hollow = h; + bool valid = limit_range(hollow, HOLLOW_MIN, max_hollow); + mProfileParams.setHollow(hollow); + + return valid; +} + +bool LLVolumeParams::setTwistBegin(const F32 b) +{ + F32 twist_begin = b; + bool valid = limit_range(twist_begin, TWIST_MIN, TWIST_MAX); + mPathParams.setTwistBegin(twist_begin); + return valid; +} + +bool LLVolumeParams::setTwistEnd(const F32 e) +{ + F32 twist_end = e; + bool valid = limit_range(twist_end, TWIST_MIN, TWIST_MAX); + mPathParams.setTwistEnd(twist_end); + return valid; +} + +bool LLVolumeParams::setRatio(const F32 x, const F32 y) +{ + F32 min_x = RATIO_MIN; + F32 max_x = RATIO_MAX; + F32 min_y = RATIO_MIN; + F32 max_y = RATIO_MAX; + // If this is a circular path (and not a sphere) then 'ratio' is actually hole size. + U8 path_type = mPathParams.getCurveType(); + U8 profile_type = mProfileParams.getCurveType() & LL_PCODE_PROFILE_MASK; + if ( LL_PCODE_PATH_CIRCLE == path_type && + LL_PCODE_PROFILE_CIRCLE_HALF != profile_type) + { + // Holes are more restricted... + min_x = HOLE_X_MIN; + max_x = HOLE_X_MAX; + min_y = HOLE_Y_MIN; + max_y = HOLE_Y_MAX; + } + + F32 ratio_x = x; + bool valid = limit_range(ratio_x, min_x, max_x); + F32 ratio_y = y; + valid &= limit_range(ratio_y, min_y, max_y); + + mPathParams.setScale(ratio_x, ratio_y); + + return valid; +} + +bool LLVolumeParams::setShear(const F32 x, const F32 y) +{ + F32 shear_x = x; + bool valid = limit_range(shear_x, SHEAR_MIN, SHEAR_MAX); + F32 shear_y = y; + valid &= limit_range(shear_y, SHEAR_MIN, SHEAR_MAX); + mPathParams.setShear(shear_x, shear_y); + return valid; +} + +bool LLVolumeParams::setTaperX(const F32 v) +{ + F32 taper = v; + bool valid = limit_range(taper, TAPER_MIN, TAPER_MAX); + mPathParams.setTaperX(taper); + return valid; +} + +bool LLVolumeParams::setTaperY(const F32 v) +{ + F32 taper = v; + bool valid = limit_range(taper, TAPER_MIN, TAPER_MAX); + mPathParams.setTaperY(taper); + return valid; +} + +bool LLVolumeParams::setRevolutions(const F32 r) +{ + F32 revolutions = r; + bool valid = limit_range(revolutions, REV_MIN, REV_MAX); + mPathParams.setRevolutions(revolutions); + return valid; +} + +bool LLVolumeParams::setRadiusOffset(const F32 offset) +{ + bool valid = true; + + // If this is a sphere, just set it to 0 and get out. + U8 path_type = mPathParams.getCurveType(); + U8 profile_type = mProfileParams.getCurveType() & LL_PCODE_PROFILE_MASK; + if ( LL_PCODE_PROFILE_CIRCLE_HALF == profile_type || + LL_PCODE_PATH_CIRCLE != path_type ) + { + mPathParams.setRadiusOffset(0.f); + return true; + } + + // Limit radius offset, based on taper and hole size y. + F32 radius_offset = offset; + F32 taper_y = getTaperY(); + F32 radius_mag = fabs(radius_offset); + F32 hole_y_mag = fabs(getRatioY()); + F32 taper_y_mag = fabs(taper_y); + // Check to see if the taper effects us. + if ( (radius_offset > 0.f && taper_y < 0.f) || + (radius_offset < 0.f && taper_y > 0.f) ) + { + // The taper does not help increase the radius offset range. + taper_y_mag = 0.f; + } + F32 max_radius_mag = 1.f - hole_y_mag * (1.f - taper_y_mag) / (1.f - hole_y_mag); + + // Enforce the maximum magnitude. + F32 delta = max_radius_mag - radius_mag; + if (delta < 0.f) + { + // Check radius offset sign. + if (radius_offset < 0.f) + { + radius_offset = -max_radius_mag; + } + else + { + radius_offset = max_radius_mag; + } + valid = is_approx_zero(delta); + } + + mPathParams.setRadiusOffset(radius_offset); + return valid; +} + +bool LLVolumeParams::setSkew(const F32 skew_value) +{ + bool valid = true; + + // Check the skew value against the revolutions. + F32 skew = llclamp(skew_value, SKEW_MIN, SKEW_MAX); + F32 skew_mag = fabs(skew); + F32 revolutions = getRevolutions(); + F32 scale_x = getRatioX(); + F32 min_skew_mag = 1.0f - 1.0f / (revolutions * scale_x + 1.0f); + // Discontinuity; A revolution of 1 allows skews below 0.5. + if ( fabs(revolutions - 1.0f) < 0.001) + min_skew_mag = 0.0f; + + // Clip skew. + F32 delta = skew_mag - min_skew_mag; + if (delta < 0.f) + { + // Check skew sign. + if (skew < 0.0f) + { + skew = -min_skew_mag; + } + else + { + skew = min_skew_mag; + } + valid = is_approx_zero(delta); + } + + mPathParams.setSkew(skew); + return valid; +} + +bool LLVolumeParams::setType(U8 profile, U8 path) +{ + bool result = true; + // First, check profile and path for validity. + U8 profile_type = profile & LL_PCODE_PROFILE_MASK; + U8 hole_type = (profile & LL_PCODE_HOLE_MASK) >> 4; + U8 path_type = path >> 4; + + if (profile_type > LL_PCODE_PROFILE_MAX) + { + // Bad profile. Make it square. + profile = LL_PCODE_PROFILE_SQUARE; + result = false; + llwarns << "LLVolumeParams::setType changing bad profile type (" << profile_type + << ") to be LL_PCODE_PROFILE_SQUARE" << llendl; + } + else if (hole_type > LL_PCODE_HOLE_MAX) + { + // Bad hole. Make it the same. + profile = profile_type; + result = false; + llwarns << "LLVolumeParams::setType changing bad hole type (" << hole_type + << ") to be LL_PCODE_HOLE_SAME" << llendl; + } + + if (path_type < LL_PCODE_PATH_MIN || + path_type > LL_PCODE_PATH_MAX) + { + // Bad path. Make it linear. + result = false; + llwarns << "LLVolumeParams::setType changing bad path (" << path + << ") to be LL_PCODE_PATH_LINE" << llendl; + path = LL_PCODE_PATH_LINE; + } + + mProfileParams.setCurveType(profile); + mPathParams.setCurveType(path); + return result; +} + +// static +bool LLVolumeParams::validate(U8 prof_curve, F32 prof_begin, F32 prof_end, F32 hollow, + U8 path_curve, F32 path_begin, F32 path_end, + F32 scx, F32 scy, F32 shx, F32 shy, + F32 twistend, F32 twistbegin, F32 radiusoffset, + F32 tx, F32 ty, F32 revolutions, F32 skew) +{ + LLVolumeParams test_params; + if (!test_params.setType (prof_curve, path_curve)) + { + return false; + } + if (!test_params.setBeginAndEndS (prof_begin, prof_end)) + { + return false; + } + if (!test_params.setBeginAndEndT (path_begin, path_end)) + { + return false; + } + if (!test_params.setHollow (hollow)) + { + return false; + } + if (!test_params.setTwistBegin (twistbegin)) + { + return false; + } + if (!test_params.setTwistEnd (twistend)) + { + return false; + } + if (!test_params.setRatio (scx, scy)) + { + return false; + } + if (!test_params.setShear (shx, shy)) + { + return false; + } + if (!test_params.setTaper (tx, ty)) + { + return false; + } + if (!test_params.setRevolutions (revolutions)) + { + return false; + } + if (!test_params.setRadiusOffset (radiusoffset)) + { + return false; + } + if (!test_params.setSkew (skew)) + { + return false; + } + return true; +} + +#define MAX_INDEX 10000 +S32 *LLVolume::getTriangleIndices(U32 &num_indices) const +{ + S32 index[MAX_INDEX]; + S32 count = 0; + S32 *indices = NULL; + + // Let's do this totally diffently, as we don't care about faces... + // Counter-clockwise triangles are forward facing... + + BOOL open = getProfile().isOpen(); + BOOL hollow = getProfile().isHollow(); + BOOL path_open = getPath().isOpen(); + S32 size_s, size_s_out, size_t; + S32 s, t, i; + size_s = getProfile().getTotal(); + size_s_out = getProfile().getTotalOut(); + size_t = getPath().mPath.size(); + + if (open) + { + if (hollow) + { + // Open hollow -- much like the closed solid, except we + // we need to stitch up the gap between s=0 and s=size_s-1 + + if ( (size_t - 1) * (((size_s -1) * 6) + 6) >= MAX_INDEX) + goto noindices; + + for (t = 0; t < size_t - 1; t++) + { + // The outer face, first cut, and inner face + for (s = 0; s < size_s - 1; s++) + { + i = s + t*size_s; + index[count++] = i; // x,y + index[count++] = i + 1; // x+1,y + index[count++] = i + size_s; // x,y+1 + + index[count++] = i + size_s; // x,y+1 + index[count++] = i + 1; // x+1,y + index[count++] = i + size_s + 1; // x+1,y+1 + } + + // The other cut face + index[count++] = s + t*size_s; // x,y + index[count++] = 0 + t*size_s; // x+1,y + index[count++] = s + (t+1)*size_s; // x,y+1 + + index[count++] = s + (t+1)*size_s; // x,y+1 + index[count++] = 0 + t*size_s; // x+1,y + index[count++] = 0 + (t+1)*size_s; // x+1,y+1 + } + + // Do the top and bottom caps, if necessary + if (path_open) + { + // Top cap + S32 pt1 = 0; + S32 pt2 = size_s-1; + S32 i = (size_t - 1)*size_s; + + while (pt2 - pt1 > 1) + { + // Use the profile points instead of the mesh, since you want + // the un-transformed profile distances. + LLVector3 p1 = getProfile().mProfile[pt1]; + LLVector3 p2 = getProfile().mProfile[pt2]; + LLVector3 pa = getProfile().mProfile[pt1+1]; + LLVector3 pb = getProfile().mProfile[pt2-1]; + + p1.mV[VZ] = 0.f; + p2.mV[VZ] = 0.f; + pa.mV[VZ] = 0.f; + pb.mV[VZ] = 0.f; + + // Use area of triangle to determine backfacing + F32 area_1a2, area_1ba, area_21b, area_2ab; + area_1a2 = (p1.mV[0]*pa.mV[1] - pa.mV[0]*p1.mV[1]) + + (pa.mV[0]*p2.mV[1] - p2.mV[0]*pa.mV[1]) + + (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]); + + area_1ba = (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) + + (pb.mV[0]*pa.mV[1] - pa.mV[0]*pb.mV[1]) + + (pa.mV[0]*p1.mV[1] - p1.mV[0]*pa.mV[1]); + + area_21b = (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]) + + (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) + + (pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]); + + area_2ab = (p2.mV[0]*pa.mV[1] - pa.mV[0]*p2.mV[1]) + + (pa.mV[0]*pb.mV[1] - pb.mV[0]*pa.mV[1]) + + (pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]); + + BOOL use_tri1a2 = TRUE; + BOOL tri_1a2 = TRUE; + BOOL tri_21b = TRUE; + + if (area_1a2 < 0) + { + tri_1a2 = FALSE; + } + if (area_2ab < 0) + { + // Can't use, because it contains point b + tri_1a2 = FALSE; + } + if (area_21b < 0) + { + tri_21b = FALSE; + } + if (area_1ba < 0) + { + // Can't use, because it contains point b + tri_21b = FALSE; + } + + if (!tri_1a2) + { + use_tri1a2 = FALSE; + } + else if (!tri_21b) + { + use_tri1a2 = TRUE; + } + else + { + LLVector3 d1 = p1 - pa; + LLVector3 d2 = p2 - pb; + + if (d1.magVecSquared() < d2.magVecSquared()) + { + use_tri1a2 = TRUE; + } + else + { + use_tri1a2 = FALSE; + } + } + + if (use_tri1a2) + { + if (count + 3 >= MAX_INDEX) + goto noindices; + index[count++] = pt1 + i; + index[count++] = pt1 + 1 + i; + index[count++] = pt2 + i; + pt1++; + } + else + { + if (count + 3 >= MAX_INDEX) + goto noindices; + index[count++] = pt1 + i; + index[count++] = pt2 - 1 + i; + index[count++] = pt2 + i; + pt2--; + } + } + + // Bottom cap + pt1 = 0; + pt2 = size_s-1; + while (pt2 - pt1 > 1) + { + // Use the profile points instead of the mesh, since you want + // the un-transformed profile distances. + LLVector3 p1 = getProfile().mProfile[pt1]; + LLVector3 p2 = getProfile().mProfile[pt2]; + LLVector3 pa = getProfile().mProfile[pt1+1]; + LLVector3 pb = getProfile().mProfile[pt2-1]; + + p1.mV[VZ] = 0.f; + p2.mV[VZ] = 0.f; + pa.mV[VZ] = 0.f; + pb.mV[VZ] = 0.f; + + // Use area of triangle to determine backfacing + F32 area_1a2, area_1ba, area_21b, area_2ab; + area_1a2 = (p1.mV[0]*pa.mV[1] - pa.mV[0]*p1.mV[1]) + + (pa.mV[0]*p2.mV[1] - p2.mV[0]*pa.mV[1]) + + (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]); + + area_1ba = (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) + + (pb.mV[0]*pa.mV[1] - pa.mV[0]*pb.mV[1]) + + (pa.mV[0]*p1.mV[1] - p1.mV[0]*pa.mV[1]); + + area_21b = (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]) + + (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) + + (pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]); + + area_2ab = (p2.mV[0]*pa.mV[1] - pa.mV[0]*p2.mV[1]) + + (pa.mV[0]*pb.mV[1] - pb.mV[0]*pa.mV[1]) + + (pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]); + + BOOL use_tri1a2 = TRUE; + BOOL tri_1a2 = TRUE; + BOOL tri_21b = TRUE; + + if (area_1a2 < 0) + { + tri_1a2 = FALSE; + } + if (area_2ab < 0) + { + // Can't use, because it contains point b + tri_1a2 = FALSE; + } + if (area_21b < 0) + { + tri_21b = FALSE; + } + if (area_1ba < 0) + { + // Can't use, because it contains point b + tri_21b = FALSE; + } + + if (!tri_1a2) + { + use_tri1a2 = FALSE; + } + else if (!tri_21b) + { + use_tri1a2 = TRUE; + } + else + { + LLVector3 d1 = p1 - pa; + LLVector3 d2 = p2 - pb; + + if (d1.magVecSquared() < d2.magVecSquared()) + { + use_tri1a2 = TRUE; + } + else + { + use_tri1a2 = FALSE; + } + } + + if (use_tri1a2) + { + if (count + 3 >= MAX_INDEX) + goto noindices; + index[count++] = pt1; + index[count++] = pt2; + index[count++] = pt1 + 1; + pt1++; + } + else + { + if (count + 3 >= MAX_INDEX) + goto noindices; + index[count++] = pt1; + index[count++] = pt2; + index[count++] = pt2 - 1; + pt2--; + } + } + } + } + else + { + // Open solid + + if ( (size_t - 1) * (((size_s -1) * 6) + 6) >= MAX_INDEX) + goto noindices; + + for (t = 0; t < size_t - 1; t++) + { + // Outer face + 1 cut face + for (s = 0; s < size_s - 1; s++) + { + i = s + t*size_s; + + index[count++] = i; // x,y + index[count++] = i + 1; // x+1,y + index[count++] = i + size_s; // x,y+1 + + index[count++] = i + size_s; // x,y+1 + index[count++] = i + 1; // x+1,y + index[count++] = i + size_s + 1; // x+1,y+1 + } + + // The other cut face + index[count++] = (size_s - 1) + (t*size_s); // x,y + index[count++] = 0 + t*size_s; // x+1,y + index[count++] = (size_s - 1) + (t+1)*size_s; // x,y+1 + + index[count++] = (size_s - 1) + (t+1)*size_s; // x,y+1 + index[count++] = 0 + (t*size_s); // x+1,y + index[count++] = 0 + (t+1)*size_s; // x+1,y+1 + } + + // Do the top and bottom caps, if necessary + if (path_open) + { + if ( count + (size_s - 2) * 3 >= MAX_INDEX) + goto noindices; + for (s = 0; s < size_s - 2; s++) + { + index[count++] = s+1; + index[count++] = s; + index[count++] = size_s - 1; + } + + // We've got a top cap + S32 offset = (size_t - 1)*size_s; + if ( count + (size_s - 2) * 3 >= MAX_INDEX) + goto noindices; + for (s = 0; s < size_s - 2; s++) + { + // Inverted ordering from bottom cap. + index[count++] = offset + size_s - 1; + index[count++] = offset + s; + index[count++] = offset + s + 1; + } + } + } + } + else if (hollow) + { + // Closed hollow + // Outer face + + if ( (size_t - 1) * (size_s_out - 1) * 6 >= MAX_INDEX) + goto noindices; + for (t = 0; t < size_t - 1; t++) + { + for (s = 0; s < size_s_out - 1; s++) + { + i = s + t*size_s; + + index[count++] = i; // x,y + index[count++] = i + 1; // x+1,y + index[count++] = i + size_s; // x,y+1 + + index[count++] = i + size_s; // x,y+1 + index[count++] = i + 1; // x+1,y + index[count++] = i + 1 + size_s; // x+1,y+1 + } + } + + // Inner face + // Invert facing from outer face + if ( count + (size_t - 1) * ((size_s - 1) - size_s_out) * 6 >= MAX_INDEX) + goto noindices; + for (t = 0; t < size_t - 1; t++) + { + for (s = size_s_out; s < size_s - 1; s++) + { + i = s + t*size_s; + + index[count++] = i; // x,y + index[count++] = i + 1; // x+1,y + index[count++] = i + size_s; // x,y+1 + + index[count++] = i + size_s; // x,y+1 + index[count++] = i + 1; // x+1,y + index[count++] = i + 1 + size_s; // x+1,y+1 + } + } + + // Do the top and bottom caps, if necessary + if (path_open) + { + // Top cap + S32 pt1 = 0; + S32 pt2 = size_s-1; + S32 i = (size_t - 1)*size_s; + + while (pt2 - pt1 > 1) + { + // Use the profile points instead of the mesh, since you want + // the un-transformed profile distances. + LLVector3 p1 = getProfile().mProfile[pt1]; + LLVector3 p2 = getProfile().mProfile[pt2]; + LLVector3 pa = getProfile().mProfile[pt1+1]; + LLVector3 pb = getProfile().mProfile[pt2-1]; + + p1.mV[VZ] = 0.f; + p2.mV[VZ] = 0.f; + pa.mV[VZ] = 0.f; + pb.mV[VZ] = 0.f; + + // Use area of triangle to determine backfacing + F32 area_1a2, area_1ba, area_21b, area_2ab; + area_1a2 = (p1.mV[0]*pa.mV[1] - pa.mV[0]*p1.mV[1]) + + (pa.mV[0]*p2.mV[1] - p2.mV[0]*pa.mV[1]) + + (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]); + + area_1ba = (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) + + (pb.mV[0]*pa.mV[1] - pa.mV[0]*pb.mV[1]) + + (pa.mV[0]*p1.mV[1] - p1.mV[0]*pa.mV[1]); + + area_21b = (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]) + + (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) + + (pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]); + + area_2ab = (p2.mV[0]*pa.mV[1] - pa.mV[0]*p2.mV[1]) + + (pa.mV[0]*pb.mV[1] - pb.mV[0]*pa.mV[1]) + + (pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]); + + BOOL use_tri1a2 = TRUE; + BOOL tri_1a2 = TRUE; + BOOL tri_21b = TRUE; + + if (area_1a2 < 0) + { + tri_1a2 = FALSE; + } + if (area_2ab < 0) + { + // Can't use, because it contains point b + tri_1a2 = FALSE; + } + if (area_21b < 0) + { + tri_21b = FALSE; + } + if (area_1ba < 0) + { + // Can't use, because it contains point b + tri_21b = FALSE; + } + + if (!tri_1a2) + { + use_tri1a2 = FALSE; + } + else if (!tri_21b) + { + use_tri1a2 = TRUE; + } + else + { + LLVector3 d1 = p1 - pa; + LLVector3 d2 = p2 - pb; + + if (d1.magVecSquared() < d2.magVecSquared()) + { + use_tri1a2 = TRUE; + } + else + { + use_tri1a2 = FALSE; + } + } + + if (use_tri1a2) + { + if (count + 3 >= MAX_INDEX) + goto noindices; + index[count++] = pt1 + i; + index[count++] = pt1 + 1 + i; + index[count++] = pt2 + i; + pt1++; + } + else + { + if (count + 3 >= MAX_INDEX) + goto noindices; + index[count++] = pt1 + i; + index[count++] = pt2 - 1 + i; + index[count++] = pt2 + i; + pt2--; + } + } + + // Bottom cap + pt1 = 0; + pt2 = size_s-1; + while (pt2 - pt1 > 1) + { + // Use the profile points instead of the mesh, since you want + // the un-transformed profile distances. + LLVector3 p1 = getProfile().mProfile[pt1]; + LLVector3 p2 = getProfile().mProfile[pt2]; + LLVector3 pa = getProfile().mProfile[pt1+1]; + LLVector3 pb = getProfile().mProfile[pt2-1]; + + p1.mV[VZ] = 0.f; + p2.mV[VZ] = 0.f; + pa.mV[VZ] = 0.f; + pb.mV[VZ] = 0.f; + + // Use area of triangle to determine backfacing + F32 area_1a2, area_1ba, area_21b, area_2ab; + area_1a2 = (p1.mV[0]*pa.mV[1] - pa.mV[0]*p1.mV[1]) + + (pa.mV[0]*p2.mV[1] - p2.mV[0]*pa.mV[1]) + + (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]); + + area_1ba = (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) + + (pb.mV[0]*pa.mV[1] - pa.mV[0]*pb.mV[1]) + + (pa.mV[0]*p1.mV[1] - p1.mV[0]*pa.mV[1]); + + area_21b = (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]) + + (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) + + (pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]); + + area_2ab = (p2.mV[0]*pa.mV[1] - pa.mV[0]*p2.mV[1]) + + (pa.mV[0]*pb.mV[1] - pb.mV[0]*pa.mV[1]) + + (pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]); + + BOOL use_tri1a2 = TRUE; + BOOL tri_1a2 = TRUE; + BOOL tri_21b = TRUE; + + if (area_1a2 < 0) + { + tri_1a2 = FALSE; + } + if (area_2ab < 0) + { + // Can't use, because it contains point b + tri_1a2 = FALSE; + } + if (area_21b < 0) + { + tri_21b = FALSE; + } + if (area_1ba < 0) + { + // Can't use, because it contains point b + tri_21b = FALSE; + } + + if (!tri_1a2) + { + use_tri1a2 = FALSE; + } + else if (!tri_21b) + { + use_tri1a2 = TRUE; + } + else + { + LLVector3 d1 = p1 - pa; + LLVector3 d2 = p2 - pb; + + if (d1.magVecSquared() < d2.magVecSquared()) + { + use_tri1a2 = TRUE; + } + else + { + use_tri1a2 = FALSE; + } + } + + if (use_tri1a2) + { + if (count + 3 >= MAX_INDEX) + goto noindices; + index[count++] = pt1; + index[count++] = pt2; + index[count++] = pt1 + 1; + pt1++; + } + else + { + if (count + 3 >= MAX_INDEX) + goto noindices; + index[count++] = pt1; + index[count++] = pt2; + index[count++] = pt2 - 1; + pt2--; + } + } + } + } + else + { + // Closed solid. Easy case. + if ( (size_t - 1) * (size_s - 1) * 6 > MAX_INDEX) + goto noindices; + for (t = 0; t < size_t - 1; t++) + { + for (s = 0; s < size_s - 1; s++) + { + // Should wrap properly, but for now... + i = s + t*size_s; + + index[count++] = i; // x,y + index[count++] = i + 1; // x+1,y + index[count++] = i + size_s; // x,y+1 + + index[count++] = i + size_s; // x,y+1 + index[count++] = i + 1; // x+1,y + index[count++] = i + size_s + 1; // x+1,y+1 + } + } + + // Do the top and bottom caps, if necessary + if (path_open) + { + // bottom cap + if ( count + (size_s - 2 - 1) * 3 >= MAX_INDEX) + goto noindices; + for (s = 1; s < size_s - 2; s++) + { + index[count++] = s+1; + index[count++] = s; + index[count++] = 0; + } + + // top cap + S32 offset = (size_t - 1)*size_s; + if ( count + (size_s - 2 - 1) * 3 >= MAX_INDEX) + goto noindices; + for (s = 1; s < size_s - 2; s++) + { + // Inverted ordering from bottom cap. + index[count++] = offset; + index[count++] = offset + s; + index[count++] = offset + s + 1; + } + } + } + +#if 0 + S32 num_vertices = mMesh.size(); + for (i = 0; i < count; i+=3) + { + llinfos << index[i] << ":" << index[i+1] << ":" << index[i+2] << llendl; + llassert(index[i] < num_vertices); + llassert(index[i+1] < num_vertices); + llassert(index[i+2] < num_vertices); + } +#endif + + indices = new S32[count]; +noindices: + if (!indices) + { + llwarns << "Couldn't allocate triangle indices" << llendl; + num_indices = 0; + return NULL; + } + num_indices = count; + memcpy(indices, index, count * sizeof(S32)); + return indices; +} + +//----------------------------------------------------------------------------- +// generateSilhouetteVertices() +//----------------------------------------------------------------------------- +void LLVolume::generateSilhouetteVertices(std::vector &vertices, + std::vector &normals, + std::vector &segments, + const LLVector3& obj_cam_vec, + const LLMatrix4& mat, + const LLMatrix3& norm_mat) +{ + vertices.clear(); + normals.clear(); + segments.clear(); + + //for each face + for (S32 i = 0; i < getNumFaces(); i++) { + LLVolumeFace face = this->getVolumeFace(i); + + if (face.mTypeMask & (LLVolumeFace::CAP_MASK)) { + + } + else { + + //============================================== + //DEBUG draw edge map instead of silhouette edge + //============================================== + +#if DEBUG_SILHOUETTE_EDGE_MAP + + //for each triangle + U32 count = face.mIndices.size(); + for (U32 j = 0; j < count/3; j++) { + //get vertices + S32 v1 = face.mIndices[j*3+0]; + S32 v2 = face.mIndices[j*3+1]; + S32 v3 = face.mIndices[j*3+2]; + + //get current face center + LLVector3 cCenter = (face.mVertices[v1].mPosition + + face.mVertices[v2].mPosition + + face.mVertices[v3].mPosition) / 3.0f; + + //for each edge + for (S32 k = 0; k < 3; k++) { + S32 nIndex = face.mEdge[j*3+k]; + if (nIndex <= -1) { + continue; + } + + if (nIndex >= (S32) count/3) { + continue; + } + //get neighbor vertices + v1 = face.mIndices[nIndex*3+0]; + v2 = face.mIndices[nIndex*3+1]; + v3 = face.mIndices[nIndex*3+2]; + + //get neighbor face center + LLVector3 nCenter = (face.mVertices[v1].mPosition + + face.mVertices[v2].mPosition + + face.mVertices[v3].mPosition) / 3.0f; + + //draw line + vertices.push_back(cCenter); + vertices.push_back(nCenter); + normals.push_back(LLVector3(1,1,1)); + normals.push_back(LLVector3(1,1,1)); + segments.push_back(vertices.size()); + } + } + + continue; + + //============================================== + //DEBUG + //============================================== + + //============================================== + //DEBUG draw normals instead of silhouette edge + //============================================== +#elif DEBUG_SILHOUETTE_NORMALS + + //for each vertex + for (U32 j = 0; j < face.mVertices.size(); j++) { + vertices.push_back(face.mVertices[j].mPosition); + vertices.push_back(face.mVertices[j].mPosition + face.mVertices[j].mNormal*0.1f); + normals.push_back(LLVector3(0,0,1)); + normals.push_back(LLVector3(0,0,1)); + segments.push_back(vertices.size()); +#if DEBUG_SILHOUETTE_BINORMALS + vertices.push_back(face.mVertices[j].mPosition); + vertices.push_back(face.mVertices[j].mPosition + face.mVertices[j].mBinormal*0.1f); + normals.push_back(LLVector3(0,0,1)); + normals.push_back(LLVector3(0,0,1)); + segments.push_back(vertices.size()); +#endif + } + + continue; +#else + //============================================== + //DEBUG + //============================================== + + static const U8 AWAY = 0x01, + TOWARDS = 0x02; + + //for each triangle + std::vector fFacing; + vector_append(fFacing, face.mIndices.size()/3); + for (U32 j = 0; j < face.mIndices.size()/3; j++) + { + //approximate normal + S32 v1 = face.mIndices[j*3+0]; + S32 v2 = face.mIndices[j*3+1]; + S32 v3 = face.mIndices[j*3+2]; + + LLVector3 norm = (face.mVertices[v1].mPosition - face.mVertices[v2].mPosition) % + (face.mVertices[v2].mPosition - face.mVertices[v3].mPosition); + + if (norm.magVecSquared() < 0.00000001f) + { + fFacing[j] = AWAY | TOWARDS; + } + else + { + //get view vector + LLVector3 view = (obj_cam_vec-face.mVertices[v1].mPosition); + bool away = view * norm > 0.0f; + if (away) + { + fFacing[j] = AWAY; + } + else + { + fFacing[j] = TOWARDS; + } + } + } + + //for each triangle + for (U32 j = 0; j < face.mIndices.size()/3; j++) + { + if (fFacing[j] == (AWAY | TOWARDS)) + { //this is a degenerate triangle + //take neighbor facing (degenerate faces get facing of one of their neighbors) + // FIXME IF NEEDED: this does not deal with neighboring degenerate faces + for (S32 k = 0; k < 3; k++) + { + S32 index = face.mEdge[j*3+k]; + if (index != -1) + { + fFacing[j] = fFacing[index]; + break; + } + } + continue; //skip degenerate face + } + + //for each edge + for (S32 k = 0; k < 3; k++) { + S32 index = face.mEdge[j*3+k]; + if (index != -1 && fFacing[index] == (AWAY | TOWARDS)) { + //our neighbor is degenerate, make him face our direction + fFacing[face.mEdge[j*3+k]] = fFacing[j]; + continue; + } + + if (index == -1 || //edge has no neighbor, MUST be a silhouette edge + (fFacing[index] & fFacing[j]) == 0) { //we found a silhouette edge + + S32 v1 = face.mIndices[j*3+k]; + S32 v2 = face.mIndices[j*3+((k+1)%3)]; + + vertices.push_back(face.mVertices[v1].mPosition*mat); + normals.push_back(face.mVertices[v1].mNormal*norm_mat); + + vertices.push_back(face.mVertices[v2].mPosition*mat); + normals.push_back(face.mVertices[v2].mNormal*norm_mat); + segments.push_back(vertices.size()); + } + } + } +#endif + } + } +} + +S32 LLVolume::lineSegmentIntersect(const LLVector3& start, LLVector3& end) const +{ + S32 ret = -1; + + LLVector3 vec = end - start; + + for (U32 i = 0; i < (U32)getNumFaces(); i++) + { + LLVolumeFace face = getVolumeFace(i); + + for (U32 j = 0; j < face.mIndices.size()/3; j++) + { + //approximate normal + S32 v1 = face.mIndices[j*3+0]; + S32 v2 = face.mIndices[j*3+1]; + S32 v3 = face.mIndices[j*3+2]; + + LLVector3 norm = (face.mVertices[v2].mPosition - face.mVertices[v1].mPosition) % + (face.mVertices[v3].mPosition - face.mVertices[v2].mPosition); + + if (norm.magVecSquared() >= 0.00000001f) + { + //get view vector + //LLVector3 view = (start-face.mVertices[v1].mPosition); + //if (view * norm < 0.0f) + { + if (LLTriangleLineSegmentIntersect( face.mVertices[v1].mPosition, + face.mVertices[v2].mPosition, + face.mVertices[v3].mPosition, + end, + vec)) + { + vec = end-start; + ret = (S32) i; + } + } + } + } + } + + return ret; +} + +class LLVertexIndexPair +{ +public: + LLVertexIndexPair(const LLVector3 &vertex, const S32 index); + + LLVector3 mVertex; + S32 mIndex; +}; + +LLVertexIndexPair::LLVertexIndexPair(const LLVector3 &vertex, const S32 index) +{ + mVertex = vertex; + mIndex = index; +} + +const F32 VERTEX_SLOP = 0.00001f; +const F32 VERTEX_SLOP_SQRD = VERTEX_SLOP * VERTEX_SLOP; + +struct lessVertex +{ + bool operator()(const LLVertexIndexPair *a, const LLVertexIndexPair *b) + { + const F32 slop = VERTEX_SLOP; + + if (a->mVertex.mV[0] + slop < b->mVertex.mV[0]) + { + return TRUE; + } + else if (a->mVertex.mV[0] - slop > b->mVertex.mV[0]) + { + return FALSE; + } + + if (a->mVertex.mV[1] + slop < b->mVertex.mV[1]) + { + return TRUE; + } + else if (a->mVertex.mV[1] - slop > b->mVertex.mV[1]) + { + return FALSE; + } + + if (a->mVertex.mV[2] + slop < b->mVertex.mV[2]) + { + return TRUE; + } + else if (a->mVertex.mV[2] - slop > b->mVertex.mV[2]) + { + return FALSE; + } + + return FALSE; + } +}; + +struct lessTriangle +{ + bool operator()(const S32 *a, const S32 *b) + { + if (*a < *b) + { + return TRUE; + } + else if (*a > *b) + { + return FALSE; + } + + if (*(a+1) < *(b+1)) + { + return TRUE; + } + else if (*(a+1) > *(b+1)) + { + return FALSE; + } + + if (*(a+2) < *(b+2)) + { + return TRUE; + } + else if (*(a+2) > *(b+2)) + { + return FALSE; + } + + return FALSE; + } +}; + +BOOL equalTriangle(const S32 *a, const S32 *b) +{ + if ((*a == *b) && (*(a+1) == *(b+1)) && ((*a+2) == (*b+2))) + { + return TRUE; + } + return FALSE; +} + +BOOL LLVolume::cleanupTriangleData( const S32 num_input_vertices, + const std::vector& input_vertices, + const S32 num_input_triangles, + S32 *input_triangles, + S32 &num_output_vertices, + LLVector3 **output_vertices, + S32 &num_output_triangles, + S32 **output_triangles) +{ + // Here's how we do this: + // Create a structure which contains the original vertex index and the + // LLVector3 data. + // "Sort" the data by the vectors + // Create an array the size of the old vertex list, with a mapping of + // old indices to new indices. + // Go through triangles, shift so the lowest index is first + // Sort triangles by first index + // Remove duplicate triangles + // Allocate and pack new triangle data. + + //LLTimer cleanupTimer; + //llinfos << "In vertices: " << num_input_vertices << llendl; + //llinfos << "In triangles: " << num_input_triangles << llendl; + + S32 i; + typedef std::multiset vertex_set_t; + vertex_set_t vertex_list; + + LLVertexIndexPair *pairp = NULL; + for (i = 0; i < num_input_vertices; i++) + { + LLVertexIndexPair *new_pairp = new LLVertexIndexPair(input_vertices[i].mPos, i); + vertex_list.insert(new_pairp); + } + + // Generate the vertex mapping and the list of vertices without + // duplicates. This will crash if there are no vertices. + S32 *vertex_mapping = new S32[num_input_vertices]; + LLVector3 *new_vertices = new LLVector3[num_input_vertices]; + LLVertexIndexPair *prev_pairp = NULL; + + S32 new_num_vertices; + + new_num_vertices = 0; + for (vertex_set_t::iterator iter = vertex_list.begin(), + end = vertex_list.end(); + iter != end; iter++) + { + pairp = *iter; + if (!prev_pairp || ((pairp->mVertex - prev_pairp->mVertex).magVecSquared() >= VERTEX_SLOP_SQRD)) + { + new_vertices[new_num_vertices] = pairp->mVertex; + //llinfos << "Added vertex " << new_num_vertices << " : " << pairp->mVertex << llendl; + new_num_vertices++; + // Update the previous + prev_pairp = pairp; + } + else + { + //llinfos << "Removed duplicate vertex " << pairp->mVertex << llendl; + } + vertex_mapping[pairp->mIndex] = new_num_vertices - 1; + } + + // Iterate through triangles and remove degenerates, re-ordering vertices + // along the way. + S32 *new_triangles = new S32[num_input_triangles * 3]; + S32 new_num_triangles = 0; + + for (i = 0; i < num_input_triangles; i++) + { + //llinfos << "Checking triangle " << input_triangles[i*3] << ":" << input_triangles[i*3+1] << ":" << input_triangles[i*3+2] << llendl; + input_triangles[i*3] = vertex_mapping[input_triangles[i*3]]; + input_triangles[i*3+1] = vertex_mapping[input_triangles[i*3+1]]; + input_triangles[i*3+2] = vertex_mapping[input_triangles[i*3+2]]; + + if ((input_triangles[i*3] == input_triangles[i*3+1]) + || (input_triangles[i*3] == input_triangles[i*3+2]) + || (input_triangles[i*3+1] == input_triangles[i*3+2])) + { + //llinfos << "Removing degenerate triangle " << input_triangles[i*3] << ":" << input_triangles[i*3+1] << ":" << input_triangles[i*3+2] << llendl; + // Degenerate triangle, skip + continue; + } + + if (input_triangles[i*3] < input_triangles[i*3+1]) + { + if (input_triangles[i*3] < input_triangles[i*3+2]) + { + // (0 < 1) && (0 < 2) + new_triangles[new_num_triangles*3] = input_triangles[i*3]; + new_triangles[new_num_triangles*3+1] = input_triangles[i*3+1]; + new_triangles[new_num_triangles*3+2] = input_triangles[i*3+2]; + } + else + { + // (0 < 1) && (2 < 0) + new_triangles[new_num_triangles*3] = input_triangles[i*3+2]; + new_triangles[new_num_triangles*3+1] = input_triangles[i*3]; + new_triangles[new_num_triangles*3+2] = input_triangles[i*3+1]; + } + } + else if (input_triangles[i*3+1] < input_triangles[i*3+2]) + { + // (1 < 0) && (1 < 2) + new_triangles[new_num_triangles*3] = input_triangles[i*3+1]; + new_triangles[new_num_triangles*3+1] = input_triangles[i*3+2]; + new_triangles[new_num_triangles*3+2] = input_triangles[i*3]; + } + else + { + // (1 < 0) && (2 < 1) + new_triangles[new_num_triangles*3] = input_triangles[i*3+2]; + new_triangles[new_num_triangles*3+1] = input_triangles[i*3]; + new_triangles[new_num_triangles*3+2] = input_triangles[i*3+1]; + } + new_num_triangles++; + } + + if (new_num_triangles == 0) + { + llwarns << "Created volume object with 0 faces." << llendl; + return FALSE; + } + + typedef std::set triangle_set_t; + triangle_set_t triangle_list; + + for (i = 0; i < new_num_triangles; i++) + { + triangle_list.insert(&new_triangles[i*3]); + } + + // Sort through the triangle list, and delete duplicates + + S32 *prevp = NULL; + S32 *curp = NULL; + + S32 *sorted_tris = new S32[new_num_triangles*3]; + S32 cur_tri = 0; + for (triangle_set_t::iterator iter = triangle_list.begin(), + end = triangle_list.end(); + iter != end; iter++) + { + curp = *iter; + if (!prevp || !equalTriangle(prevp, curp)) + { + //llinfos << "Added triangle " << *curp << ":" << *(curp+1) << ":" << *(curp+2) << llendl; + sorted_tris[cur_tri*3] = *curp; + sorted_tris[cur_tri*3+1] = *(curp+1); + sorted_tris[cur_tri*3+2] = *(curp+2); + cur_tri++; + prevp = curp; + } + else + { + //llinfos << "Skipped triangle " << *curp << ":" << *(curp+1) << ":" << *(curp+2) << llendl; + } + } + + *output_vertices = new LLVector3[new_num_vertices]; + num_output_vertices = new_num_vertices; + for (i = 0; i < new_num_vertices; i++) + { + (*output_vertices)[i] = new_vertices[i]; + } + + *output_triangles = new S32[cur_tri*3]; + num_output_triangles = cur_tri; + memcpy(*output_triangles, sorted_tris, 3*cur_tri*sizeof(S32)); + + /* + llinfos << "Out vertices: " << num_output_vertices << llendl; + llinfos << "Out triangles: " << num_output_triangles << llendl; + for (i = 0; i < num_output_vertices; i++) + { + llinfos << i << ":" << (*output_vertices)[i] << llendl; + } + for (i = 0; i < num_output_triangles; i++) + { + llinfos << i << ":" << (*output_triangles)[i*3] << ":" << (*output_triangles)[i*3+1] << ":" << (*output_triangles)[i*3+2] << llendl; + } + */ + + //llinfos << "Out vertices: " << num_output_vertices << llendl; + //llinfos << "Out triangles: " << num_output_triangles << llendl; + delete[] vertex_mapping; + vertex_mapping = NULL; + delete[] new_vertices; + new_vertices = NULL; + delete[] new_triangles; + new_triangles = NULL; + delete[] sorted_tris; + sorted_tris = NULL; + triangle_list.clear(); + std::for_each(vertex_list.begin(), vertex_list.end(), DeletePointer()); + vertex_list.clear(); + + return TRUE; +} + + +BOOL LLVolumeParams::importFile(FILE *fp) +{ + //llinfos << "importing volume" << llendl; + const S32 BUFSIZE = 16384; + char buffer[BUFSIZE]; + char keyword[256]; + keyword[0] = 0; + + while (!feof(fp)) + { + fgets(buffer, BUFSIZE, fp); + sscanf(buffer, " %s", keyword); + if (!keyword) + { + continue; + } + if (!strcmp("{", keyword)) + { + continue; + } + if (!strcmp("}",keyword)) + { + break; + } + else if (!strcmp("profile", keyword)) + { + mProfileParams.importFile(fp); + } + else if (!strcmp("path",keyword)) + { + mPathParams.importFile(fp); + } + else + { + llwarns << "unknown keyword " << keyword << " in volume import" << llendl; + } + } + + return TRUE; +} + +BOOL LLVolumeParams::exportFile(FILE *fp) const +{ + fprintf(fp,"\tshape 0\n"); + fprintf(fp,"\t{\n"); + mPathParams.exportFile(fp); + mProfileParams.exportFile(fp); + fprintf(fp, "\t}\n"); + return TRUE; +} + + +BOOL LLVolumeParams::importLegacyStream(std::istream& input_stream) +{ + //llinfos << "importing volume" << llendl; + const S32 BUFSIZE = 16384; + char buffer[BUFSIZE]; + char keyword[256]; + keyword[0] = 0; + + while (input_stream.good()) + { + input_stream.getline(buffer, BUFSIZE); + sscanf(buffer, " %s", keyword); + if (!keyword) + { + continue; + } + if (!strcmp("{", keyword)) + { + continue; + } + if (!strcmp("}",keyword)) + { + break; + } + else if (!strcmp("profile", keyword)) + { + mProfileParams.importLegacyStream(input_stream); + } + else if (!strcmp("path",keyword)) + { + mPathParams.importLegacyStream(input_stream); + } + else + { + llwarns << "unknown keyword " << keyword << " in volume import" << llendl; + } + } + + return TRUE; +} + +BOOL LLVolumeParams::exportLegacyStream(std::ostream& output_stream) const +{ + output_stream <<"\tshape 0\n"; + output_stream <<"\t{\n"; + mPathParams.exportLegacyStream(output_stream); + mProfileParams.exportLegacyStream(output_stream); + output_stream << "\t}\n"; + return TRUE; +} + +LLSD LLVolumeParams::asLLSD() const +{ + LLSD sd = LLSD(); + sd["path"] = mPathParams; + sd["profile"] = mProfileParams; + return sd; +} + +bool LLVolumeParams::fromLLSD(LLSD& sd) +{ + mPathParams.fromLLSD(sd["path"]); + mProfileParams.fromLLSD(sd["profile"]); + return true; +} + +void LLVolumeParams::reduceS(F32 begin, F32 end) +{ + begin = llclampf(begin); + end = llclampf(end); + if (begin > end) + { + F32 temp = begin; + begin = end; + end = temp; + } + F32 a = mProfileParams.getBegin(); + F32 b = mProfileParams.getEnd(); + mProfileParams.setBegin(a + begin * (b - a)); + mProfileParams.setEnd(a + end * (b - a)); +} + +void LLVolumeParams::reduceT(F32 begin, F32 end) +{ + begin = llclampf(begin); + end = llclampf(end); + if (begin > end) + { + F32 temp = begin; + begin = end; + end = temp; + } + F32 a = mPathParams.getBegin(); + F32 b = mPathParams.getEnd(); + mPathParams.setBegin(a + begin * (b - a)); + mPathParams.setEnd(a + end * (b - a)); +} + +BOOL LLVolumeParams::isConvex() const +{ + // The logic for determining convexity is a little convoluted. + + // Do we need to take getTwistBegin into account? DK 08/12/04 + if ( mProfileParams.getHollow() != 0.0f + || mPathParams.getTwist() != mPathParams.getTwistBegin() ) + { + // hollow or twist gaurantees concavity + return FALSE; + } + + F32 profile_length = mProfileParams.getEnd() - mProfileParams.getBegin(); + BOOL concave_profile = (profile_length < 1.0f) && (profile_length > 0.5f); + if (concave_profile) + { + // concave profile + return FALSE; + } + + U8 path_type = mPathParams.getCurveType(); + if ( LL_PCODE_PATH_LINE == path_type ) + { + // straight paths with convex profile + return TRUE; + } + + F32 path_length = mPathParams.getEnd() - mPathParams.getBegin(); + BOOL concave_path = (path_length < 1.0f) && (path_length > 0.5f); + if (concave_path) + { + return FALSE; + } + + // we're left with spheres, toroids and tubes + // only the spheres can be convex + U8 profile_type = mProfileParams.getCurveType() & LL_PCODE_PROFILE_MASK; + if ( LL_PCODE_PROFILE_CIRCLE_HALF == profile_type ) + { + return TRUE; + } + + // it's a toroid or tube + return FALSE; +} + +LLFaceID LLVolume::generateFaceMask() +{ + LLFaceID new_mask = 0x0000; + + switch(mProfilep->mParams.getCurveType() & LL_PCODE_PROFILE_MASK) + { + case LL_PCODE_PROFILE_CIRCLE: + case LL_PCODE_PROFILE_CIRCLE_HALF: + new_mask |= LL_FACE_OUTER_SIDE_0; + break; + case LL_PCODE_PROFILE_SQUARE: + { + for(S32 side = (S32)(mProfilep->mParams.getBegin() * 4.f); side < llceil(mProfilep->mParams.getEnd() * 4.f); side++) + { + new_mask |= LL_FACE_OUTER_SIDE_0 << side; + } + } + break; + case LL_PCODE_PROFILE_ISOTRI: + case LL_PCODE_PROFILE_EQUALTRI: + case LL_PCODE_PROFILE_RIGHTTRI: + { + for(S32 side = (S32)(mProfilep->mParams.getBegin() * 3.f); side < llceil(mProfilep->mParams.getEnd() * 3.f); side++) + { + new_mask |= LL_FACE_OUTER_SIDE_0 << side; + } + } + break; + default: + llerrs << "Unknown profile!" << llendl + break; + } + + // handle hollow objects + if (mProfilep->isHollow()) + { + new_mask |= LL_FACE_INNER_SIDE; + } + + // handle open profile curves + if (mProfilep->isOpen()) + { + new_mask |= LL_FACE_PROFILE_BEGIN | LL_FACE_PROFILE_END; + } + + // handle open path curves + if (mPathp->isOpen()) + { + new_mask |= LL_FACE_PATH_BEGIN | LL_FACE_PATH_END; + } + + return new_mask; +} + +BOOL LLVolume::isFaceMaskValid(LLFaceID face_mask) +{ + LLFaceID test_mask = 0; + for(S32 i = 0; i < getNumFaces(); i++) + { + test_mask |= mProfilep->mFaces[i].mFaceID; + } + + return test_mask == face_mask; +} + +BOOL LLVolume::isConvex() const +{ + // mParams.isConvex() may return FALSE even though the final + // geometry is actually convex due to LOD approximations. + // TODO -- provide LLPath and LLProfile with isConvex() methods + // that correctly determine convexity. -- Leviathan + return mParams.isConvex(); +} + + +std::ostream& operator<<(std::ostream &s, const LLProfileParams &profile_params) +{ + s << "{type=" << (U32) profile_params.mCurveType; + s << ", begin=" << profile_params.mBegin; + s << ", end=" << profile_params.mEnd; + s << ", hollow=" << profile_params.mHollow; + s << "}"; + return s; +} + + +std::ostream& operator<<(std::ostream &s, const LLPathParams &path_params) +{ + s << "{type=" << (U32) path_params.mCurveType; + s << ", begin=" << path_params.mBegin; + s << ", end=" << path_params.mEnd; + s << ", twist=" << path_params.mTwistEnd; + s << ", scale=" << path_params.mScale; + s << ", shear=" << path_params.mShear; + s << ", twist_begin=" << path_params.mTwistBegin; + s << ", radius_offset=" << path_params.mRadiusOffset; + s << ", taper=" << path_params.mTaper; + s << ", revolutions=" << path_params.mRevolutions; + s << ", skew=" << path_params.mSkew; + s << "}"; + return s; +} + + +std::ostream& operator<<(std::ostream &s, const LLVolumeParams &volume_params) +{ + s << "{profileparams = " << volume_params.mProfileParams; + s << ", pathparams = " << volume_params.mPathParams; + s << "}"; + return s; +} + + +std::ostream& operator<<(std::ostream &s, const LLProfile &profile) +{ + s << " {open=" << (U32) profile.mOpen; + s << ", dirty=" << profile.mDirty; + s << ", totalout=" << profile.mTotalOut; + s << ", total=" << profile.mTotal; + s << "}"; + return s; +} + + +std::ostream& operator<<(std::ostream &s, const LLPath &path) +{ + s << "{open=" << (U32) path.mOpen; + s << ", dirty=" << path.mDirty; + s << ", step=" << path.mStep; + s << ", total=" << path.mTotal; + s << "}"; + return s; +} + +std::ostream& operator<<(std::ostream &s, const LLVolume &volume) +{ + s << "{params = " << volume.mParams; + s << ", path = " << *volume.mPathp; + s << ", profile = " << *volume.mProfilep; + s << "}"; + return s; +} + + +std::ostream& operator<<(std::ostream &s, const LLVolume *volumep) +{ + s << "{params = " << volumep->mParams; + s << ", path = " << *(volumep->mPathp); + s << ", profile = " << *(volumep->mProfilep); + s << "}"; + return s; +} + + +LLVolumeFace::LLVolumeFace() +{ + mTypeMask = 0; + mID = 0; + mBeginS = 0; + mBeginT = 0; + mNumS = 0; + mNumT = 0; +} + + +BOOL LLVolumeFace::create() +{ + if (mTypeMask & CAP_MASK) + { + return createCap(); + } + else if ((mTypeMask & END_MASK) || (mTypeMask & SIDE_MASK)) + { + return createSide(); + } + else + { + llerrs << "Unknown/uninitialized face type!" << llendl; + return FALSE; + } +} + +void LerpPlanarVertex(LLVolumeFace::VertexData& v0, + LLVolumeFace::VertexData& v1, + LLVolumeFace::VertexData& v2, + LLVolumeFace::VertexData& vout, + F32 coef01, + F32 coef02) +{ + vout.mPosition = v0.mPosition + ((v1.mPosition-v0.mPosition)*coef01)+((v2.mPosition-v0.mPosition)*coef02); + vout.mTexCoord = v0.mTexCoord + ((v1.mTexCoord-v0.mTexCoord)*coef01)+((v2.mTexCoord-v0.mTexCoord)*coef02); + vout.mNormal = v0.mNormal; + vout.mBinormal = v0.mBinormal; +} + +BOOL LLVolumeFace::createUnCutCubeCap() +{ + const std::vector& mesh = mVolumep->getMesh(); + const std::vector& profile = mVolumep->getProfile().mProfile; + S32 max_s = mVolumep->getProfile().getTotal(); + S32 max_t = mVolumep->getPath().mPath.size(); + + // S32 i; + S32 num_vertices = 0, num_indices = 0; + S32 grid_size = (profile.size()-1)/4; + S32 quad_count = (grid_size * grid_size); + + num_vertices = (grid_size+1)*(grid_size+1); + num_indices = quad_count * 4; + + S32 offset = 0; + if (mTypeMask & TOP_MASK) + offset = (max_t-1) * max_s; + else + offset = mBeginS; + + VertexData corners[4]; + VertexData baseVert; + for(int t = 0; t < 4; t++){ + corners[t].mPosition = mesh[offset + (grid_size*t)].mPos; + corners[t].mTexCoord.mV[0] = profile[grid_size*t].mV[0]+0.5f; + corners[t].mTexCoord.mV[1] = 0.5f - profile[grid_size*t].mV[1]; + } + baseVert.mNormal = + ((corners[1].mPosition-corners[0].mPosition) % + (corners[2].mPosition-corners[1].mPosition)); + baseVert.mNormal.normVec(); + if(!(mTypeMask & TOP_MASK)){ + baseVert.mNormal *= -1.0f; + }else{ + //Swap the UVs on the U(X) axis for top face + LLVector2 swap; + swap = corners[0].mTexCoord; + corners[0].mTexCoord=corners[3].mTexCoord; + corners[3].mTexCoord=swap; + swap = corners[1].mTexCoord; + corners[1].mTexCoord=corners[2].mTexCoord; + corners[2].mTexCoord=swap; + } + baseVert.mBinormal = calc_binormal_from_triangle( + corners[0].mPosition, corners[0].mTexCoord, + corners[1].mPosition, corners[1].mTexCoord, + corners[2].mPosition, corners[2].mTexCoord); + for(int t = 0; t < 4; t++){ + corners[t].mBinormal = baseVert.mBinormal; + corners[t].mNormal = baseVert.mNormal; + } + + S32 vtop = mVertices.size(); +// S32 itop = mIndices.size(); +/// vector_append(mVertices,4); +// vector_append(mIndices,4); +// LLVector3 new_pt = lerp(pt1, pt2, t_fraction); +#if 0 + for(int t=0;t<4;t++){ + VertexData vd; + vd.mPosition = corners[t].mPosition; + vd.mNormal = + ((corners[(t+1)%4].mPosition-corners[t].mPosition)% + (corners[(t+2)%4].mPosition-corners[(t+1)%4].mPosition)); + vd.mNormal.normVec(); + + if (mTypeMask & TOP_MASK) + vd.mNormal *= -1.0f; + vd.mBinormal = vd.mNormal; + vd.mTexCoord = corners[t].mTexCoord; + mVertices.push_back(vd); + } + int idxs[] = {0,1,2,2,3,0}; + if (mTypeMask & TOP_MASK){ + for(int i=0;i<6;i++)mIndices.push_back(vtop+idxs[i]); + }else{ + for(int i=5;i>=0;i--)mIndices.push_back(vtop+idxs[i]); + } +#else + for(int gx = 0;gx=0;i--)mIndices.push_back(vtop+(gy*(grid_size+1))+gx+idxs[i]); + }else{ + for(int i=0;i<6;i++)mIndices.push_back(vtop+(gy*(grid_size+1))+gx+idxs[i]); + } + } + } +#endif + return TRUE; +} + + +BOOL LLVolumeFace::createCap() +{ + if (!(mTypeMask & HOLLOW_MASK) && + !(mTypeMask & OPEN_MASK) && + ((this->mVolumep->getParams().getPathParams().getBegin()==0.0f)&& + (this->mVolumep->getParams().getPathParams().getEnd()==1.0f))&& + (mVolumep->getProfile().mParams.getCurveType()==LL_PCODE_PROFILE_SQUARE && + mVolumep->getPath().mParams.getCurveType()==LL_PCODE_PATH_LINE) + ){ + return createUnCutCubeCap(); + } + + S32 i; + S32 num_vertices = 0, num_indices = 0; + + const std::vector& mesh = mVolumep->getMesh(); + const std::vector& profile = mVolumep->getProfile().mProfile; + + // All types of caps have the same number of vertices and indices + num_vertices = profile.size(); + num_indices = (profile.size() - 2)*3; + vector_append(mVertices,num_vertices); + vector_append(mIndices,num_indices); + + S32 max_s = mVolumep->getProfile().getTotal(); + S32 max_t = mVolumep->getPath().mPath.size(); + + mCenter.clearVec(); + + S32 offset = 0; + if (mTypeMask & TOP_MASK) + { + offset = (max_t-1) * max_s; + } + else + { + offset = mBeginS; + } + + // Figure out the normal, assume all caps are flat faces. + // Cross product to get normals. + + LLVector2 cuv = LLVector2(0,0); + + // Copy the vertices into the array + for (i = 0; i < num_vertices; i++) + { + + if (mTypeMask & TOP_MASK) + { + mVertices[i].mTexCoord.mV[0] = profile[i].mV[0]+0.5f; + mVertices[i].mTexCoord.mV[1] = profile[i].mV[1]+0.5f; + } + else + { + // Mirror for underside. + mVertices[i].mTexCoord.mV[0] = profile[i].mV[0]+0.5f; + mVertices[i].mTexCoord.mV[1] = 0.5f - profile[i].mV[1]; + } + + if(i){ + //Dont include the first point of the profile in the average + cuv += mVertices[i].mTexCoord; + mCenter += mVertices[i].mPosition = mesh[i + offset].mPos; + } + else mVertices[i].mPosition = mesh[i + offset].mPos; + //mVertices[i].mNormal = normal; + } + + mCenter /= (F32)(num_vertices-1); + cuv /= (F32)(num_vertices-1); + + LLVector3 binormal = calc_binormal_from_triangle( + mCenter, cuv, + mVertices[0].mPosition, mVertices[0].mTexCoord, + mVertices[1].mPosition, mVertices[1].mTexCoord); + binormal.normVec(); + + LLVector3 d0; + LLVector3 d1; + LLVector3 normal; + + d0 = mCenter-mVertices[0].mPosition; + d1 = mCenter-mVertices[1].mPosition; + + normal = (mTypeMask & TOP_MASK) ? (d0%d1) : (d1%d0); + normal.normVec(); + + VertexData vd; + vd.mPosition = mCenter; + vd.mNormal = normal; + vd.mBinormal = binormal; + vd.mTexCoord = cuv; + + if (!(mTypeMask & HOLLOW_MASK) && !(mTypeMask & OPEN_MASK)) + { + mVertices.push_back(vd); + num_vertices++; + vector_append(mIndices, 3); + } + + + for (i = 0; i < num_vertices; i++) + { + mVertices[i].mBinormal = binormal; + mVertices[i].mNormal = normal; + } + + if (mTypeMask & HOLLOW_MASK) + { + if (mTypeMask & TOP_MASK) + { + // HOLLOW TOP + // Does it matter if it's open or closed? - djs + + S32 pt1 = 0, pt2 = num_vertices - 1; + i = 0; + while (pt2 - pt1 > 1) + { + // Use the profile points instead of the mesh, since you want + // the un-transformed profile distances. + LLVector3 p1 = profile[pt1]; + LLVector3 p2 = profile[pt2]; + LLVector3 pa = profile[pt1+1]; + LLVector3 pb = profile[pt2-1]; + + p1.mV[VZ] = 0.f; + p2.mV[VZ] = 0.f; + pa.mV[VZ] = 0.f; + pb.mV[VZ] = 0.f; + + // Use area of triangle to determine backfacing + F32 area_1a2, area_1ba, area_21b, area_2ab; + area_1a2 = (p1.mV[0]*pa.mV[1] - pa.mV[0]*p1.mV[1]) + + (pa.mV[0]*p2.mV[1] - p2.mV[0]*pa.mV[1]) + + (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]); + + area_1ba = (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) + + (pb.mV[0]*pa.mV[1] - pa.mV[0]*pb.mV[1]) + + (pa.mV[0]*p1.mV[1] - p1.mV[0]*pa.mV[1]); + + area_21b = (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]) + + (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) + + (pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]); + + area_2ab = (p2.mV[0]*pa.mV[1] - pa.mV[0]*p2.mV[1]) + + (pa.mV[0]*pb.mV[1] - pb.mV[0]*pa.mV[1]) + + (pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]); + + BOOL use_tri1a2 = TRUE; + BOOL tri_1a2 = TRUE; + BOOL tri_21b = TRUE; + + if (area_1a2 < 0) + { + tri_1a2 = FALSE; + } + if (area_2ab < 0) + { + // Can't use, because it contains point b + tri_1a2 = FALSE; + } + if (area_21b < 0) + { + tri_21b = FALSE; + } + if (area_1ba < 0) + { + // Can't use, because it contains point b + tri_21b = FALSE; + } + + if (!tri_1a2) + { + use_tri1a2 = FALSE; + } + else if (!tri_21b) + { + use_tri1a2 = TRUE; + } + else + { + LLVector3 d1 = p1 - pa; + LLVector3 d2 = p2 - pb; + + if (d1.magVecSquared() < d2.magVecSquared()) + { + use_tri1a2 = TRUE; + } + else + { + use_tri1a2 = FALSE; + } + } + + if (use_tri1a2) + { + mIndices[i++] = pt1; + mIndices[i++] = pt1 + 1; + mIndices[i++] = pt2; + pt1++; + } + else + { + mIndices[i++] = pt1; + mIndices[i++] = pt2 - 1; + mIndices[i++] = pt2; + pt2--; + } + } + } + else + { + // HOLLOW BOTTOM + // Does it matter if it's open or closed? - djs + + llassert(mTypeMask & BOTTOM_MASK); + S32 pt1 = 0, pt2 = num_vertices - 1; + + i = 0; + while (pt2 - pt1 > 1) + { + // Use the profile points instead of the mesh, since you want + // the un-transformed profile distances. + LLVector3 p1 = profile[pt1]; + LLVector3 p2 = profile[pt2]; + LLVector3 pa = profile[pt1+1]; + LLVector3 pb = profile[pt2-1]; + + p1.mV[VZ] = 0.f; + p2.mV[VZ] = 0.f; + pa.mV[VZ] = 0.f; + pb.mV[VZ] = 0.f; + + // Use area of triangle to determine backfacing + F32 area_1a2, area_1ba, area_21b, area_2ab; + area_1a2 = (p1.mV[0]*pa.mV[1] - pa.mV[0]*p1.mV[1]) + + (pa.mV[0]*p2.mV[1] - p2.mV[0]*pa.mV[1]) + + (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]); + + area_1ba = (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) + + (pb.mV[0]*pa.mV[1] - pa.mV[0]*pb.mV[1]) + + (pa.mV[0]*p1.mV[1] - p1.mV[0]*pa.mV[1]); + + area_21b = (p2.mV[0]*p1.mV[1] - p1.mV[0]*p2.mV[1]) + + (p1.mV[0]*pb.mV[1] - pb.mV[0]*p1.mV[1]) + + (pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]); + + area_2ab = (p2.mV[0]*pa.mV[1] - pa.mV[0]*p2.mV[1]) + + (pa.mV[0]*pb.mV[1] - pb.mV[0]*pa.mV[1]) + + (pb.mV[0]*p2.mV[1] - p2.mV[0]*pb.mV[1]); + + BOOL use_tri1a2 = TRUE; + BOOL tri_1a2 = TRUE; + BOOL tri_21b = TRUE; + + if (area_1a2 < 0) + { + tri_1a2 = FALSE; + } + if (area_2ab < 0) + { + // Can't use, because it contains point b + tri_1a2 = FALSE; + } + if (area_21b < 0) + { + tri_21b = FALSE; + } + if (area_1ba < 0) + { + // Can't use, because it contains point b + tri_21b = FALSE; + } + + if (!tri_1a2) + { + use_tri1a2 = FALSE; + } + else if (!tri_21b) + { + use_tri1a2 = TRUE; + } + else + { + LLVector3 d1 = p1 - pa; + LLVector3 d2 = p2 - pb; + + if (d1.magVecSquared() < d2.magVecSquared()) + { + use_tri1a2 = TRUE; + } + else + { + use_tri1a2 = FALSE; + } + } + + // Flipped backfacing from top + if (use_tri1a2) + { + mIndices[i++] = pt1; + mIndices[i++] = pt2; + mIndices[i++] = pt1 + 1; + pt1++; + } + else + { + mIndices[i++] = pt1; + mIndices[i++] = pt2; + mIndices[i++] = pt2 - 1; + pt2--; + } + } + } + } + else + { + // Not hollow, generate the triangle fan. + if (mTypeMask & TOP_MASK) + { + if (mTypeMask & OPEN_MASK) + { + // SOLID OPEN TOP + // Generate indices + // This is a tri-fan, so we reuse the same first point for all triangles. + for (i = 0; i < (num_vertices - 2); i++) + { + mIndices[3*i] = num_vertices - 1; + mIndices[3*i+1] = i; + mIndices[3*i+2] = i + 1; + } + } + else + { + // SOLID CLOSED TOP + for (i = 0; i < (num_vertices - 2); i++) + { + //MSMSM fix these caps but only for the un-cut case + mIndices[3*i] = num_vertices - 1; + mIndices[3*i+1] = i; + mIndices[3*i+2] = i + 1; + } + } + } + else + { + if (mTypeMask & OPEN_MASK) + { + // SOLID OPEN BOTTOM + // Generate indices + // This is a tri-fan, so we reuse the same first point for all triangles. + for (i = 0; i < (num_vertices - 2); i++) + { + mIndices[3*i] = num_vertices - 1; + mIndices[3*i+1] = i + 1; + mIndices[3*i+2] = i; + } + } + else + { + // SOLID CLOSED BOTTOM + for (i = 0; i < (num_vertices - 2); i++) + { + //MSMSM fix these caps but only for the un-cut case + mIndices[3*i] = num_vertices - 1; + mIndices[3*i+1] = i + 1; + mIndices[3*i+2] = i; + } + } + } + } + return TRUE; +} + + +BOOL LLVolumeFace::createSide() +{ + BOOL flat = mTypeMask & FLAT_MASK; + S32 num_vertices, num_indices; + + + const std::vector& mesh = mVolumep->getMesh(); + const std::vector& profile = mVolumep->getProfile().mProfile; + const std::vector& path_data = mVolumep->getPath().mPath; + + S32 max_s = mVolumep->getProfile().getTotal(); + + S32 s, t, i; + F32 ss, tt; + + num_vertices = mNumS*mNumT; + num_indices = (mNumS-1)*(mNumT-1)*6; + vector_append(mVertices,num_vertices); + vector_append(mIndices,num_indices); + vector_append(mEdge, num_indices); + + mCenter.clearVec(); + + S32 begin_stex = llfloor( profile[mBeginS].mV[2] ); + S32 num_s = ((mTypeMask & INNER_MASK) && (mTypeMask & FLAT_MASK) && mNumS > 2) ? mNumS/2 : mNumS; + + S32 cur_vertex = 0; + // Copy the vertices into the array + for (t = mBeginT; t < mBeginT + mNumT; t++) + { + tt = path_data[t].mTexT; + for (s = 0; s < num_s; s++) + { + if (mTypeMask & END_MASK) + { + if (s) + { + ss = 1.f; + } + else + { + ss = 0.f; + } + } + else + { + // Get s value for tex-coord. + if (!flat) + { + ss = profile[mBeginS + s].mV[2]; + } + else + { + ss = profile[mBeginS + s].mV[2] - begin_stex; + } + } + + // Check to see if this triangle wraps around the array. + if (mBeginS + s >= max_s) + { + // We're wrapping + i = mBeginS + s + max_s*(t-1); + } + else + { + i = mBeginS + s + max_s*t; + } + + mCenter += mVertices[cur_vertex].mPosition = mesh[i].mPos; + mVertices[cur_vertex].mTexCoord = LLVector2(ss,tt); + + mVertices[cur_vertex].mNormal = LLVector3(0,0,0); + mVertices[cur_vertex].mBinormal = LLVector3(0,0,0); + + cur_vertex++; + + if ((mTypeMask & INNER_MASK) && (mTypeMask & FLAT_MASK) && mNumS > 2 && s > 0) + { + mCenter += mVertices[cur_vertex].mPosition = mesh[i].mPos; + mVertices[cur_vertex].mTexCoord = LLVector2(ss,tt); + + mVertices[cur_vertex].mNormal = LLVector3(0,0,0); + mVertices[cur_vertex].mBinormal = LLVector3(0,0,0); + cur_vertex++; + } + } + + if ((mTypeMask & INNER_MASK) && (mTypeMask & FLAT_MASK) && mNumS > 2) + { + if (mTypeMask & OPEN_MASK) + { + s = num_s-1; + } + else + { + s = 0; + } + + i = mBeginS + s + max_s*t; + ss = profile[mBeginS + s].mV[2] - begin_stex; + mCenter += mVertices[cur_vertex].mPosition = mesh[i].mPos; + mVertices[cur_vertex].mTexCoord = LLVector2(ss,tt); + + mVertices[cur_vertex].mNormal = LLVector3(0,0,0); + mVertices[cur_vertex].mBinormal = LLVector3(0,0,0); + cur_vertex++; + } + } + mCenter /= (F32)num_vertices; + + S32 cur_index = 0; + S32 cur_edge = 0; + BOOL flat_face = mTypeMask & FLAT_MASK; + + // Now we generate the indices. + for (t = 0; t < (mNumT-1); t++) + { + for (s = 0; s < (mNumS-1); s++) + { + mIndices[cur_index++] = s + mNumS*t; //bottom left + mIndices[cur_index++] = s+1 + mNumS*(t+1); //top right + mIndices[cur_index++] = s + mNumS*(t+1); //top left + mIndices[cur_index++] = s + mNumS*t; //bottom left + mIndices[cur_index++] = s+1 + mNumS*t; //bottom right + mIndices[cur_index++] = s+1 + mNumS*(t+1); //top right + + mEdge[cur_edge++] = (mNumS-1)*2*t+s*2+1; //bottom left/top right neighbor face + if (t < mNumT-2) { //top right/top left neighbor face + mEdge[cur_edge++] = (mNumS-1)*2*(t+1)+s*2+1; + } + else if (mNumT <= 3 || mVolumep->getPath().isOpen() == TRUE) { //no neighbor + mEdge[cur_edge++] = -1; + } + else { //wrap on T + mEdge[cur_edge++] = s*2+1; + } + if (s > 0) { //top left/bottom left neighbor face + mEdge[cur_edge++] = (mNumS-1)*2*t+s*2-1; + } + else if (flat_face || mVolumep->getProfile().isOpen() == TRUE) { //no neighbor + mEdge[cur_edge++] = -1; + } + else { //wrap on S + mEdge[cur_edge++] = (mNumS-1)*2*t+(mNumS-2)*2+1; + } + + if (t > 0) { //bottom left/bottom right neighbor face + mEdge[cur_edge++] = (mNumS-1)*2*(t-1)+s*2; + } + else if (mNumT <= 3 || mVolumep->getPath().isOpen() == TRUE) { //no neighbor + mEdge[cur_edge++] = -1; + } + else { //wrap on T + mEdge[cur_edge++] = (mNumS-1)*2*(mNumT-2)+s*2; + } + if (s < mNumS-2) { //bottom right/top right neighbor face + mEdge[cur_edge++] = (mNumS-1)*2*t+(s+1)*2; + } + else if (flat_face || mVolumep->getProfile().isOpen() == TRUE) { //no neighbor + mEdge[cur_edge++] = -1; + } + else { //wrap on S + mEdge[cur_edge++] = (mNumS-1)*2*t; + } + mEdge[cur_edge++] = (mNumS-1)*2*t+s*2; //top right/bottom left neighbor face + } + } + + + //generate normals + for (U32 i = 0; i < mIndices.size()/3; i++) { //for each triangle + const VertexData& v0 = mVertices[mIndices[i*3+0]]; + const VertexData& v1 = mVertices[mIndices[i*3+1]]; + const VertexData& v2 = mVertices[mIndices[i*3+2]]; + + //calculate triangle normal + LLVector3 norm = (v0.mPosition-v1.mPosition)% + (v0.mPosition-v2.mPosition); + + //calculate binormal + LLVector3 binorm = calc_binormal_from_triangle(v0.mPosition, v0.mTexCoord, + v1.mPosition, v1.mTexCoord, + v2.mPosition, v2.mTexCoord); + + for (U32 j = 0; j < 3; j++) { //add triangle normal to vertices + mVertices[mIndices[i*3+j]].mNormal += norm; // * (weight_sum - d[j])/weight_sum; + mVertices[mIndices[i*3+j]].mBinormal += binorm; // * (weight_sum - d[j])/weight_sum; + } + + //even out quad contributions + if (i % 2 == 0) { + mVertices[mIndices[i*3+2]].mNormal += norm; + mVertices[mIndices[i*3+2]].mBinormal += binorm; + } + else { + mVertices[mIndices[i*3+1]].mNormal += norm; + mVertices[mIndices[i*3+1]].mBinormal += binorm; + } + } + + if (mVolumep->getPath().isOpen() == FALSE) { //wrap normals on T + for (S32 i = 0; i < mNumS; i++) { + LLVector3 norm = mVertices[i].mNormal + mVertices[mNumS*(mNumT-1)+i].mNormal; + mVertices[i].mNormal = norm; + mVertices[mNumS*(mNumT-1)+i].mNormal = norm; + } + } + + if (mVolumep->getProfile().isOpen() == FALSE) { //wrap normals on S + for (S32 i = 0; i < mNumT; i++) { + LLVector3 norm = mVertices[mNumS*i].mNormal + mVertices[mNumS*i+mNumS-1].mNormal; + mVertices[mNumS * i].mNormal = norm; + mVertices[mNumS * i+mNumS-1].mNormal = norm; + } + } + + if (mVolumep->getPathType() == LL_PCODE_PATH_CIRCLE && ((mVolumep->getProfileType() & LL_PCODE_PROFILE_MASK) == LL_PCODE_PROFILE_CIRCLE_HALF)) { + if ((mVertices[0].mPosition - mVertices[mNumS*(mNumT-2)].mPosition).magVecSquared() < 0.000001f) + { //all lower S have same normal + for (S32 i = 0; i < mNumT; i++) { + mVertices[mNumS*i].mNormal = LLVector3(1,0,0); + } + } + + if ((mVertices[mNumS-1].mPosition - mVertices[mNumS*(mNumT-2)+mNumS-1].mPosition).magVecSquared() < 0.000001f) + { //all upper T have same normal + for (S32 i = 0; i < mNumT; i++) { + mVertices[mNumS*i+mNumS-1].mNormal = LLVector3(-1,0,0); + } + } + } + + //this loop would LOVE OpenMP + LLVector3 min = mVolumep->mBounds[0] - mVolumep->mBounds[1]; + LLVector3 max = mVolumep->mBounds[0] + mVolumep->mBounds[1]; + + if (min == max && min == LLVector3(512,512,512)) + { + min = max = mVertices[0].mPosition; + } + + for (U32 i = 0; i < mVertices.size(); i++) { + mVertices[i].mNormal.normVec(); + mVertices[i].mBinormal.normVec(); + + for (U32 j = 0; j < 3; j++) { + if (mVertices[i].mPosition.mV[j] > max.mV[j]) { + max.mV[j] = mVertices[i].mPosition.mV[j]; + } + if (mVertices[i].mPosition.mV[j] < min.mV[j]) { + min.mV[j] = mVertices[i].mPosition.mV[j]; + } + } + } + + mVolumep->mBounds[0] = (min + max) * 0.5f; //center + mVolumep->mBounds[1] = (max - min) * 0.5f; //half-height + + return TRUE; +} + +// Static +BOOL LLVolumeFace::updateColors(LLColor4U *old_colors, const S32 num_old, const LLVolumeFace &old_vf, + LLStrider &new_colors, const S32 num_new, const LLVolumeFace &new_vf) +{ + if (new_vf.mTypeMask & CAP_MASK) + { + // These aren't interpolated correctly. Need to fix when shadows go in... + F32 ratio = (F32)num_old / (F32)num_new; + S32 v = 0; + for (v = 0; v < num_new; v++) + { + new_colors[v] = old_colors[(S32)(v*ratio)]; + } + return FALSE; + } + else if (new_vf.mTypeMask & END_MASK) + { + // These aren't interpolated correctly. Need to fix when shadows go in... + F32 ratio = (F32)num_old / (F32)num_new; + S32 v = 0; + for (v = 0; v < num_new; v++) + { + new_colors[v] = old_colors[(S32)(v*ratio)]; + } + return FALSE; + } + else if (new_vf.mTypeMask & SIDE_MASK) + { + S32 s, t; + F32 s_ratio = (F32)old_vf.mNumS / (F32)new_vf.mNumS; + F32 t_ratio = (F32)old_vf.mNumT / (F32)new_vf.mNumT; + + S32 v = 0; + for (t = 0; t < new_vf.mNumT; t++) + { + F32 t_frac = t * t_ratio; + S32 old_t = (S32)t_frac; + S32 t_target = llmin(old_t + 1, (old_vf.mNumT - 1)); + t_frac -= old_t; + for (s = 0; s < new_vf.mNumS; s++) + { + F32 s_frac = s * s_ratio; + S32 old_s = (S32)s_frac; + S32 s_target = llmin(old_s + 1, (old_vf.mNumS - 1)); + s_frac -= old_s; + + // Interpolate along s, then along t. + LLColor4U s_interp0 = old_colors[old_t * old_vf.mNumS + old_s].multAll(1.f - s_frac).addClampMax(old_colors[old_t * old_vf.mNumS + s_target].multAll(s_frac)); + LLColor4U s_interp1 = old_colors[t_target * old_vf.mNumS + old_s].multAll(1.f - s_frac).addClampMax(old_colors[t_target * old_vf.mNumS + s_target].multAll(s_frac)); + new_colors[v] = s_interp0.multAll(1.f - t_frac).addClampMax(s_interp1.multAll(t_frac)); + v++; + } + } + } + else + { + llerrs << "Unknown/uninitialized face type!" << llendl; + return FALSE; + } + return TRUE; +} + + +// Finds binormal based on three vertices with texture coordinates. +// Fills in dummy values if the triangle has degenerate texture coordinates. +LLVector3 calc_binormal_from_triangle( + const LLVector3& pos0, + const LLVector2& tex0, + const LLVector3& pos1, + const LLVector2& tex1, + const LLVector3& pos2, + const LLVector2& tex2) +{ + LLVector3 rx0( pos0.mV[VX], tex0.mV[VX], tex0.mV[VY] ); + LLVector3 rx1( pos1.mV[VX], tex1.mV[VX], tex1.mV[VY] ); + LLVector3 rx2( pos2.mV[VX], tex2.mV[VX], tex2.mV[VY] ); + + LLVector3 ry0( pos0.mV[VY], tex0.mV[VX], tex0.mV[VY] ); + LLVector3 ry1( pos1.mV[VY], tex1.mV[VX], tex1.mV[VY] ); + LLVector3 ry2( pos2.mV[VY], tex2.mV[VX], tex2.mV[VY] ); + + LLVector3 rz0( pos0.mV[VZ], tex0.mV[VX], tex0.mV[VY] ); + LLVector3 rz1( pos1.mV[VZ], tex1.mV[VX], tex1.mV[VY] ); + LLVector3 rz2( pos2.mV[VZ], tex2.mV[VX], tex2.mV[VY] ); + + LLVector3 r0 = (rx0 - rx1) % (rx0 - rx2); + LLVector3 r1 = (ry0 - ry1) % (ry0 - ry2); + LLVector3 r2 = (rz0 - rz1) % (rz0 - rz2); + + if( r0.mV[VX] && r1.mV[VX] && r2.mV[VX] ) + { + LLVector3 binormal( + -r0.mV[VZ] / r0.mV[VX], + -r1.mV[VZ] / r1.mV[VX], + -r2.mV[VZ] / r2.mV[VX]); + //binormal.normVec(); + return binormal; + } + else + { + return LLVector3( 0, 1 , 0 ); + } +} diff --git a/indra/llmath/llvolume.h b/indra/llmath/llvolume.h new file mode 100644 index 0000000000..cf9ae00f21 --- /dev/null +++ b/indra/llmath/llvolume.h @@ -0,0 +1,882 @@ +/** + * @file llvolume.h + * @brief LLVolume base class. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLVOLUME_H +#define LL_LLVOLUME_H + +#include + +class LLProfileParams; +class LLPathParams; +class LLVolumeParams; +class LLProfile; +class LLPath; +class LLVolumeFace; +class LLVolume; + +#include "lldarray.h" +#include "lluuid.h" +#include "v4color.h" +//#include "vmath.h" +#include "v2math.h" +#include "v3math.h" +#include "llquaternion.h" +#include "llstrider.h" +#include "v4coloru.h" +#include "llmemory.h" + +//============================================================================ + +const S32 MIN_DETAIL_FACES = 6; +const S32 MIN_LOD = 0; +const S32 MAX_LOD = 3; + +// These are defined here but are not enforced at this level, +// rather they are here for the convenience of code that uses +// the LLVolume class. +const F32 MIN_VOLUME_PROFILE_WIDTH = 0.05f; +const F32 MIN_VOLUME_PATH_WIDTH = 0.05f; + +const F32 CUT_QUANTA = 0.005f; +const F32 SCALE_QUANTA = 0.01f; +const F32 SHEAR_QUANTA = 0.01f; +const F32 TAPER_QUANTA = 0.01f; +const F32 REV_QUANTA = 0.015f; + + +//============================================================================ + +// useful masks +const LLPCode LL_PCODE_HOLLOW_MASK = 0x80; // has a thickness +const LLPCode LL_PCODE_SEGMENT_MASK = 0x40; // segments (1 angle) +const LLPCode LL_PCODE_PATCH_MASK = 0x20; // segmented segments (2 angles) +const LLPCode LL_PCODE_HEMI_MASK = 0x10; // half-primitives get their own type per PR's dictum +const LLPCode LL_PCODE_BASE_MASK = 0x0F; + + // primitive shapes +const LLPCode LL_PCODE_CUBE = 1; +const LLPCode LL_PCODE_PRISM = 2; +const LLPCode LL_PCODE_TETRAHEDRON = 3; +const LLPCode LL_PCODE_PYRAMID = 4; +const LLPCode LL_PCODE_CYLINDER = 5; +const LLPCode LL_PCODE_CONE = 6; +const LLPCode LL_PCODE_SPHERE = 7; +const LLPCode LL_PCODE_TORUS = 8; +const LLPCode LL_PCODE_VOLUME = 9; + + // surfaces +//const LLPCode LL_PCODE_SURFACE_TRIANGLE = 10; +//const LLPCode LL_PCODE_SURFACE_SQUARE = 11; +//const LLPCode LL_PCODE_SURFACE_DISC = 12; + +const LLPCode LL_PCODE_APP = 14; // App specific pcode (for viewer/sim side only objects) +const LLPCode LL_PCODE_LEGACY = 15; + +// Pcodes for legacy objects +//const LLPCode LL_PCODE_LEGACY_ATOR = 0x10 | LL_PCODE_LEGACY; // ATOR +const LLPCode LL_PCODE_LEGACY_AVATAR = 0x20 | LL_PCODE_LEGACY; // PLAYER +//const LLPCode LL_PCODE_LEGACY_BIRD = 0x30 | LL_PCODE_LEGACY; // BIRD +//const LLPCode LL_PCODE_LEGACY_DEMON = 0x40 | LL_PCODE_LEGACY; // DEMON +const LLPCode LL_PCODE_LEGACY_GRASS = 0x50 | LL_PCODE_LEGACY; // GRASS +const LLPCode LL_PCODE_TREE_NEW = 0x60 | LL_PCODE_LEGACY; // new trees +//const LLPCode LL_PCODE_LEGACY_ORACLE = 0x70 | LL_PCODE_LEGACY; // ORACLE +const LLPCode LL_PCODE_LEGACY_PART_SYS = 0x80 | LL_PCODE_LEGACY; // PART_SYS +const LLPCode LL_PCODE_LEGACY_ROCK = 0x90 | LL_PCODE_LEGACY; // ROCK +//const LLPCode LL_PCODE_LEGACY_SHOT = 0xA0 | LL_PCODE_LEGACY; // BASIC_SHOT +//const LLPCode LL_PCODE_LEGACY_SHOT_BIG = 0xB0 | LL_PCODE_LEGACY; +//const LLPCode LL_PCODE_LEGACY_SMOKE = 0xC0 | LL_PCODE_LEGACY; // SMOKE +//const LLPCode LL_PCODE_LEGACY_SPARK = 0xD0 | LL_PCODE_LEGACY;// SPARK +const LLPCode LL_PCODE_LEGACY_TEXT_BUBBLE = 0xE0 | LL_PCODE_LEGACY; // TEXTBUBBLE +const LLPCode LL_PCODE_LEGACY_TREE = 0xF0 | LL_PCODE_LEGACY; // TREE + + // hemis +const LLPCode LL_PCODE_CYLINDER_HEMI = LL_PCODE_CYLINDER | LL_PCODE_HEMI_MASK; +const LLPCode LL_PCODE_CONE_HEMI = LL_PCODE_CONE | LL_PCODE_HEMI_MASK; +const LLPCode LL_PCODE_SPHERE_HEMI = LL_PCODE_SPHERE | LL_PCODE_HEMI_MASK; +const LLPCode LL_PCODE_TORUS_HEMI = LL_PCODE_TORUS | LL_PCODE_HEMI_MASK; + + +// Volumes consist of a profile at the base that is swept around +// a path to make a volume. +// The profile code +const U8 LL_PCODE_PROFILE_MASK = 0x0f; +const U8 LL_PCODE_PROFILE_MIN = 0x00; +const U8 LL_PCODE_PROFILE_CIRCLE = 0x00; +const U8 LL_PCODE_PROFILE_SQUARE = 0x01; +const U8 LL_PCODE_PROFILE_ISOTRI = 0x02; +const U8 LL_PCODE_PROFILE_EQUALTRI = 0x03; +const U8 LL_PCODE_PROFILE_RIGHTTRI = 0x04; +const U8 LL_PCODE_PROFILE_CIRCLE_HALF = 0x05; +const U8 LL_PCODE_PROFILE_MAX = 0x05; + +// Stored in the profile byte +const U8 LL_PCODE_HOLE_MASK = 0xf0; +const U8 LL_PCODE_HOLE_MIN = 0x00; +const U8 LL_PCODE_HOLE_SAME = 0x00; // same as outside profile +const U8 LL_PCODE_HOLE_CIRCLE = 0x10; +const U8 LL_PCODE_HOLE_SQUARE = 0x20; +const U8 LL_PCODE_HOLE_TRIANGLE = 0x30; +const U8 LL_PCODE_HOLE_MAX = 0x03; // min/max needs to be >> 4 of real min/max + +const U8 LL_PCODE_PATH_IGNORE = 0x00; +const U8 LL_PCODE_PATH_MIN = 0x01; // min/max needs to be >> 4 of real min/max +const U8 LL_PCODE_PATH_LINE = 0x10; +const U8 LL_PCODE_PATH_CIRCLE = 0x20; +const U8 LL_PCODE_PATH_CIRCLE2 = 0x30; +const U8 LL_PCODE_PATH_TEST = 0x40; +const U8 LL_PCODE_PATH_FLEXIBLE = 0x80; +const U8 LL_PCODE_PATH_MAX = 0x08; + +//============================================================================ + +// face identifiers +typedef U16 LLFaceID; + +const LLFaceID LL_FACE_PATH_BEGIN = 0x1 << 0; +const LLFaceID LL_FACE_PATH_END = 0x1 << 1; +const LLFaceID LL_FACE_INNER_SIDE = 0x1 << 2; +const LLFaceID LL_FACE_PROFILE_BEGIN = 0x1 << 3; +const LLFaceID LL_FACE_PROFILE_END = 0x1 << 4; +const LLFaceID LL_FACE_OUTER_SIDE_0 = 0x1 << 5; +const LLFaceID LL_FACE_OUTER_SIDE_1 = 0x1 << 6; +const LLFaceID LL_FACE_OUTER_SIDE_2 = 0x1 << 7; +const LLFaceID LL_FACE_OUTER_SIDE_3 = 0x1 << 8; + +//============================================================================ + +class LLProfileParams +{ +public: + LLProfileParams() + { + mBegin = 0; + mEnd = 1; + mHollow = 0; + mCurveType = LL_PCODE_PROFILE_SQUARE; + } + + LLProfileParams(U8 curve, F32 begin, F32 end, F32 hollow) + : mCurveType(curve), mBegin(begin), mEnd(end), mHollow(hollow) + { + } + + LLProfileParams(U8 curve, U8 begin, U8 end, U8 hollow) + { + mCurveType = curve; + F32 temp_f32 = begin * CUT_QUANTA; + if (temp_f32 > 1.f) + { + temp_f32 = 1.f; + } + mBegin = temp_f32; + temp_f32 = end * CUT_QUANTA; + if (temp_f32 > 1.f) + { + temp_f32 = 1.f; + } + mEnd = 1.f - temp_f32; + temp_f32 = hollow * SCALE_QUANTA; + if (temp_f32 > 1.f) + { + temp_f32 = 1.f; + } + mHollow = temp_f32; + } + + bool operator==(const LLProfileParams ¶ms) const; + bool operator!=(const LLProfileParams ¶ms) const; + bool operator<(const LLProfileParams ¶ms) const; + + void copyParams(const LLProfileParams ¶ms); + + BOOL importFile(FILE *fp); + BOOL exportFile(FILE *fp) const; + + BOOL importLegacyStream(std::istream& input_stream); + BOOL exportLegacyStream(std::ostream& output_stream) const; + + LLSD asLLSD() const; + operator LLSD() const { return asLLSD(); } + bool fromLLSD(LLSD& sd); + + const F32& getBegin () const { return mBegin; } + const F32& getEnd () const { return mEnd; } + const F32& getHollow() const { return mHollow; } + const U8& getCurveType () const { return mCurveType; } + + void setCurveType(const U32 type) { mCurveType = type;} + void setBegin(const F32 begin) { mBegin = (begin >= 1.0f) ? 0.0f : ((int) (begin * 1000))/1000.0f;} + void setEnd(const F32 end) { mEnd = (end <= 0.0f) ? 1.0f : ((int) (end * 1000))/1000.0f;} + void setHollow(const F32 hollow) { mHollow = ((int) (hollow * 1000))/1000.0f;} + + friend std::ostream& operator<<(std::ostream &s, const LLProfileParams &profile_params); + +protected: + // Profile params + U8 mCurveType; + F32 mBegin; + F32 mEnd; + F32 mHollow; + + U32 mCRC; +}; + +inline bool LLProfileParams::operator==(const LLProfileParams ¶ms) const +{ + return + (getCurveType() == params.getCurveType()) && + (getBegin() == params.getBegin()) && + (getEnd() == params.getEnd()) && + (getHollow() == params.getHollow()); +} + +inline bool LLProfileParams::operator!=(const LLProfileParams ¶ms) const +{ + return + (getCurveType() != params.getCurveType()) || + (getBegin() != params.getBegin()) || + (getEnd() != params.getEnd()) || + (getHollow() != params.getHollow()); +} + + +inline bool LLProfileParams::operator<(const LLProfileParams ¶ms) const +{ + if (getCurveType() != params.getCurveType()) + { + return getCurveType() < params.getCurveType(); + } + else + if (getBegin() != params.getBegin()) + { + return getBegin() < params.getBegin(); + } + else + if (getEnd() != params.getEnd()) + { + return getEnd() < params.getEnd(); + } + else + { + return getHollow() < params.getHollow(); + } +} + +#define U8_TO_F32(x) (F32)(*((S8 *)&x)) + +class LLPathParams +{ +public: + LLPathParams() + { + mBegin = 0; + mEnd = 1; + mScale.setVec(1,1); + mShear.setVec(0,0); + mCurveType = LL_PCODE_PATH_LINE; + mTwistBegin = 0; + mTwistEnd = 0; + mRadiusOffset = 0; + mTaper.setVec(0,0); + mRevolutions = 1; + mSkew = 0; + } + + LLPathParams(U8 curve, F32 begin, F32 end, F32 scx, F32 scy, F32 shx, F32 shy, F32 twistend, F32 twistbegin, F32 radiusoffset, F32 tx, F32 ty, F32 revolutions, F32 skew) + : mCurveType(curve), mBegin(begin), mEnd(end), mTwistBegin(twistbegin), mTwistEnd(twistend), + mRadiusOffset(radiusoffset), mRevolutions(revolutions), mSkew(skew) + { + mScale.setVec(scx,scy); + mShear.setVec(shx,shy); + mTaper.setVec(tx,ty); + } + + LLPathParams(U8 curve, U8 begin, U8 end, U8 scx, U8 scy, U8 shx, U8 shy, U8 twistend, U8 twistbegin, U8 radiusoffset, U8 tx, U8 ty, U8 revolutions, U8 skew) + { + mCurveType = curve; + mBegin = (F32)(begin * SCALE_QUANTA); + mEnd = (F32)(100.f - end) * SCALE_QUANTA; + if (mEnd > 1.f) + mEnd = 1.f; + mScale.setVec((F32) (200 - scx) * SCALE_QUANTA,(F32) (200 - scy) * SCALE_QUANTA); + mShear.setVec(U8_TO_F32(shx) * SHEAR_QUANTA,U8_TO_F32(shy) * SHEAR_QUANTA); + mTwistBegin = U8_TO_F32(twistbegin) * SCALE_QUANTA; + mTwistEnd = U8_TO_F32(twistend) * SCALE_QUANTA; + mRadiusOffset = U8_TO_F32(radiusoffset) * SCALE_QUANTA; + mTaper.setVec(U8_TO_F32(tx) * TAPER_QUANTA,U8_TO_F32(ty) * TAPER_QUANTA); + mRevolutions = ((F32)revolutions) * REV_QUANTA + 1.0f; + mSkew = U8_TO_F32(skew) * SCALE_QUANTA; + } + + bool operator==(const LLPathParams ¶ms) const; + bool operator!=(const LLPathParams ¶ms) const; + bool operator<(const LLPathParams ¶ms) const; + + void copyParams(const LLPathParams ¶ms); + + BOOL importFile(FILE *fp); + BOOL exportFile(FILE *fp) const; + + BOOL importLegacyStream(std::istream& input_stream); + BOOL exportLegacyStream(std::ostream& output_stream) const; + + LLSD asLLSD() const; + operator LLSD() const { return asLLSD(); } + bool fromLLSD(LLSD& sd); + + const F32& getBegin() const { return mBegin; } + const F32& getEnd() const { return mEnd; } + const LLVector2 &getScale() const { return mScale; } + const F32& getScaleX() const { return mScale.mV[0]; } + const F32& getScaleY() const { return mScale.mV[1]; } + const LLVector2 getBeginScale() const; + const LLVector2 getEndScale() const; + const LLVector2 &getShear() const { return mShear; } + const F32& getShearX() const { return mShear.mV[0]; } + const F32& getShearY() const { return mShear.mV[1]; } + const U8& getCurveType () const { return mCurveType; } + + const F32& getTwistBegin() const { return mTwistBegin; } + const F32& getTwistEnd() const { return mTwistEnd; } + const F32& getTwist() const { return mTwistEnd; } // deprecated + const F32& getRadiusOffset() const { return mRadiusOffset; } + const LLVector2 &getTaper() const { return mTaper; } + const F32& getTaperX() const { return mTaper.mV[0]; } + const F32& getTaperY() const { return mTaper.mV[1]; } + const F32& getRevolutions() const { return mRevolutions; } + const F32& getSkew() const { return mSkew; } + + void setCurveType(const U8 type) { mCurveType = type; } + void setBegin(const F32 begin) { mBegin = begin; } + void setEnd(const F32 end) { mEnd = end; } + + void setScale(const F32 x, const F32 y) { mScale.setVec(x,y); } + void setScaleX(const F32 v) { mScale.mV[VX] = v; } + void setScaleY(const F32 v) { mScale.mV[VY] = v; } + void setShear(const F32 x, const F32 y) { mShear.setVec(x,y); } + void setShearX(const F32 v) { mShear.mV[VX] = v; } + void setShearY(const F32 v) { mShear.mV[VY] = v; } + + void setTwistBegin(const F32 twist_begin) { mTwistBegin = twist_begin; } + void setTwistEnd(const F32 twist_end) { mTwistEnd = twist_end; } + void setTwist(const F32 twist) { setTwistEnd(twist); } // deprecated + void setRadiusOffset(const F32 radius_offset){ mRadiusOffset = radius_offset; } + void setTaper(const F32 x, const F32 y) { mTaper.setVec(x,y); } + void setTaperX(const F32 v) { mTaper.mV[VX] = v; } + void setTaperY(const F32 v) { mTaper.mV[VY] = v; } + void setRevolutions(const F32 revolutions) { mRevolutions = revolutions; } + void setSkew(const F32 skew) { mSkew = skew; } + + friend std::ostream& operator<<(std::ostream &s, const LLPathParams &path_params); + +protected: + // Path params + U8 mCurveType; + F32 mBegin; + F32 mEnd; + LLVector2 mScale; + LLVector2 mShear; + + F32 mTwistBegin; + F32 mTwistEnd; + F32 mRadiusOffset; + LLVector2 mTaper; + F32 mRevolutions; + F32 mSkew; + + U32 mCRC; +}; + +inline bool LLPathParams::operator==(const LLPathParams ¶ms) const +{ + return + (getCurveType() == params.getCurveType()) && + (getScale() == params.getScale()) && + (getBegin() == params.getBegin()) && + (getEnd() == params.getEnd()) && + (getShear() == params.getShear()) && + (getTwist() == params.getTwist()) && + (getTwistBegin() == params.getTwistBegin()) && + (getRadiusOffset() == params.getRadiusOffset()) && + (getTaper() == params.getTaper()) && + (getRevolutions() == params.getRevolutions()) && + (getSkew() == params.getSkew()); +} + +inline bool LLPathParams::operator!=(const LLPathParams ¶ms) const +{ + return + (getCurveType() != params.getCurveType()) || + (getScale() != params.getScale()) || + (getBegin() != params.getBegin()) || + (getEnd() != params.getEnd()) || + (getShear() != params.getShear()) || + (getTwist() != params.getTwist()) || + (getTwistBegin() !=params.getTwistBegin()) || + (getRadiusOffset() != params.getRadiusOffset()) || + (getTaper() != params.getTaper()) || + (getRevolutions() != params.getRevolutions()) || + (getSkew() != params.getSkew()); +} + + +inline bool LLPathParams::operator<(const LLPathParams ¶ms) const +{ + if( getCurveType() != params.getCurveType()) + { + return getCurveType() < params.getCurveType(); + } + else + if( getScale() != params.getScale()) + { + return getScale() < params.getScale(); + } + else + if( getBegin() != params.getBegin()) + { + return getBegin() < params.getBegin(); + } + else + if( getEnd() != params.getEnd()) + { + return getEnd() < params.getEnd(); + } + else + if( getShear() != params.getShear()) + { + return getShear() < params.getShear(); + } + else + if( getTwist() != params.getTwist()) + { + return getTwist() < params.getTwist(); + } + else + if( getTwistBegin() != params.getTwistBegin()) + { + return getTwistBegin() < params.getTwistBegin(); + } + else + if( getRadiusOffset() != params.getRadiusOffset()) + { + return getRadiusOffset() < params.getRadiusOffset(); + } + else + if( getTaper() != params.getTaper()) + { + return getTaper() < params.getTaper(); + } + else + if( getRevolutions() != params.getRevolutions()) + { + return getRevolutions() < params.getRevolutions(); + } + else + { + return getSkew() < params.getSkew(); + } +} + +typedef LLVolumeParams* LLVolumeParamsPtr; +typedef const LLVolumeParams* const_LLVolumeParamsPtr; + +class LLVolumeParams +{ +public: + LLVolumeParams() + { + } + + LLVolumeParams(LLProfileParams &profile, LLPathParams &path) + : mProfileParams(profile), mPathParams(path) + { + } + + bool operator==(const LLVolumeParams ¶ms) const; + bool operator!=(const LLVolumeParams ¶ms) const; + bool operator<(const LLVolumeParams ¶ms) const; + + + void copyParams(const LLVolumeParams ¶ms); + + const LLProfileParams &getProfileParams() const {return mProfileParams;} + LLProfileParams &getProfileParams() {return mProfileParams;} + const LLPathParams &getPathParams() const {return mPathParams;} + LLPathParams &getPathParams() {return mPathParams;} + + BOOL importFile(FILE *fp); + BOOL exportFile(FILE *fp) const; + + BOOL importLegacyStream(std::istream& input_stream); + BOOL exportLegacyStream(std::ostream& output_stream) const; + + LLSD asLLSD() const; + operator LLSD() const { return asLLSD(); } + bool fromLLSD(LLSD& sd); + + bool setType(U8 profile, U8 path); + + //void setBeginS(const F32 beginS) { mProfileParams.setBegin(beginS); } // range 0 to 1 + //void setBeginT(const F32 beginT) { mPathParams.setBegin(beginT); } // range 0 to 1 + //void setEndS(const F32 endS) { mProfileParams.setEnd(endS); } // range 0 to 1, must be greater than begin + //void setEndT(const F32 endT) { mPathParams.setEnd(endT); } // range 0 to 1, must be greater than begin + + bool setBeginAndEndS(const F32 begin, const F32 end); // both range from 0 to 1, begin must be less than end + bool setBeginAndEndT(const F32 begin, const F32 end); // both range from 0 to 1, begin must be less than end + + bool setHollow(const F32 hollow); // range 0 to 1 + bool setRatio(const F32 x) { return setRatio(x,x); } // 0 = point, 1 = same as base + bool setShear(const F32 x) { return setShear(x,x); } // 0 = no movement, + bool setRatio(const F32 x, const F32 y); // 0 = point, 1 = same as base + bool setShear(const F32 x, const F32 y); // 0 = no movement + + bool setTwistBegin(const F32 twist_begin); // range -1 to 1 + bool setTwistEnd(const F32 twist_end); // range -1 to 1 + bool setTwist(const F32 twist) { return setTwistEnd(twist); } // deprecated + bool setTaper(const F32 x, const F32 y) { bool pass_x = setTaperX(x); bool pass_y = setTaperY(y); return pass_x && pass_y; } + bool setTaperX(const F32 v); // -1 to 1 + bool setTaperY(const F32 v); // -1 to 1 + bool setRevolutions(const F32 revolutions); // 1 to 4 + bool setRadiusOffset(const F32 radius_offset); + bool setSkew(const F32 skew); + + static bool validate(U8 prof_curve, F32 prof_begin, F32 prof_end, F32 hollow, + U8 path_curve, F32 path_begin, F32 path_end, + F32 scx, F32 scy, F32 shx, F32 shy, + F32 twistend, F32 twistbegin, F32 radiusoffset, + F32 tx, F32 ty, F32 revolutions, F32 skew); + + const F32& getBeginS() const { return mProfileParams.getBegin(); } + const F32& getBeginT() const { return mPathParams.getBegin(); } + const F32& getEndS() const { return mProfileParams.getEnd(); } + const F32& getEndT() const { return mPathParams.getEnd(); } + + const F32& getHollow() const { return mProfileParams.getHollow(); } + const F32& getTwist() const { return mPathParams.getTwist(); } + const F32& getRatio() const { return mPathParams.getScaleX(); } + const F32& getRatioX() const { return mPathParams.getScaleX(); } + const F32& getRatioY() const { return mPathParams.getScaleY(); } + const F32& getShearX() const { return mPathParams.getShearX(); } + const F32& getShearY() const { return mPathParams.getShearY(); } + + const F32& getTwistBegin()const { return mPathParams.getTwistBegin(); } + const F32& getRadiusOffset() const { return mPathParams.getRadiusOffset(); } + const F32& getTaper() const { return mPathParams.getTaperX(); } + const F32& getTaperX() const { return mPathParams.getTaperX(); } + const F32& getTaperY() const { return mPathParams.getTaperY(); } + const F32& getRevolutions() const { return mPathParams.getRevolutions(); } + const F32& getSkew() const { return mPathParams.getSkew(); } + + BOOL isConvex() const; + + // 'begin' and 'end' should be in range [0, 1] (they will be clamped) + // (begin, end) = (0, 1) will not change the volume + // (begin, end) = (0, 0.5) will reduce the volume to the first half of its profile/path (S/T) + void reduceS(F32 begin, F32 end); + void reduceT(F32 begin, F32 end); + + struct compare + { + bool operator()( const const_LLVolumeParamsPtr& first, const const_LLVolumeParamsPtr& second) const + { + return (*first < *second); + } + }; + + friend std::ostream& operator<<(std::ostream &s, const LLVolumeParams &volume_params); + +protected: + LLProfileParams mProfileParams; + LLPathParams mPathParams; +}; + + +class LLProfile +{ +public: + LLProfile(const LLProfileParams ¶ms) : mParams(params) + { + mTotal = 2; + mTotalOut = 0; + mDirty = TRUE; + mConcave = FALSE; + } + + ~LLProfile(); + + S32 getTotal() const { return mTotal; } + S32 getTotalOut() const { return mTotalOut; } // Total number of outside points + BOOL isHollow() const { return (mParams.getHollow() > 0); } + BOOL isFlat(S32 face) const { return (mFaces[face].mCount == 2); } + BOOL isOpen() const { return mOpen; } + void setDirty() { mDirty = TRUE; } + BOOL generate(BOOL path_open, F32 detail = 1.0f, S32 split = 0); + BOOL isConcave() const { return mConcave; } +public: + const LLProfileParams &mParams; + + struct Face + { + S32 mIndex; + S32 mCount; + F32 mScaleU; + BOOL mCap; + BOOL mFlat; + LLFaceID mFaceID; + }; + + std::vector mProfile; + std::vector mNormals; + std::vector mFaces; + std::vector mEdgeNormals; + std::vector mEdgeCenters; + F32 mMaxX; + F32 mMinX; + + friend std::ostream& operator<<(std::ostream &s, const LLProfile &profile); + +protected: + void genNormals(); + void genNGon(S32 sides, F32 offset=0.0f, F32 bevel = 0.0f, F32 ang_scale = 1.f, S32 split = 0); + + Face* addHole(BOOL flat, F32 sides, F32 offset, F32 box_hollow, F32 ang_scale, S32 split = 0); + Face* addCap (S16 faceID); + Face* addFace(S32 index, S32 count, F32 scaleU, S16 faceID, BOOL flat); + +protected: + BOOL mOpen; + BOOL mConcave; + BOOL mDirty; + + S32 mTotalOut; + S32 mTotal; +}; + +//------------------------------------------------------------------- +// SWEEP/EXTRUDE PATHS +//------------------------------------------------------------------- + +class LLPath +{ +public: + struct PathPt + { + LLVector3 mPos; + LLVector2 mScale; + LLQuaternion mRot; + F32 mTexT; + PathPt() { mPos.setVec(0,0,0); mTexT = 0; mScale.setVec(0,0); mRot.loadIdentity(); } + }; + +public: + LLPath(const LLPathParams ¶ms) : mParams(params) + { + mOpen = FALSE; + mDirty = TRUE; + mStep = 1; + } + + virtual ~LLPath(); + + void genNGon(S32 sides, F32 offset=0.0f, F32 end_scale = 1.f, F32 twist_scale = 1.f); + virtual BOOL generate(F32 detail=1.0f, S32 split = 0); + + BOOL isOpen() const { return mOpen; } + F32 getStep() const { return mStep; } + void setDirty() { mDirty = TRUE; } + + S32 getPathLength() const { return (S32)mPath.size(); } + + void resizePath(S32 length) { mPath.resize(length); } + + friend std::ostream& operator<<(std::ostream &s, const LLPath &path); + +public: + const LLPathParams &mParams; + std::vector mPath; + +protected: + BOOL mOpen; + S32 mTotal; + BOOL mDirty; + F32 mStep; +}; + +class LLDynamicPath : public LLPath +{ +public: + LLDynamicPath(const LLPathParams ¶ms) : LLPath(params) { } + BOOL generate(F32 detail=1.0f, S32 split = 0); +}; + +// Yet another "face" class - caches volume-specific, but not instance-specific data for faces) +class LLVolumeFace +{ +public: + LLVolumeFace(); + BOOL create(); + + class VertexData + { + public: + LLVector3 mPosition; + LLVector3 mNormal; + LLVector3 mBinormal; + LLVector2 mTexCoord; + }; + + enum + { + SINGLE_MASK = 0x0001, + CAP_MASK = 0x0002, + END_MASK = 0x0004, + SIDE_MASK = 0x0008, + INNER_MASK = 0x0010, + OUTER_MASK = 0x0020, + HOLLOW_MASK = 0x0040, + OPEN_MASK = 0x0080, + FLAT_MASK = 0x0100, + TOP_MASK = 0x0200, + BOTTOM_MASK = 0x0400 + } TypeMask; + +public: + S32 mID; + U32 mTypeMask; + LLVector3 mCenter; + + // Only used for INNER/OUTER faces + S32 mBeginS; + S32 mBeginT; + S32 mNumS; + S32 mNumT; + + std::vector mVertices; + std::vector mIndices; + std::vector mEdge; + LLVolume *mVolumep; // Deliberately NOT reference counted - djs 11/20/03 - otherwise would make an annoying circular reference + + // Shouldn't need num_old and num_new, really - djs + static BOOL updateColors(LLColor4U *old_colors, const S32 num_old, const LLVolumeFace &old_face, + LLStrider &new_colors, const S32 num_new, const LLVolumeFace &new_face); + +protected: + BOOL createUnCutCubeCap(); + BOOL createCap(); + BOOL createSide(); +}; + +class LLVolume : public LLRefCount +{ + friend class LLVolumeLODGroup; + +private: + LLVolume(const LLVolume&); // Don't implement + ~LLVolume(); // use unref + +public: + struct Point + { + LLVector3 mPos; + }; + + struct FaceParams + { + LLFaceID mFaceID; + S32 mBeginS; + S32 mCountS; + S32 mBeginT; + S32 mCountT; + }; + + LLVolume(const LLVolumeParams ¶ms, const F32 detail, const BOOL generate_single_face = FALSE, const BOOL is_unique = FALSE); + + U8 getProfileType() const { return mProfilep->mParams.getCurveType(); } + U8 getPathType() const { return mPathp->mParams.getCurveType(); } + S32 getNumFaces() const { return (S32)mProfilep->mFaces.size(); } + + const F32 getDetail() const { return mDetail; } + const LLVolumeParams & getParams() const { return mParams; } + LLVolumeParams getCopyOfParams() const { return mParams; } + const LLProfile& getProfile() const { return *mProfilep; } + LLPath& getPath() const { return *mPathp; } + const std::vector& getMesh() const { return mMesh; } + const LLVector3& getMeshPt(const U32 i) const { return mMesh[i].mPos; } + + void setDirty() { mPathp->setDirty(); mProfilep->setDirty(); } + + void regen(); + + BOOL isConvex() const; + BOOL isCap(S32 face); + BOOL isFlat(S32 face); + BOOL isUnique() const { return mUnique; } + + S32 *getTriangleIndices(U32 &num_indices) const; + void generateSilhouetteVertices(std::vector &vertices, std::vector &normals, std::vector &segments, const LLVector3& view_vec, + const LLMatrix4& mat, + const LLMatrix3& norm_mat); + + //get the face index of the face that intersects with the given line segment at the point + //closest to start. Moves end to the point of intersection. Returns -1 if no intersection. + //Line segment must be in volume space. + S32 lineSegmentIntersect(const LLVector3& start, LLVector3& end) const; + + // The following cleans up vertices and triangles, + // getting rid of degenerate triangles and duplicate vertices, + // and allocates new arrays with the clean data. + static BOOL cleanupTriangleData( const S32 num_input_vertices, + const std::vector &input_vertices, + const S32 num_input_triangles, + S32 *input_triangles, + S32 &num_output_vertices, + LLVector3 **output_vertices, + S32 &num_output_triangles, + S32 **output_triangles); + LLFaceID generateFaceMask(); + + BOOL isFaceMaskValid(LLFaceID face_mask); + static S32 mNumMeshPoints; + + friend std::ostream& operator<<(std::ostream &s, const LLVolume &volume); + friend std::ostream& operator<<(std::ostream &s, const LLVolume *volumep); // HACK to bypass Windoze confusion over + // conversion if *(LLVolume*) to LLVolume& + const LLVolumeFace &getVolumeFace(const S32 f) const {return mVolumeFaces[f];} // DO NOT DELETE VOLUME WHILE USING THIS REFERENCE, OR HOLD A POINTER TO THIS VOLUMEFACE + + U32 mFaceMask; // bit array of which faces exist in this volume + LLVector3 mBounds[2]; // bounding box (center, half-height) + LLVector3 mLODScaleBias; // vector for biasing LOD based on scale + +protected: + BOOL generate(); + void createVolumeFaces(); + +protected: + BOOL mUnique; + F32 mDetail; + LLVolumeParams mParams; + LLPath *mPathp; + LLProfile *mProfilep; + std::vector mMesh; + + BOOL mGenerateSingleFace; + S32 mNumVolumeFaces; + LLVolumeFace *mVolumeFaces; +}; + +std::ostream& operator<<(std::ostream &s, const LLVolumeParams &volume_params); + +LLVector3 calc_binormal_from_triangle( + const LLVector3& pos0, + const LLVector2& tex0, + const LLVector3& pos1, + const LLVector2& tex1, + const LLVector3& pos2, + const LLVector2& tex2); + +#endif diff --git a/indra/llmath/llvolumemgr.cpp b/indra/llmath/llvolumemgr.cpp new file mode 100644 index 0000000000..54be916c12 --- /dev/null +++ b/indra/llmath/llvolumemgr.cpp @@ -0,0 +1,295 @@ +/** + * @file llvolumemgr.cpp + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llvolumemgr.h" +#include "llvolume.h" + + +//#define DEBUG_VOLUME + +LLVolumeMgr* gVolumeMgr = 0; + +const F32 BASE_THRESHOLD = 0.03f; + +//static +F32 LLVolumeLODGroup::mDetailThresholds[NUM_LODS] = {BASE_THRESHOLD, + 2*BASE_THRESHOLD, + 8*BASE_THRESHOLD, + 100*BASE_THRESHOLD}; + +//static +F32 LLVolumeLODGroup::mDetailScales[NUM_LODS] = {1.f, 1.5f, 2.5f, 4.f}; + +//============================================================================ +//static +void LLVolumeMgr::initClass() +{ + gVolumeMgr = new LLVolumeMgr(); +} + +//static +BOOL LLVolumeMgr::cleanupClass() +{ + BOOL res = FALSE; + if (gVolumeMgr) { + res = gVolumeMgr->cleanup(); + delete gVolumeMgr; + gVolumeMgr = 0; + } + return res; +} + +//============================================================================ + +LLVolumeMgr::LLVolumeMgr() +{ + mDataMutex = new LLMutex(gAPRPoolp); +// mNumVolumes = 0; +} + +LLVolumeMgr::~LLVolumeMgr() +{ + cleanup(); + delete mDataMutex; +} + +BOOL LLVolumeMgr::cleanup() +{ + #ifdef DEBUG_VOLUME + { + lldebugs << "LLVolumeMgr::cleanup()" << llendl; + } + #endif + BOOL no_refs = TRUE; + mDataMutex->lock(); + for (volume_lod_group_map_t::iterator iter = mVolumeLODGroups.begin(), + end = mVolumeLODGroups.end(); + iter != end; iter++) + { + LLVolumeLODGroup *volgroupp = iter->second; + if (volgroupp->getNumRefs() != 1) + { + llwarns << "Volume group " << volgroupp << " has " + << volgroupp->getNumRefs() << " remaining refs" << llendl; + llwarns << volgroupp->getParams() << llendl; + no_refs = FALSE; + } + volgroupp->unref();// this ); + } + mVolumeLODGroups.clear(); + mDataMutex->unlock(); + return no_refs; +} + +LLVolume *LLVolumeMgr::getVolume(const LLVolumeParams &volume_params, const S32 detail) +{ + LLVolumeLODGroup* volgroupp; + mDataMutex->lock(); + volume_lod_group_map_t::iterator iter = mVolumeLODGroups.find(&volume_params); + if( iter == mVolumeLODGroups.end() ) + { + volgroupp = new LLVolumeLODGroup(volume_params); + const LLVolumeParams* params = &(volgroupp->getParams()); + mVolumeLODGroups[params] = volgroupp; + volgroupp->ref(); // initial reference + } + else + { + volgroupp = iter->second; + } + volgroupp->ref();// this ); + mDataMutex->unlock(); + // mNumVolumes++; + #ifdef DEBUG_VOLUME + { + lldebugs << "LLVolumeMgr::getVolume() " << (*this) << llendl; + } + #endif + return volgroupp->getLOD(detail); +} + +void LLVolumeMgr::cleanupVolume(LLVolume *volumep) +{ + if (volumep->isUnique()) + { + // TomY: Don't need to manage this volume. It is a unique instance. + return; + } + LLVolumeParams* params = (LLVolumeParams*) &(volumep->getParams()); + mDataMutex->lock(); + volume_lod_group_map_t::iterator iter = mVolumeLODGroups.find(params); + if( iter == mVolumeLODGroups.end() ) + { + llerrs << "Warning! Tried to cleanup unknown volume type! " << *params << llendl; + mDataMutex->unlock(); + return; + } + else + { + LLVolumeLODGroup* volgroupp = iter->second; + + volgroupp->derefLOD(volumep); + volgroupp->unref();// this ); + if (volgroupp->getNumRefs() == 1) + { + mVolumeLODGroups.erase(params); + volgroupp->unref();// this ); + } + // mNumVolumes--; + } + mDataMutex->unlock(); + + #ifdef DEBUG_VOLUME + { + lldebugs << "LLVolumeMgr::cleanupVolume() " << (*this) << llendl; + } + #endif +} + +void LLVolumeMgr::dump() +{ + F32 avg = 0.f; + mDataMutex->lock(); + for (volume_lod_group_map_t::iterator iter = mVolumeLODGroups.begin(), + end = mVolumeLODGroups.end(); + iter != end; iter++) + { + LLVolumeLODGroup *volgroupp = iter->second; + avg += volgroupp->dump(); + } + int count = (int)mVolumeLODGroups.size(); + avg = count ? avg / (F32)count : 0.0f; + mDataMutex->unlock(); + llinfos << "Average usage of LODs " << avg << llendl; +} + +std::ostream& operator<<(std::ostream& s, const LLVolumeMgr& volume_mgr) +{ + s << "{ numLODgroups=" << volume_mgr.mVolumeLODGroups.size() << ", "; + + S32 total_refs = 0; + volume_mgr.mDataMutex->lock(); + + LLVolumeMgr::volume_lod_group_map_iter iter = volume_mgr.mVolumeLODGroups.begin(); + LLVolumeMgr::volume_lod_group_map_iter end = volume_mgr.mVolumeLODGroups.end(); + for ( ; iter != end; ++iter) + { + LLVolumeLODGroup *volgroupp = iter->second; + total_refs += volgroupp->getNumRefs(); + s << ", " << (*volgroupp); + } + + volume_mgr.mDataMutex->unlock(); + + s << ", total_refs=" << total_refs << " }"; + return s; +} + +LLVolumeLODGroup::LLVolumeLODGroup(const LLVolumeParams ¶ms) +{ + S32 i; + mParams = params; + + for (i = 0; i < NUM_LODS; i++) + { + mLODRefs[i] = 0; + mVolumeLODs[i] = NULL; + mAccessCount[i] = 0; + } +} + +LLVolumeLODGroup::~LLVolumeLODGroup() +{ + S32 i; + for (i = 0; i < NUM_LODS; i++) + { + delete mVolumeLODs[i]; + mVolumeLODs[i] = NULL; + } +} + + +LLVolume * LLVolumeLODGroup::getLOD(const S32 detail) +{ + llassert(detail >=0 && detail < NUM_LODS); + mAccessCount[detail]++; + mLODRefs[detail]++; + if (!mVolumeLODs[detail]) + { + mVolumeLODs[detail] = new LLVolume(mParams, mDetailScales[detail]); + } + return mVolumeLODs[detail]; +} + +BOOL LLVolumeLODGroup::derefLOD(LLVolume *volumep) +{ + S32 i; + for (i = 0; i < NUM_LODS; i++) + { + if (mVolumeLODs[i] == volumep) + { + mLODRefs[i]--; + if (!mLODRefs[i]) + { + mVolumeLODs[i] = NULL; + } + return TRUE; + } + } + llerrs << "Deref of non-matching LOD in volume LOD group" << llendl; + return FALSE; +} + +S32 LLVolumeLODGroup::getDetailFromTan(const F32 tan_angle) +{ + S32 i = 0; + while (i < (NUM_LODS - 1)) + { + if (tan_angle <= mDetailThresholds[i]) + { + return i; + } + i++; + } + return NUM_LODS - 1; +} + +F32 LLVolumeLODGroup::getVolumeScaleFromDetail(const S32 detail) +{ + return mDetailScales[detail]; +} + +F32 LLVolumeLODGroup::dump() +{ + char dump_str[255]; + F32 usage = 0.f; + for (S32 i = 0; i < NUM_LODS; i++) + { + if (mAccessCount[i] > 0) + { + usage += 1.f; + } + } + usage = usage / (F32)NUM_LODS; + + sprintf(dump_str, "%.3f %d %d %d %d", usage, mAccessCount[0], mAccessCount[1], mAccessCount[2], mAccessCount[3]); + + llinfos << dump_str << llendl; + return usage; +} + +std::ostream& operator<<(std::ostream& s, const LLVolumeLODGroup& volgroup) +{ + s << "{ numRefs=" << volgroup.getNumRefs(); + s << ", mParams=" << volgroup.mParams; + s << " }"; + + return s; +} + diff --git a/indra/llmath/llvolumemgr.h b/indra/llmath/llvolumemgr.h new file mode 100644 index 0000000000..675c8f640c --- /dev/null +++ b/indra/llmath/llvolumemgr.h @@ -0,0 +1,82 @@ +/** + * @file llvolumemgr.h + * @brief LLVolumeMgr class. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLVOLUMEMGR_H +#define LL_LLVOLUMEMGR_H + +#include + +#include "llvolume.h" +#include "llmemory.h" +#include "llthread.h" + +class LLVolumeParams; +class LLVolumeLODGroup; + +class LLVolumeLODGroup : public LLThreadSafeRefCount +{ +protected: + ~LLVolumeLODGroup(); + +public: + enum + { + NUM_LODS = 4 + }; + + LLVolumeLODGroup(const LLVolumeParams ¶ms); + + BOOL derefLOD(LLVolume *volumep); + static S32 getDetailFromTan(const F32 tan_angle); + static F32 getVolumeScaleFromDetail(const S32 detail); + + LLVolume *getLOD(const S32 detail); + const LLVolumeParams &getParams() const { return mParams; }; + + F32 dump(); + friend std::ostream& operator<<(std::ostream& s, const LLVolumeLODGroup& volgroup); + +protected: + LLVolumeParams mParams; + + S32 mLODRefs[NUM_LODS]; + LLVolume *mVolumeLODs[NUM_LODS]; + static F32 mDetailThresholds[NUM_LODS]; + static F32 mDetailScales[NUM_LODS]; + S32 mAccessCount[NUM_LODS]; +}; + +class LLVolumeMgr +{ +public: + static void initClass(); + static BOOL cleanupClass(); + +public: + LLVolumeMgr(); + ~LLVolumeMgr(); + BOOL cleanup(); // Cleanup all volumes being managed, returns TRUE if no dangling references + LLVolume *getVolume(const LLVolumeParams &volume_params, const S32 detail); + void cleanupVolume(LLVolume *volumep); + + void dump(); + friend std::ostream& operator<<(std::ostream& s, const LLVolumeMgr& volume_mgr); + +protected: + typedef std::map volume_lod_group_map_t; + typedef volume_lod_group_map_t::const_iterator volume_lod_group_map_iter; + volume_lod_group_map_t mVolumeLODGroups; + + LLMutex* mDataMutex; + +// S32 mNumVolumes; +}; + +extern LLVolumeMgr* gVolumeMgr; + +#endif // LL_LLVOLUMEMGR_H diff --git a/indra/llmath/m3math.cpp b/indra/llmath/m3math.cpp new file mode 100644 index 0000000000..523acb4dcf --- /dev/null +++ b/indra/llmath/m3math.cpp @@ -0,0 +1,530 @@ +/** + * @file m3math.cpp + * @brief LLMatrix3 class implementation. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +//#include "vmath.h" +#include "v3math.h" +#include "v3dmath.h" +#include "v4math.h" +#include "m4math.h" +#include "m3math.h" +#include "llquaternion.h" + +// LLMatrix3 + +// ji +// LLMatrix3 = |00 01 02 | +// |10 11 12 | +// |20 21 22 | + +// LLMatrix3 = |fx fy fz | forward-axis +// |lx ly lz | left-axis +// |ux uy uz | up-axis + + +// Constructors + + +LLMatrix3::LLMatrix3(const LLQuaternion &q) +{ + *this = q.getMatrix3(); +} + + +LLMatrix3::LLMatrix3(const F32 angle, const LLVector3 &vec) +{ + LLQuaternion quat(angle, vec); + *this = setRot(quat); +} + +LLMatrix3::LLMatrix3(const F32 angle, const LLVector3d &vec) +{ + LLVector3 vec_f; + vec_f.setVec(vec); + LLQuaternion quat(angle, vec_f); + *this = setRot(quat); +} + +LLMatrix3::LLMatrix3(const F32 angle, const LLVector4 &vec) +{ + LLQuaternion quat(angle, vec); + *this = setRot(quat); +} + +LLMatrix3::LLMatrix3(const F32 angle, const F32 x, const F32 y, const F32 z) +{ + LLVector3 vec(x, y, z); + LLQuaternion quat(angle, vec); + *this = setRot(quat); +} + +LLMatrix3::LLMatrix3(const F32 roll, const F32 pitch, const F32 yaw) +{ + // Rotates RH about x-axis by 'roll' then + // rotates RH about the old y-axis by 'pitch' then + // rotates RH about the original z-axis by 'yaw'. + // . + // /|\ yaw axis + // | __. + // ._ ___| /| pitch axis + // _||\ \\ |-. / + // \|| \_______\_|__\_/_______ + // | _ _ o o o_o_o_o o /_\_ ________\ roll axis + // // /_______/ /__________> / + // /_,-' // / + // /__,-' + + F32 cx, sx, cy, sy, cz, sz; + F32 cxsy, sxsy; + + cx = (F32)cos(roll); //A + sx = (F32)sin(roll); //B + cy = (F32)cos(pitch); //C + sy = (F32)sin(pitch); //D + cz = (F32)cos(yaw); //E + sz = (F32)sin(yaw); //F + + cxsy = cx * sy; //AD + sxsy = sx * sy; //BD + + mMatrix[0][0] = cy * cz; + mMatrix[1][0] = -cy * sz; + mMatrix[2][0] = sy; + mMatrix[0][1] = sxsy * cz + cx * sz; + mMatrix[1][1] = -sxsy * sz + cx * cz; + mMatrix[2][1] = -sx * cy; + mMatrix[0][2] = -cxsy * cz + sx * sz; + mMatrix[1][2] = cxsy * sz + sx * cz; + mMatrix[2][2] = cx * cy; +} + +// From Matrix and Quaternion FAQ +void LLMatrix3::getEulerAngles(F32 *roll, F32 *pitch, F32 *yaw) const +{ + F64 angle_x, angle_y, angle_z; + F64 cx, cy, cz; // cosine of angle_x, angle_y, angle_z + F64 sx, sz; // sine of angle_x, angle_y, angle_z + + angle_y = asin(llclamp(mMatrix[2][0], -1.f, 1.f)); + cy = cos(angle_y); + + if (fabs(cy) > 0.005) // non-zero + { + // no gimbal lock + cx = mMatrix[2][2] / cy; + sx = - mMatrix[2][1] / cy; + + angle_x = (F32) atan2(sx, cx); + + cz = mMatrix[0][0] / cy; + sz = - mMatrix[1][0] / cy; + + angle_z = (F32) atan2(sz, cz); + } + else + { + // yup, gimbal lock + angle_x = 0; + + // some tricky math thereby avoided, see article + + cz = mMatrix[1][1]; + sz = mMatrix[0][1]; + + angle_z = atan2(sz, cz); + } + + *roll = (F32)angle_x; + *pitch = (F32)angle_y; + *yaw = (F32)angle_z; +} + + +// Clear and Assignment Functions + +const LLMatrix3& LLMatrix3::identity() +{ + mMatrix[0][0] = 1.f; + mMatrix[0][1] = 0.f; + mMatrix[0][2] = 0.f; + + mMatrix[1][0] = 0.f; + mMatrix[1][1] = 1.f; + mMatrix[1][2] = 0.f; + + mMatrix[2][0] = 0.f; + mMatrix[2][1] = 0.f; + mMatrix[2][2] = 1.f; + return (*this); +} + +const LLMatrix3& LLMatrix3::zero() +{ + mMatrix[0][0] = 0.f; + mMatrix[0][1] = 0.f; + mMatrix[0][2] = 0.f; + + mMatrix[1][0] = 0.f; + mMatrix[1][1] = 0.f; + mMatrix[1][2] = 0.f; + + mMatrix[2][0] = 0.f; + mMatrix[2][1] = 0.f; + mMatrix[2][2] = 0.f; + return (*this); +} + +// various useful mMatrix functions + +const LLMatrix3& LLMatrix3::transpose() +{ + // transpose the matrix + F32 temp; + temp = mMatrix[VX][VY]; mMatrix[VX][VY] = mMatrix[VY][VX]; mMatrix[VY][VX] = temp; + temp = mMatrix[VX][VZ]; mMatrix[VX][VZ] = mMatrix[VZ][VX]; mMatrix[VZ][VX] = temp; + temp = mMatrix[VY][VZ]; mMatrix[VY][VZ] = mMatrix[VZ][VY]; mMatrix[VZ][VY] = temp; + return *this; +} + + +F32 LLMatrix3::determinant() const +{ + // Is this a useful method when we assume the matrices are valid rotation + // matrices throughout this implementation? + return mMatrix[0][0] * (mMatrix[1][1] * mMatrix[2][2] - mMatrix[1][2] * mMatrix[2][1]) + + mMatrix[0][1] * (mMatrix[1][2] * mMatrix[2][0] - mMatrix[1][0] * mMatrix[2][2]) + + mMatrix[0][2] * (mMatrix[1][0] * mMatrix[2][1] - mMatrix[1][1] * mMatrix[2][0]); +} + +// This is identical to the transMat3() method because we assume a rotation matrix +const LLMatrix3& LLMatrix3::invert() +{ + // transpose the matrix + F32 temp; + temp = mMatrix[VX][VY]; mMatrix[VX][VY] = mMatrix[VY][VX]; mMatrix[VY][VX] = temp; + temp = mMatrix[VX][VZ]; mMatrix[VX][VZ] = mMatrix[VZ][VX]; mMatrix[VZ][VX] = temp; + temp = mMatrix[VY][VZ]; mMatrix[VY][VZ] = mMatrix[VZ][VY]; mMatrix[VZ][VY] = temp; + return *this; +} + +// does not assume a rotation matrix, and does not divide by determinant, assuming results will be renormalized +const LLMatrix3& LLMatrix3::adjointTranspose() +{ + LLMatrix3 adjoint_transpose; + adjoint_transpose.mMatrix[VX][VX] = mMatrix[VY][VY] * mMatrix[VZ][VZ] - mMatrix[VY][VZ] * mMatrix[VZ][VY] ; + adjoint_transpose.mMatrix[VY][VX] = mMatrix[VY][VZ] * mMatrix[VZ][VX] - mMatrix[VY][VX] * mMatrix[VZ][VZ] ; + adjoint_transpose.mMatrix[VZ][VX] = mMatrix[VY][VX] * mMatrix[VZ][VY] - mMatrix[VY][VY] * mMatrix[VZ][VX] ; + adjoint_transpose.mMatrix[VX][VY] = mMatrix[VZ][VY] * mMatrix[VX][VZ] - mMatrix[VZ][VZ] * mMatrix[VX][VY] ; + adjoint_transpose.mMatrix[VY][VY] = mMatrix[VZ][VZ] * mMatrix[VX][VX] - mMatrix[VZ][VX] * mMatrix[VX][VZ] ; + adjoint_transpose.mMatrix[VZ][VY] = mMatrix[VZ][VX] * mMatrix[VX][VY] - mMatrix[VZ][VY] * mMatrix[VX][VX] ; + adjoint_transpose.mMatrix[VX][VZ] = mMatrix[VX][VY] * mMatrix[VY][VZ] - mMatrix[VX][VZ] * mMatrix[VY][VY] ; + adjoint_transpose.mMatrix[VY][VZ] = mMatrix[VX][VZ] * mMatrix[VY][VX] - mMatrix[VX][VX] * mMatrix[VY][VZ] ; + adjoint_transpose.mMatrix[VZ][VZ] = mMatrix[VX][VX] * mMatrix[VY][VY] - mMatrix[VX][VY] * mMatrix[VY][VX] ; + + *this = adjoint_transpose; + return *this; +} + +// SJB: This code is correct for a logicly stored (non-transposed) matrix; +// Our matrices are stored transposed, OpenGL style, so this generates the +// INVERSE quaternion (-x, -y, -z, w)! +// Because we use similar logic in LLQuaternion::getMatrix3, +// we are internally consistant so everything works OK :) +LLQuaternion LLMatrix3::quaternion() const +{ + LLQuaternion quat; + F32 tr, s, q[4]; + U32 i, j, k; + U32 nxt[3] = {1, 2, 0}; + + tr = mMatrix[0][0] + mMatrix[1][1] + mMatrix[2][2]; + + // check the diagonal + if (tr > 0.f) + { + s = (F32)sqrt (tr + 1.f); + quat.mQ[VS] = s / 2.f; + s = 0.5f / s; + quat.mQ[VX] = (mMatrix[1][2] - mMatrix[2][1]) * s; + quat.mQ[VY] = (mMatrix[2][0] - mMatrix[0][2]) * s; + quat.mQ[VZ] = (mMatrix[0][1] - mMatrix[1][0]) * s; + } + else + { + // diagonal is negative + i = 0; + if (mMatrix[1][1] > mMatrix[0][0]) + i = 1; + if (mMatrix[2][2] > mMatrix[i][i]) + i = 2; + + j = nxt[i]; + k = nxt[j]; + + + s = (F32)sqrt ((mMatrix[i][i] - (mMatrix[j][j] + mMatrix[k][k])) + 1.f); + + q[i] = s * 0.5f; + + if (s != 0.f) + s = 0.5f / s; + + q[3] = (mMatrix[j][k] - mMatrix[k][j]) * s; + q[j] = (mMatrix[i][j] + mMatrix[j][i]) * s; + q[k] = (mMatrix[i][k] + mMatrix[k][i]) * s; + + quat.setQuat(q); + } + return quat; +} + + +// These functions take Rotation arguments +const LLMatrix3& LLMatrix3::setRot(const F32 angle, const F32 x, const F32 y, const F32 z) +{ + LLMatrix3 mat(angle, x, y, z); + *this = mat; + return *this; +} + +const LLMatrix3& LLMatrix3::setRot(const F32 angle, const LLVector3 &vec) +{ + LLMatrix3 mat(angle, vec); + *this = mat; + return *this; +} + +const LLMatrix3& LLMatrix3::setRot(const F32 roll, const F32 pitch, const F32 yaw) +{ + LLMatrix3 mat(roll, pitch, yaw); + *this = mat; + return *this; +} + + +const LLMatrix3& LLMatrix3::setRot(const LLQuaternion &q) +{ + LLMatrix3 mat(q); + *this = mat; + return *this; +} + + +const LLMatrix3& LLMatrix3::setRows(const LLVector3 &fwd, const LLVector3 &left, const LLVector3 &up) +{ + mMatrix[0][0] = fwd.mV[0]; + mMatrix[0][1] = fwd.mV[1]; + mMatrix[0][2] = fwd.mV[2]; + + mMatrix[1][0] = left.mV[0]; + mMatrix[1][1] = left.mV[1]; + mMatrix[1][2] = left.mV[2]; + + mMatrix[2][0] = up.mV[0]; + mMatrix[2][1] = up.mV[1]; + mMatrix[2][2] = up.mV[2]; + + return *this; +} + + +// Rotate exisitng mMatrix +const LLMatrix3& LLMatrix3::rotate(const F32 angle, const F32 x, const F32 y, const F32 z) +{ + LLMatrix3 mat(angle, x, y, z); + *this *= mat; + return *this; +} + + +const LLMatrix3& LLMatrix3::rotate(const F32 angle, const LLVector3 &vec) +{ + LLMatrix3 mat(angle, vec); + *this *= mat; + return *this; +} + + +const LLMatrix3& LLMatrix3::rotate(const F32 roll, const F32 pitch, const F32 yaw) +{ + LLMatrix3 mat(roll, pitch, yaw); + *this *= mat; + return *this; +} + + +const LLMatrix3& LLMatrix3::rotate(const LLQuaternion &q) +{ + LLMatrix3 mat(q); + *this *= mat; + return *this; +} + + +LLVector3 LLMatrix3::getFwdRow() const +{ + return LLVector3(mMatrix[VX]); +} + +LLVector3 LLMatrix3::getLeftRow() const +{ + return LLVector3(mMatrix[VY]); +} + +LLVector3 LLMatrix3::getUpRow() const +{ + return LLVector3(mMatrix[VZ]); +} + + + +const LLMatrix3& LLMatrix3::orthogonalize() +{ + LLVector3 x_axis(mMatrix[VX]); + LLVector3 y_axis(mMatrix[VY]); + LLVector3 z_axis(mMatrix[VZ]); + + x_axis.normVec(); + y_axis -= x_axis * (x_axis * y_axis); + y_axis.normVec(); + z_axis = x_axis % y_axis; + setRows(x_axis, y_axis, z_axis); + return (*this); +} + + +// LLMatrix3 Operators + +LLMatrix3 operator*(const LLMatrix3 &a, const LLMatrix3 &b) +{ + U32 i, j; + LLMatrix3 mat; + for (i = 0; i < NUM_VALUES_IN_MAT3; i++) + { + for (j = 0; j < NUM_VALUES_IN_MAT3; j++) + { + mat.mMatrix[j][i] = a.mMatrix[j][0] * b.mMatrix[0][i] + + a.mMatrix[j][1] * b.mMatrix[1][i] + + a.mMatrix[j][2] * b.mMatrix[2][i]; + } + } + return mat; +} + +/* Not implemented to help enforce code consistency with the syntax of + row-major notation. This is a Good Thing. +LLVector3 operator*(const LLMatrix3 &a, const LLVector3 &b) +{ + LLVector3 vec; + // matrix operates "from the left" on column vector + vec.mV[VX] = a.mMatrix[VX][VX] * b.mV[VX] + + a.mMatrix[VX][VY] * b.mV[VY] + + a.mMatrix[VX][VZ] * b.mV[VZ]; + + vec.mV[VY] = a.mMatrix[VY][VX] * b.mV[VX] + + a.mMatrix[VY][VY] * b.mV[VY] + + a.mMatrix[VY][VZ] * b.mV[VZ]; + + vec.mV[VZ] = a.mMatrix[VZ][VX] * b.mV[VX] + + a.mMatrix[VZ][VY] * b.mV[VY] + + a.mMatrix[VZ][VZ] * b.mV[VZ]; + return vec; +} +*/ + + +LLVector3 operator*(const LLVector3 &a, const LLMatrix3 &b) +{ + // matrix operates "from the right" on row vector + return LLVector3( + a.mV[VX] * b.mMatrix[VX][VX] + + a.mV[VY] * b.mMatrix[VY][VX] + + a.mV[VZ] * b.mMatrix[VZ][VX], + + a.mV[VX] * b.mMatrix[VX][VY] + + a.mV[VY] * b.mMatrix[VY][VY] + + a.mV[VZ] * b.mMatrix[VZ][VY], + + a.mV[VX] * b.mMatrix[VX][VZ] + + a.mV[VY] * b.mMatrix[VY][VZ] + + a.mV[VZ] * b.mMatrix[VZ][VZ] ); +} + +LLVector3d operator*(const LLVector3d &a, const LLMatrix3 &b) +{ + // matrix operates "from the right" on row vector + return LLVector3d( + a.mdV[VX] * b.mMatrix[VX][VX] + + a.mdV[VY] * b.mMatrix[VY][VX] + + a.mdV[VZ] * b.mMatrix[VZ][VX], + + a.mdV[VX] * b.mMatrix[VX][VY] + + a.mdV[VY] * b.mMatrix[VY][VY] + + a.mdV[VZ] * b.mMatrix[VZ][VY], + + a.mdV[VX] * b.mMatrix[VX][VZ] + + a.mdV[VY] * b.mMatrix[VY][VZ] + + a.mdV[VZ] * b.mMatrix[VZ][VZ] ); +} + +bool operator==(const LLMatrix3 &a, const LLMatrix3 &b) +{ + U32 i, j; + for (i = 0; i < NUM_VALUES_IN_MAT3; i++) + { + for (j = 0; j < NUM_VALUES_IN_MAT3; j++) + { + if (a.mMatrix[j][i] != b.mMatrix[j][i]) + return FALSE; + } + } + return TRUE; +} + +bool operator!=(const LLMatrix3 &a, const LLMatrix3 &b) +{ + U32 i, j; + for (i = 0; i < NUM_VALUES_IN_MAT3; i++) + { + for (j = 0; j < NUM_VALUES_IN_MAT3; j++) + { + if (a.mMatrix[j][i] != b.mMatrix[j][i]) + return TRUE; + } + } + return FALSE; +} + +const LLMatrix3& operator*=(LLMatrix3 &a, const LLMatrix3 &b) +{ + U32 i, j; + LLMatrix3 mat; + for (i = 0; i < NUM_VALUES_IN_MAT3; i++) + { + for (j = 0; j < NUM_VALUES_IN_MAT3; j++) + { + mat.mMatrix[j][i] = a.mMatrix[j][0] * b.mMatrix[0][i] + + a.mMatrix[j][1] * b.mMatrix[1][i] + + a.mMatrix[j][2] * b.mMatrix[2][i]; + } + } + a = mat; + return a; +} + +std::ostream& operator<<(std::ostream& s, const LLMatrix3 &a) +{ + s << "{ " + << a.mMatrix[VX][VX] << ", " << a.mMatrix[VX][VY] << ", " << a.mMatrix[VX][VZ] << "; " + << a.mMatrix[VY][VX] << ", " << a.mMatrix[VY][VY] << ", " << a.mMatrix[VY][VZ] << "; " + << a.mMatrix[VZ][VX] << ", " << a.mMatrix[VZ][VY] << ", " << a.mMatrix[VZ][VZ] + << " }"; + return s; +} + diff --git a/indra/llmath/m3math.h b/indra/llmath/m3math.h new file mode 100644 index 0000000000..ac93ed86fb --- /dev/null +++ b/indra/llmath/m3math.h @@ -0,0 +1,198 @@ +/** + * @file m3math.h + * @brief LLMatrix3 class header file. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_M3MATH_H +#define LL_M3MATH_H + +#include "llerror.h" + +class LLVector4; +class LLVector3; +class LLVector3d; +class LLQuaternion; + +// NOTA BENE: Currently assuming a right-handed, z-up universe + +// ji +// LLMatrix3 = | 00 01 02 | +// | 10 11 12 | +// | 20 21 22 | + +// LLMatrix3 = | fx fy fz | forward-axis +// | lx ly lz | left-axis +// | ux uy uz | up-axis + +// NOTE: The world of computer graphics uses column-vectors and matricies that +// "operate to the left". + + +static const U32 NUM_VALUES_IN_MAT3 = 3; +#if LL_WINDOWS +__declspec( align(16) ) +#endif +class LLMatrix3 +{ + public: + F32 mMatrix[NUM_VALUES_IN_MAT3][NUM_VALUES_IN_MAT3]; + + LLMatrix3(void); // Initializes Matrix to identity matrix + explicit LLMatrix3(const F32 *mat); // Initializes Matrix to values in mat + explicit LLMatrix3(const LLQuaternion &q); // Initializes Matrix with rotation q + + LLMatrix3(const F32 angle, const F32 x, const F32 y, const F32 z); // Initializes Matrix with axis angle + LLMatrix3(const F32 angle, const LLVector3 &vec); // Initializes Matrix with axis angle + LLMatrix3(const F32 angle, const LLVector3d &vec); // Initializes Matrix with axis angle + LLMatrix3(const F32 angle, const LLVector4 &vec); // Initializes Matrix with axis angle + LLMatrix3(const F32 roll, const F32 pitch, const F32 yaw); // Initializes Matrix with Euler angles + + ////////////////////////////// + // + // Matrix initializers - these replace any existing values in the matrix + // + + // various useful matrix functions + const LLMatrix3& identity(); // Load identity matrix + const LLMatrix3& zero(); // Clears Matrix to zero + + /////////////////////////// + // + // Matrix setters - set some properties without modifying others + // + + // These functions take Rotation arguments + const LLMatrix3& setRot(const F32 angle, const F32 x, const F32 y, const F32 z); // Calculate rotation matrix for rotating angle radians about (x, y, z) + const LLMatrix3& setRot(const F32 angle, const LLVector3 &vec); // Calculate rotation matrix for rotating angle radians about vec + const LLMatrix3& setRot(const F32 roll, const F32 pitch, const F32 yaw); // Calculate rotation matrix from Euler angles + const LLMatrix3& setRot(const LLQuaternion &q); // Transform matrix by Euler angles and translating by pos + + const LLMatrix3& setRows(const LLVector3 &x_axis, const LLVector3 &y_axis, const LLVector3 &z_axis); + + /////////////////////////// + // + // Get properties of a matrix + // + LLQuaternion quaternion() const; // Returns quaternion from mat + void getEulerAngles(F32 *roll, F32 *pitch, F32 *yaw) const; // Returns Euler angles, in radians + + // Axis extraction routines + LLVector3 getFwdRow() const; + LLVector3 getLeftRow() const; + LLVector3 getUpRow() const; + F32 determinant() const; // Return determinant + + + /////////////////////////// + // + // Operations on an existing matrix + // + const LLMatrix3& transpose(); // Transpose MAT4 + const LLMatrix3& invert(); // Invert MAT4 + const LLMatrix3& orthogonalize(); // Orthogonalizes X, then Y, then Z + const LLMatrix3& adjointTranspose(); // returns transpose of matrix adjoint, for multiplying normals + + + // Rotate existing matrix + // Note: the two lines below are equivalent: + // foo.rotate(bar) + // foo = foo * bar + // That is, foo.rotMat3(bar) multiplies foo by bar FROM THE RIGHT + const LLMatrix3& rotate(const F32 angle, const F32 x, const F32 y, const F32 z); // Rotate matrix by rotating angle radians about (x, y, z) + const LLMatrix3& rotate(const F32 angle, const LLVector3 &vec); // Rotate matrix by rotating angle radians about vec + const LLMatrix3& rotate(const F32 roll, const F32 pitch, const F32 yaw); // Rotate matrix by roll (about x), pitch (about y), and yaw (about z) + const LLMatrix3& rotate(const LLQuaternion &q); // Transform matrix by Euler angles and translating by pos + +// This operator is misleading as to operation direction +// friend LLVector3 operator*(const LLMatrix3 &a, const LLVector3 &b); // Apply rotation a to vector b + + friend LLVector3 operator*(const LLVector3 &a, const LLMatrix3 &b); // Apply rotation b to vector a + friend LLVector3d operator*(const LLVector3d &a, const LLMatrix3 &b); // Apply rotation b to vector a + friend LLMatrix3 operator*(const LLMatrix3 &a, const LLMatrix3 &b); // Return a * b + + friend bool operator==(const LLMatrix3 &a, const LLMatrix3 &b); // Return a == b + friend bool operator!=(const LLMatrix3 &a, const LLMatrix3 &b); // Return a != b + + friend const LLMatrix3& operator*=(LLMatrix3 &a, const LLMatrix3 &b); // Return a * b + + friend std::ostream& operator<<(std::ostream& s, const LLMatrix3 &a); // Stream a +} +#if LL_DARWIN +__attribute__ ((aligned (16))) +#endif +; + +inline LLMatrix3::LLMatrix3(void) +{ + mMatrix[0][0] = 1.f; + mMatrix[0][1] = 0.f; + mMatrix[0][2] = 0.f; + + mMatrix[1][0] = 0.f; + mMatrix[1][1] = 1.f; + mMatrix[1][2] = 0.f; + + mMatrix[2][0] = 0.f; + mMatrix[2][1] = 0.f; + mMatrix[2][2] = 1.f; +} + +inline LLMatrix3::LLMatrix3(const F32 *mat) +{ + mMatrix[0][0] = mat[0]; + mMatrix[0][1] = mat[1]; + mMatrix[0][2] = mat[2]; + + mMatrix[1][0] = mat[3]; + mMatrix[1][1] = mat[4]; + mMatrix[1][2] = mat[5]; + + mMatrix[2][0] = mat[6]; + mMatrix[2][1] = mat[7]; + mMatrix[2][2] = mat[8]; +} + + +#endif + + +// Rotation matrix hints... + +// Inverse of Rotation Matrices +// ---------------------------- +// If R is a rotation matrix that rotate vectors from Frame-A to Frame-B, +// then the transpose of R will rotate vectors from Frame-B to Frame-A. + + +// Creating Rotation Matricies From Object Axes +// -------------------------------------------- +// Suppose you know the three axes of some object in some "absolute-frame". +// If you take those three vectors and throw them into the rows of +// a rotation matrix what do you get? +// +// R = | X0 X1 X2 | +// | Y0 Y1 Y2 | +// | Z0 Z1 Z2 | +// +// Yeah, but what does it mean? +// +// Transpose the matrix and have it operate on a vector... +// +// V * R_transpose = [ V0 V1 V2 ] * | X0 Y0 Z0 | +// | X1 Y1 Z1 | +// | X2 Y2 Z2 | +// +// = [ V*X V*Y V*Z ] +// +// = components of V that are parallel to the three object axes +// +// = transformation of V into object frame +// +// Since the transformation of a rotation matrix is its inverse, then +// R must rotate vectors from the object-frame into the absolute-frame. + + + diff --git a/indra/llmath/m4math.cpp b/indra/llmath/m4math.cpp new file mode 100644 index 0000000000..969c3663e6 --- /dev/null +++ b/indra/llmath/m4math.cpp @@ -0,0 +1,794 @@ +/** + * @file m4math.cpp + * @brief LLMatrix4 class implementation. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +//#include "vmath.h" +#include "v3math.h" +#include "v4math.h" +#include "m4math.h" +#include "m3math.h" +#include "llquaternion.h" + + + + +// LLMatrix4 + +// Constructors + + +LLMatrix4::LLMatrix4(const F32 *mat) +{ + mMatrix[0][0] = mat[0]; + mMatrix[0][1] = mat[1]; + mMatrix[0][2] = mat[2]; + mMatrix[0][3] = mat[3]; + + mMatrix[1][0] = mat[4]; + mMatrix[1][1] = mat[5]; + mMatrix[1][2] = mat[6]; + mMatrix[1][3] = mat[7]; + + mMatrix[2][0] = mat[8]; + mMatrix[2][1] = mat[9]; + mMatrix[2][2] = mat[10]; + mMatrix[2][3] = mat[11]; + + mMatrix[3][0] = mat[12]; + mMatrix[3][1] = mat[13]; + mMatrix[3][2] = mat[14]; + mMatrix[3][3] = mat[15]; +} + +LLMatrix4::LLMatrix4(const LLMatrix3 &mat, const LLVector4 &vec) +{ + mMatrix[0][0] = mat.mMatrix[0][0]; + mMatrix[0][1] = mat.mMatrix[0][1]; + mMatrix[0][2] = mat.mMatrix[0][2]; + mMatrix[0][3] = 0.f; + + mMatrix[1][0] = mat.mMatrix[1][0]; + mMatrix[1][1] = mat.mMatrix[1][1]; + mMatrix[1][2] = mat.mMatrix[1][2]; + mMatrix[1][3] = 0.f; + + mMatrix[2][0] = mat.mMatrix[2][0]; + mMatrix[2][1] = mat.mMatrix[2][1]; + mMatrix[2][2] = mat.mMatrix[2][2]; + mMatrix[2][3] = 0.f; + + mMatrix[3][0] = vec.mV[0]; + mMatrix[3][1] = vec.mV[1]; + mMatrix[3][2] = vec.mV[2]; + mMatrix[3][3] = 1.f; +} + +LLMatrix4::LLMatrix4(const LLMatrix3 &mat) +{ + mMatrix[0][0] = mat.mMatrix[0][0]; + mMatrix[0][1] = mat.mMatrix[0][1]; + mMatrix[0][2] = mat.mMatrix[0][2]; + mMatrix[0][3] = 0.f; + + mMatrix[1][0] = mat.mMatrix[1][0]; + mMatrix[1][1] = mat.mMatrix[1][1]; + mMatrix[1][2] = mat.mMatrix[1][2]; + mMatrix[1][3] = 0.f; + + mMatrix[2][0] = mat.mMatrix[2][0]; + mMatrix[2][1] = mat.mMatrix[2][1]; + mMatrix[2][2] = mat.mMatrix[2][2]; + mMatrix[2][3] = 0.f; + + mMatrix[3][0] = 0.f; + mMatrix[3][1] = 0.f; + mMatrix[3][2] = 0.f; + mMatrix[3][3] = 1.f; +} + +LLMatrix4::LLMatrix4(const LLQuaternion &q) +{ + *this = initRotation(q); +} + +LLMatrix4::LLMatrix4(const LLQuaternion &q, const LLVector4 &pos) +{ + *this = initRotTrans(q, pos); +} + +LLMatrix4::LLMatrix4(const F32 angle, const LLVector4 &vec, const LLVector4 &pos) +{ + initRotTrans(LLQuaternion(angle, vec), pos); +} + +LLMatrix4::LLMatrix4(const F32 angle, const LLVector4 &vec) +{ + initRotation(LLQuaternion(angle, vec)); + + mMatrix[3][0] = 0.f; + mMatrix[3][1] = 0.f; + mMatrix[3][2] = 0.f; + mMatrix[3][3] = 1.f; +} + +LLMatrix4::LLMatrix4(const F32 roll, const F32 pitch, const F32 yaw, const LLVector4 &pos) +{ + LLMatrix3 mat(roll, pitch, yaw); + initRotTrans(LLQuaternion(mat), pos); +} + +LLMatrix4::LLMatrix4(const F32 roll, const F32 pitch, const F32 yaw) +{ + LLMatrix3 mat(roll, pitch, yaw); + initRotation(LLQuaternion(mat)); + + mMatrix[3][0] = 0.f; + mMatrix[3][1] = 0.f; + mMatrix[3][2] = 0.f; + mMatrix[3][3] = 1.f; +} + +LLMatrix4::~LLMatrix4(void) +{ +} + +// Clear and Assignment Functions + +const LLMatrix4& LLMatrix4::zero() +{ + mMatrix[0][0] = 0.f; + mMatrix[0][1] = 0.f; + mMatrix[0][2] = 0.f; + mMatrix[0][3] = 0.f; + + mMatrix[1][0] = 0.f; + mMatrix[1][1] = 0.f; + mMatrix[1][2] = 0.f; + mMatrix[1][3] = 0.f; + + mMatrix[2][0] = 0.f; + mMatrix[2][1] = 0.f; + mMatrix[2][2] = 0.f; + mMatrix[2][3] = 0.f; + + mMatrix[3][0] = 0.f; + mMatrix[3][1] = 0.f; + mMatrix[3][2] = 0.f; + mMatrix[3][3] = 0.f; + return *this; +} + + +// various useful mMatrix functions + +const LLMatrix4& LLMatrix4::transpose() +{ + LLMatrix4 mat; + mat.mMatrix[0][0] = mMatrix[0][0]; + mat.mMatrix[1][0] = mMatrix[0][1]; + mat.mMatrix[2][0] = mMatrix[0][2]; + mat.mMatrix[3][0] = mMatrix[0][3]; + + mat.mMatrix[0][1] = mMatrix[1][0]; + mat.mMatrix[1][1] = mMatrix[1][1]; + mat.mMatrix[2][1] = mMatrix[1][2]; + mat.mMatrix[3][1] = mMatrix[1][3]; + + mat.mMatrix[0][2] = mMatrix[2][0]; + mat.mMatrix[1][2] = mMatrix[2][1]; + mat.mMatrix[2][2] = mMatrix[2][2]; + mat.mMatrix[3][2] = mMatrix[2][3]; + + mat.mMatrix[0][3] = mMatrix[3][0]; + mat.mMatrix[1][3] = mMatrix[3][1]; + mat.mMatrix[2][3] = mMatrix[3][2]; + mat.mMatrix[3][3] = mMatrix[3][3]; + + *this = mat; + return *this; +} + + +F32 LLMatrix4::determinant() const +{ + llerrs << "Not implemented!" << llendl; + return 0.f; +} + +// Only works for pure orthonormal, homogeneous transform matrices. +const LLMatrix4& LLMatrix4::invert(void) +{ + // transpose the rotation part + F32 temp; + temp = mMatrix[VX][VY]; mMatrix[VX][VY] = mMatrix[VY][VX]; mMatrix[VY][VX] = temp; + temp = mMatrix[VX][VZ]; mMatrix[VX][VZ] = mMatrix[VZ][VX]; mMatrix[VZ][VX] = temp; + temp = mMatrix[VY][VZ]; mMatrix[VY][VZ] = mMatrix[VZ][VY]; mMatrix[VZ][VY] = temp; + + // rotate the translation part by the new rotation + // (temporarily store in empty column of matrix) + U32 j; + for (j=0; j<3; j++) + { + mMatrix[j][VW] = mMatrix[VW][VX] * mMatrix[VX][j] + + mMatrix[VW][VY] * mMatrix[VY][j] + + mMatrix[VW][VZ] * mMatrix[VZ][j]; + } + + // negate and copy the temporary vector back to the tranlation row + mMatrix[VW][VX] = -mMatrix[VX][VW]; + mMatrix[VW][VY] = -mMatrix[VY][VW]; + mMatrix[VW][VZ] = -mMatrix[VZ][VW]; + + // zero the empty column again + mMatrix[VX][VW] = mMatrix[VY][VW] = mMatrix[VZ][VW] = 0.0f; + + return *this; +} + +LLVector4 LLMatrix4::getFwdRow4() const +{ + return LLVector4(mMatrix[VX][VX], mMatrix[VX][VY], mMatrix[VX][VZ], mMatrix[VX][VW]); +} + +LLVector4 LLMatrix4::getLeftRow4() const +{ + return LLVector4(mMatrix[VY][VX], mMatrix[VY][VY], mMatrix[VY][VZ], mMatrix[VY][VW]); +} + +LLVector4 LLMatrix4::getUpRow4() const +{ + return LLVector4(mMatrix[VZ][VX], mMatrix[VZ][VY], mMatrix[VZ][VZ], mMatrix[VZ][VW]); +} + +// SJB: This code is correct for a logicly stored (non-transposed) matrix; +// Our matrices are stored transposed, OpenGL style, so this generates the +// INVERSE quaternion (-x, -y, -z, w)! +// Because we use similar logic in LLQuaternion::getMatrix3, +// we are internally consistant so everything works OK :) +LLQuaternion LLMatrix4::quaternion() const +{ + LLQuaternion quat; + F32 tr, s, q[4]; + U32 i, j, k; + U32 nxt[3] = {1, 2, 0}; + + tr = mMatrix[0][0] + mMatrix[1][1] + mMatrix[2][2]; + + // check the diagonal + if (tr > 0.f) + { + s = (F32)sqrt (tr + 1.f); + quat.mQ[VS] = s / 2.f; + s = 0.5f / s; + quat.mQ[VX] = (mMatrix[1][2] - mMatrix[2][1]) * s; + quat.mQ[VY] = (mMatrix[2][0] - mMatrix[0][2]) * s; + quat.mQ[VZ] = (mMatrix[0][1] - mMatrix[1][0]) * s; + } + else + { + // diagonal is negative + i = 0; + if (mMatrix[1][1] > mMatrix[0][0]) + i = 1; + if (mMatrix[2][2] > mMatrix[i][i]) + i = 2; + + j = nxt[i]; + k = nxt[j]; + + + s = (F32)sqrt ((mMatrix[i][i] - (mMatrix[j][j] + mMatrix[k][k])) + 1.f); + + q[i] = s * 0.5f; + + if (s != 0.f) + s = 0.5f / s; + + q[3] = (mMatrix[j][k] - mMatrix[k][j]) * s; + q[j] = (mMatrix[i][j] + mMatrix[j][i]) * s; + q[k] = (mMatrix[i][k] + mMatrix[k][i]) * s; + + quat.setQuat(q); + } + return quat; +} + + + +void LLMatrix4::initRows(const LLVector4 &row0, + const LLVector4 &row1, + const LLVector4 &row2, + const LLVector4 &row3) +{ + mMatrix[0][0] = row0.mV[0]; + mMatrix[0][1] = row0.mV[1]; + mMatrix[0][2] = row0.mV[2]; + mMatrix[0][3] = row0.mV[3]; + + mMatrix[1][0] = row1.mV[0]; + mMatrix[1][1] = row1.mV[1]; + mMatrix[1][2] = row1.mV[2]; + mMatrix[1][3] = row1.mV[3]; + + mMatrix[2][0] = row2.mV[0]; + mMatrix[2][1] = row2.mV[1]; + mMatrix[2][2] = row2.mV[2]; + mMatrix[2][3] = row2.mV[3]; + + mMatrix[3][0] = row3.mV[0]; + mMatrix[3][1] = row3.mV[1]; + mMatrix[3][2] = row3.mV[2]; + mMatrix[3][3] = row3.mV[3]; +} + + +const LLMatrix4& LLMatrix4::initRotation(const F32 angle, const F32 x, const F32 y, const F32 z) +{ + LLMatrix3 mat(angle, x, y, z); + return initMatrix(mat); +} + + +const LLMatrix4& LLMatrix4::initRotation(F32 angle, const LLVector4 &vec) +{ + LLMatrix3 mat(angle, vec); + return initMatrix(mat); +} + + +const LLMatrix4& LLMatrix4::initRotation(const F32 roll, const F32 pitch, const F32 yaw) +{ + LLMatrix3 mat(roll, pitch, yaw); + return initMatrix(mat); +} + + +const LLMatrix4& LLMatrix4::initRotation(const LLQuaternion &q) +{ + LLMatrix3 mat(q); + return initMatrix(mat); +} + + +// Position and Rotation +const LLMatrix4& LLMatrix4::initRotTrans(const F32 angle, const F32 rx, const F32 ry, const F32 rz, + const F32 tx, const F32 ty, const F32 tz) +{ + LLMatrix3 mat(angle, rx, ry, rz); + LLVector3 translation(tx, ty, tz); + initMatrix(mat); + setTranslation(translation); + return (*this); +} + +const LLMatrix4& LLMatrix4::initRotTrans(const F32 angle, const LLVector3 &axis, const LLVector3&translation) +{ + LLMatrix3 mat(angle, axis); + initMatrix(mat); + setTranslation(translation); + return (*this); +} + +const LLMatrix4& LLMatrix4::initRotTrans(const F32 roll, const F32 pitch, const F32 yaw, const LLVector4 &translation) +{ + LLMatrix3 mat(roll, pitch, yaw); + initMatrix(mat); + setTranslation(translation); + return (*this); +} + +/* +const LLMatrix4& LLMatrix4::initRotTrans(const LLVector4 &fwd, + const LLVector4 &left, + const LLVector4 &up, + const LLVector4 &translation) +{ + LLMatrix3 mat(fwd, left, up); + initMatrix(mat); + setTranslation(translation); + return (*this); +} +*/ + +const LLMatrix4& LLMatrix4::initRotTrans(const LLQuaternion &q, const LLVector4 &translation) +{ + LLMatrix3 mat(q); + initMatrix(mat); + setTranslation(translation); + return (*this); +} + +const LLMatrix4& LLMatrix4::initAll(const LLVector3 &scale, const LLQuaternion &q, const LLVector3 &pos) +{ + F32 sx, sy, sz; + F32 xx, xy, xz, xw, yy, yz, yw, zz, zw; + + sx = scale.mV[0]; + sy = scale.mV[1]; + sz = scale.mV[2]; + + xx = q.mQ[VX] * q.mQ[VX]; + xy = q.mQ[VX] * q.mQ[VY]; + xz = q.mQ[VX] * q.mQ[VZ]; + xw = q.mQ[VX] * q.mQ[VW]; + + yy = q.mQ[VY] * q.mQ[VY]; + yz = q.mQ[VY] * q.mQ[VZ]; + yw = q.mQ[VY] * q.mQ[VW]; + + zz = q.mQ[VZ] * q.mQ[VZ]; + zw = q.mQ[VZ] * q.mQ[VW]; + + mMatrix[0][0] = (1.f - 2.f * ( yy + zz )) *sx; + mMatrix[0][1] = ( 2.f * ( xy + zw )) *sx; + mMatrix[0][2] = ( 2.f * ( xz - yw )) *sx; + + mMatrix[1][0] = ( 2.f * ( xy - zw )) *sy; + mMatrix[1][1] = (1.f - 2.f * ( xx + zz )) *sy; + mMatrix[1][2] = ( 2.f * ( yz + xw )) *sy; + + mMatrix[2][0] = ( 2.f * ( xz + yw )) *sz; + mMatrix[2][1] = ( 2.f * ( yz - xw )) *sz; + mMatrix[2][2] = (1.f - 2.f * ( xx + yy )) *sz; + + mMatrix[3][0] = pos.mV[0]; + mMatrix[3][1] = pos.mV[1]; + mMatrix[3][2] = pos.mV[2]; + mMatrix[3][3] = 1.0; + + // TODO -- should we set the translation portion to zero? + return (*this); +} + +// Rotate exisitng mMatrix +const LLMatrix4& LLMatrix4::rotate(const F32 angle, const F32 x, const F32 y, const F32 z) +{ + LLVector4 vec4(x, y, z); + LLMatrix4 mat(angle, vec4); + *this *= mat; + return *this; +} + +const LLMatrix4& LLMatrix4::rotate(const F32 angle, const LLVector4 &vec) +{ + LLMatrix4 mat(angle, vec); + *this *= mat; + return *this; +} + +const LLMatrix4& LLMatrix4::rotate(const F32 roll, const F32 pitch, const F32 yaw) +{ + LLMatrix4 mat(roll, pitch, yaw); + *this *= mat; + return *this; +} + +const LLMatrix4& LLMatrix4::rotate(const LLQuaternion &q) +{ + LLMatrix4 mat(q); + *this *= mat; + return *this; +} + + +const LLMatrix4& LLMatrix4::translate(const LLVector3 &vec) +{ + mMatrix[3][0] += vec.mV[0]; + mMatrix[3][1] += vec.mV[1]; + mMatrix[3][2] += vec.mV[2]; + return (*this); +} + + +void LLMatrix4::setFwdRow(const LLVector3 &row) +{ + mMatrix[VX][VX] = row.mV[VX]; + mMatrix[VX][VY] = row.mV[VY]; + mMatrix[VX][VZ] = row.mV[VZ]; +} + +void LLMatrix4::setLeftRow(const LLVector3 &row) +{ + mMatrix[VY][VX] = row.mV[VX]; + mMatrix[VY][VY] = row.mV[VY]; + mMatrix[VY][VZ] = row.mV[VZ]; +} + +void LLMatrix4::setUpRow(const LLVector3 &row) +{ + mMatrix[VZ][VX] = row.mV[VX]; + mMatrix[VZ][VY] = row.mV[VY]; + mMatrix[VZ][VZ] = row.mV[VZ]; +} + + +void LLMatrix4::setFwdCol(const LLVector3 &col) +{ + mMatrix[VX][VX] = col.mV[VX]; + mMatrix[VY][VX] = col.mV[VY]; + mMatrix[VZ][VX] = col.mV[VZ]; +} + +void LLMatrix4::setLeftCol(const LLVector3 &col) +{ + mMatrix[VX][VY] = col.mV[VX]; + mMatrix[VY][VY] = col.mV[VY]; + mMatrix[VZ][VY] = col.mV[VZ]; +} + +void LLMatrix4::setUpCol(const LLVector3 &col) +{ + mMatrix[VX][VZ] = col.mV[VX]; + mMatrix[VY][VZ] = col.mV[VY]; + mMatrix[VZ][VZ] = col.mV[VZ]; +} + + +const LLMatrix4& LLMatrix4::setTranslation(const F32 tx, const F32 ty, const F32 tz) +{ + mMatrix[VW][VX] = tx; + mMatrix[VW][VY] = ty; + mMatrix[VW][VZ] = tz; + return (*this); +} + +const LLMatrix4& LLMatrix4::setTranslation(const LLVector3 &translation) +{ + mMatrix[VW][VX] = translation.mV[VX]; + mMatrix[VW][VY] = translation.mV[VY]; + mMatrix[VW][VZ] = translation.mV[VZ]; + return (*this); +} + +const LLMatrix4& LLMatrix4::setTranslation(const LLVector4 &translation) +{ + mMatrix[VW][VX] = translation.mV[VX]; + mMatrix[VW][VY] = translation.mV[VY]; + mMatrix[VW][VZ] = translation.mV[VZ]; + return (*this); +} + +// LLMatrix3 Extraction and Setting +LLMatrix3 LLMatrix4::getMat3() const +{ + LLMatrix3 retmat; + + retmat.mMatrix[0][0] = mMatrix[0][0]; + retmat.mMatrix[0][1] = mMatrix[0][1]; + retmat.mMatrix[0][2] = mMatrix[0][2]; + + retmat.mMatrix[1][0] = mMatrix[1][0]; + retmat.mMatrix[1][1] = mMatrix[1][1]; + retmat.mMatrix[1][2] = mMatrix[1][2]; + + retmat.mMatrix[2][0] = mMatrix[2][0]; + retmat.mMatrix[2][1] = mMatrix[2][1]; + retmat.mMatrix[2][2] = mMatrix[2][2]; + + return retmat; +} + +const LLMatrix4& LLMatrix4::initMatrix(const LLMatrix3 &mat) +{ + mMatrix[0][0] = mat.mMatrix[0][0]; + mMatrix[0][1] = mat.mMatrix[0][1]; + mMatrix[0][2] = mat.mMatrix[0][2]; + mMatrix[0][3] = 0.f; + + mMatrix[1][0] = mat.mMatrix[1][0]; + mMatrix[1][1] = mat.mMatrix[1][1]; + mMatrix[1][2] = mat.mMatrix[1][2]; + mMatrix[1][3] = 0.f; + + mMatrix[2][0] = mat.mMatrix[2][0]; + mMatrix[2][1] = mat.mMatrix[2][1]; + mMatrix[2][2] = mat.mMatrix[2][2]; + mMatrix[2][3] = 0.f; + + mMatrix[3][0] = 0.f; + mMatrix[3][1] = 0.f; + mMatrix[3][2] = 0.f; + mMatrix[3][3] = 1.f; + return (*this); +} + +const LLMatrix4& LLMatrix4::initMatrix(const LLMatrix3 &mat, const LLVector4 &translation) +{ + mMatrix[0][0] = mat.mMatrix[0][0]; + mMatrix[0][1] = mat.mMatrix[0][1]; + mMatrix[0][2] = mat.mMatrix[0][2]; + mMatrix[0][3] = 0.f; + + mMatrix[1][0] = mat.mMatrix[1][0]; + mMatrix[1][1] = mat.mMatrix[1][1]; + mMatrix[1][2] = mat.mMatrix[1][2]; + mMatrix[1][3] = 0.f; + + mMatrix[2][0] = mat.mMatrix[2][0]; + mMatrix[2][1] = mat.mMatrix[2][1]; + mMatrix[2][2] = mat.mMatrix[2][2]; + mMatrix[2][3] = 0.f; + + mMatrix[3][0] = translation.mV[0]; + mMatrix[3][1] = translation.mV[1]; + mMatrix[3][2] = translation.mV[2]; + mMatrix[3][3] = 1.f; + return (*this); +} + +// LLMatrix4 Operators + + +/* Not implemented to help enforce code consistency with the syntax of + row-major notation. This is a Good Thing. +LLVector4 operator*(const LLMatrix4 &a, const LLVector4 &b) +{ + // Operate "to the right" on column-vector b + LLVector4 vec; + vec.mV[VX] = a.mMatrix[VX][VX] * b.mV[VX] + + a.mMatrix[VY][VX] * b.mV[VY] + + a.mMatrix[VZ][VX] * b.mV[VZ] + + a.mMatrix[VW][VX] * b.mV[VW]; + + vec.mV[VY] = a.mMatrix[VX][VY] * b.mV[VX] + + a.mMatrix[VY][VY] * b.mV[VY] + + a.mMatrix[VZ][VY] * b.mV[VZ] + + a.mMatrix[VW][VY] * b.mV[VW]; + + vec.mV[VZ] = a.mMatrix[VX][VZ] * b.mV[VX] + + a.mMatrix[VY][VZ] * b.mV[VY] + + a.mMatrix[VZ][VZ] * b.mV[VZ] + + a.mMatrix[VW][VZ] * b.mV[VW]; + + vec.mV[VW] = a.mMatrix[VX][VW] * b.mV[VX] + + a.mMatrix[VY][VW] * b.mV[VY] + + a.mMatrix[VZ][VW] * b.mV[VZ] + + a.mMatrix[VW][VW] * b.mV[VW]; + return vec; +} +*/ + + +LLVector4 operator*(const LLVector4 &a, const LLMatrix4 &b) +{ + // Operate "to the left" on row-vector a + LLVector4 vec; + vec.mV[VX] = a.mV[VX] * b.mMatrix[VX][VX] + + a.mV[VY] * b.mMatrix[VY][VX] + + a.mV[VZ] * b.mMatrix[VZ][VX] + + a.mV[VW] * b.mMatrix[VW][VX]; + + vec.mV[VY] = a.mV[VX] * b.mMatrix[VX][VY] + + a.mV[VY] * b.mMatrix[VY][VY] + + a.mV[VZ] * b.mMatrix[VZ][VY] + + a.mV[VW] * b.mMatrix[VW][VY]; + + vec.mV[VZ] = a.mV[VX] * b.mMatrix[VX][VZ] + + a.mV[VY] * b.mMatrix[VY][VZ] + + a.mV[VZ] * b.mMatrix[VZ][VZ] + + a.mV[VW] * b.mMatrix[VW][VZ]; + + vec.mV[VW] = a.mV[VX] * b.mMatrix[VX][VW] + + a.mV[VY] * b.mMatrix[VY][VW] + + a.mV[VZ] * b.mMatrix[VZ][VW] + + a.mV[VW] * b.mMatrix[VW][VW]; + return vec; +} + +LLVector4 rotate_vector(const LLVector4 &a, const LLMatrix4 &b) +{ + // Rotates but does not translate + // Operate "to the left" on row-vector a + LLVector4 vec; + vec.mV[VX] = a.mV[VX] * b.mMatrix[VX][VX] + + a.mV[VY] * b.mMatrix[VY][VX] + + a.mV[VZ] * b.mMatrix[VZ][VX]; + + vec.mV[VY] = a.mV[VX] * b.mMatrix[VX][VY] + + a.mV[VY] * b.mMatrix[VY][VY] + + a.mV[VZ] * b.mMatrix[VZ][VY]; + + vec.mV[VZ] = a.mV[VX] * b.mMatrix[VX][VZ] + + a.mV[VY] * b.mMatrix[VY][VZ] + + a.mV[VZ] * b.mMatrix[VZ][VZ]; + +// vec.mV[VW] = a.mV[VX] * b.mMatrix[VX][VW] + +// a.mV[VY] * b.mMatrix[VY][VW] + +// a.mV[VZ] * b.mMatrix[VZ][VW] + + vec.mV[VW] = a.mV[VW]; + return vec; +} + +LLVector3 rotate_vector(const LLVector3 &a, const LLMatrix4 &b) +{ + // Rotates but does not translate + // Operate "to the left" on row-vector a + LLVector3 vec; + vec.mV[VX] = a.mV[VX] * b.mMatrix[VX][VX] + + a.mV[VY] * b.mMatrix[VY][VX] + + a.mV[VZ] * b.mMatrix[VZ][VX]; + + vec.mV[VY] = a.mV[VX] * b.mMatrix[VX][VY] + + a.mV[VY] * b.mMatrix[VY][VY] + + a.mV[VZ] * b.mMatrix[VZ][VY]; + + vec.mV[VZ] = a.mV[VX] * b.mMatrix[VX][VZ] + + a.mV[VY] * b.mMatrix[VY][VZ] + + a.mV[VZ] * b.mMatrix[VZ][VZ]; + return vec; +} + +bool operator==(const LLMatrix4 &a, const LLMatrix4 &b) +{ + U32 i, j; + for (i = 0; i < NUM_VALUES_IN_MAT4; i++) + { + for (j = 0; j < NUM_VALUES_IN_MAT4; j++) + { + if (a.mMatrix[j][i] != b.mMatrix[j][i]) + return FALSE; + } + } + return TRUE; +} + +bool operator!=(const LLMatrix4 &a, const LLMatrix4 &b) +{ + U32 i, j; + for (i = 0; i < NUM_VALUES_IN_MAT4; i++) + { + for (j = 0; j < NUM_VALUES_IN_MAT4; j++) + { + if (a.mMatrix[j][i] != b.mMatrix[j][i]) + return TRUE; + } + } + return FALSE; +} + +const LLMatrix4& operator*=(LLMatrix4 &a, F32 k) +{ + U32 i, j; + for (i = 0; i < NUM_VALUES_IN_MAT4; i++) + { + for (j = 0; j < NUM_VALUES_IN_MAT4; j++) + { + a.mMatrix[j][i] *= k; + } + } + return a; +} + +std::ostream& operator<<(std::ostream& s, const LLMatrix4 &a) +{ + s << "{ " + << a.mMatrix[VX][VX] << ", " + << a.mMatrix[VX][VY] << ", " + << a.mMatrix[VX][VZ] << ", " + << a.mMatrix[VX][VW] + << "; " + << a.mMatrix[VY][VX] << ", " + << a.mMatrix[VY][VY] << ", " + << a.mMatrix[VY][VZ] << ", " + << a.mMatrix[VY][VW] + << "; " + << a.mMatrix[VZ][VX] << ", " + << a.mMatrix[VZ][VY] << ", " + << a.mMatrix[VZ][VZ] << ", " + << a.mMatrix[VZ][VW] + << "; " + << a.mMatrix[VW][VX] << ", " + << a.mMatrix[VW][VY] << ", " + << a.mMatrix[VW][VZ] << ", " + << a.mMatrix[VW][VW] + << " }"; + return s; +} + + diff --git a/indra/llmath/m4math.h b/indra/llmath/m4math.h new file mode 100644 index 0000000000..4f26d875ff --- /dev/null +++ b/indra/llmath/m4math.h @@ -0,0 +1,365 @@ +/** + * @file m4math.h + * @brief LLMatrix3 class header file. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_M4MATH_H +#define LL_M4MATH_H + +#include "v3math.h" + +class LLVector4; +class LLMatrix3; +class LLQuaternion; + +// NOTA BENE: Currently assuming a right-handed, x-forward, y-left, z-up universe + +// Us versus OpenGL: + +// Even though OpenGL uses column vectors and we use row vectors, we can plug our matrices +// directly into OpenGL. This is because OpenGL numbers its matrices going columnwise: +// +// OpenGL indexing: Our indexing: +// 0 4 8 12 [0][0] [0][1] [0][2] [0][3] +// 1 5 9 13 [1][0] [1][1] [1][2] [1][3] +// 2 6 10 14 [2][0] [2][1] [2][2] [2][3] +// 3 7 11 15 [3][0] [3][1] [3][2] [3][3] +// +// So when you're looking at OpenGL related matrices online, our matrices will be +// "transposed". But our matrices can be plugged directly into OpenGL and work fine! +// + +// We're using row vectors - [vx, vy, vz, vw] +// +// There are several different ways of thinking of matrices, if you mix them up, you'll get very confused. +// +// One way to think about it is a matrix that takes the origin frame A +// and rotates it into B': i.e. A*M = B +// +// Vectors: +// f - forward axis of B expressed in A +// l - left axis of B expressed in A +// u - up axis of B expressed in A +// +// | 0: fx 1: fy 2: fz 3:0 | +// M = | 4: lx 5: ly 6: lz 7:0 | +// | 8: ux 9: uy 10: uz 11:0 | +// | 12: 0 13: 0 14: 0 15:1 | +// +// +// +// +// Another way to think of matrices is matrix that takes a point p in frame A, and puts it into frame B: +// This is used most commonly for the modelview matrix. +// +// so p*M = p' +// +// Vectors: +// f - forward of frame B in frame A +// l - left of frame B in frame A +// u - up of frame B in frame A +// o - origin of frame frame B in frame A +// +// | 0: fx 1: lx 2: ux 3:0 | +// M = | 4: fy 5: ly 6: uy 7:0 | +// | 8: fz 9: lz 10: uz 11:0 | +// | 12:-of 13:-ol 14:-ou 15:1 | +// +// of, ol, and ou mean the component of the "global" origin o in the f axis, l axis, and u axis. +// + +static const U32 NUM_VALUES_IN_MAT4 = 4; + +#if LL_WINDOWS +__declspec(align(16)) +#endif +class LLMatrix4 +{ +public: + F32 mMatrix[NUM_VALUES_IN_MAT4][NUM_VALUES_IN_MAT4]; + + LLMatrix4(); // Initializes Matrix to identity matrix + explicit LLMatrix4(const F32 *mat); // Initializes Matrix to values in mat + explicit LLMatrix4(const LLMatrix3 &mat); // Initializes Matrix to valuee in mat and sets position to (0,0,0) + explicit LLMatrix4(const LLQuaternion &q); // Initializes Matrix with rotation q and sets position to (0,0,0) + + LLMatrix4(const LLMatrix3 &mat, const LLVector4 &pos); // Initializes Matrix to values in mat and pos + + // These are really, really, inefficient as implemented! - djs + LLMatrix4(const LLQuaternion &q, const LLVector4 &pos); // Initializes Matrix with rotation q and position pos + LLMatrix4(F32 angle, + const LLVector4 &vec, + const LLVector4 &pos); // Initializes Matrix with axis-angle and position + LLMatrix4(F32 angle, const LLVector4 &vec); // Initializes Matrix with axis-angle and sets position to (0,0,0) + LLMatrix4(const F32 roll, const F32 pitch, const F32 yaw, + const LLVector4 &pos); // Initializes Matrix with Euler angles + LLMatrix4(const F32 roll, const F32 pitch, const F32 yaw); // Initializes Matrix with Euler angles + + ~LLMatrix4(void); // Destructor + + + ////////////////////////////// + // + // Matrix initializers - these replace any existing values in the matrix + // + + void initRows(const LLVector4 &row0, + const LLVector4 &row1, + const LLVector4 &row2, + const LLVector4 &row3); + + // various useful matrix functions + const LLMatrix4& identity(); // Load identity matrix + const LLMatrix4& zero(); // Clears matrix to all zeros. + + const LLMatrix4& initRotation(const F32 angle, const F32 x, const F32 y, const F32 z); // Calculate rotation matrix by rotating angle radians about (x, y, z) + const LLMatrix4& initRotation(const F32 angle, const LLVector4 &axis); // Calculate rotation matrix for rotating angle radians about vec + const LLMatrix4& initRotation(const F32 roll, const F32 pitch, const F32 yaw); // Calculate rotation matrix from Euler angles + const LLMatrix4& initRotation(const LLQuaternion &q); // Set with Quaternion and position + + // Position Only + const LLMatrix4& initMatrix(const LLMatrix3 &mat); // + const LLMatrix4& initMatrix(const LLMatrix3 &mat, const LLVector4 &translation); + + // These operation create a matrix that will rotate and translate by the + // specified amounts. + const LLMatrix4& initRotTrans(const F32 angle, + const F32 rx, const F32 ry, const F32 rz, + const F32 px, const F32 py, const F32 pz); + + const LLMatrix4& initRotTrans(const F32 angle, const LLVector3 &axis, const LLVector3 &translation); // Rotation from axis angle + translation + const LLMatrix4& initRotTrans(const F32 roll, const F32 pitch, const F32 yaw, const LLVector4 &pos); // Rotation from Euler + translation + const LLMatrix4& initRotTrans(const LLQuaternion &q, const LLVector4 &pos); // Set with Quaternion and position + + + // Set all + const LLMatrix4& initAll(const LLVector3 &scale, const LLQuaternion &q, const LLVector3 &pos); + + + /////////////////////////// + // + // Matrix setters - set some properties without modifying others + // + + const LLMatrix4& setTranslation(const F32 x, const F32 y, const F32 z); // Sets matrix to translate by (x,y,z) + + void setFwdRow(const LLVector3 &row); + void setLeftRow(const LLVector3 &row); + void setUpRow(const LLVector3 &row); + + void setFwdCol(const LLVector3 &col); + void setLeftCol(const LLVector3 &col); + void setUpCol(const LLVector3 &col); + + const LLMatrix4& setTranslation(const LLVector4 &translation); + const LLMatrix4& setTranslation(const LLVector3 &translation); + + /////////////////////////// + // + // Get properties of a matrix + // + + F32 determinant(void) const; // Return determinant + LLQuaternion quaternion(void) const; // Returns quaternion + + LLVector4 getFwdRow4() const; + LLVector4 getLeftRow4() const; + LLVector4 getUpRow4() const; + + LLMatrix3 getMat3() const; + + const LLVector3& getTranslation() const { return *(LLVector3*)&mMatrix[3][0]; } + + /////////////////////////// + // + // Operations on an existing matrix + // + + const LLMatrix4& transpose(); // Transpose LLMatrix4 + const LLMatrix4& invert(); // Invert LLMatrix4 + + // Rotate existing matrix + // These are really, really, inefficient as implemented! - djs + const LLMatrix4& rotate(const F32 angle, const F32 x, const F32 y, const F32 z); // Rotate matrix by rotating angle radians about (x, y, z) + const LLMatrix4& rotate(const F32 angle, const LLVector4 &vec); // Rotate matrix by rotating angle radians about vec + const LLMatrix4& rotate(const F32 roll, const F32 pitch, const F32 yaw); // Rotate matrix by Euler angles + const LLMatrix4& rotate(const LLQuaternion &q); // Rotate matrix by Quaternion + + + // Translate existing matrix + const LLMatrix4& translate(const LLVector3 &vec); // Translate matrix by (vec[VX], vec[VY], vec[VZ]) + + + + + /////////////////////// + // + // Operators + // + +// Not implemented to enforce code that agrees with symbolic syntax +// friend LLVector4 operator*(const LLMatrix4 &a, const LLVector4 &b); // Apply rotation a to vector b + +// friend inline LLMatrix4 operator*(const LLMatrix4 &a, const LLMatrix4 &b); // Return a * b + friend LLVector4 operator*(const LLVector4 &a, const LLMatrix4 &b); // Return transform of vector a by matrix b + friend LLVector3 operator*(const LLVector3 &a, const LLMatrix4 &b); // Return full transform of a by matrix b + friend LLVector4 rotate_vector(const LLVector4 &a, const LLMatrix4 &b); // Rotates a but does not translate + friend LLVector3 rotate_vector(const LLVector3 &a, const LLMatrix4 &b); // Rotates a but does not translate + + friend bool operator==(const LLMatrix4 &a, const LLMatrix4 &b); // Return a == b + friend bool operator!=(const LLMatrix4 &a, const LLMatrix4 &b); // Return a != b + + friend const LLMatrix4& operator+=(LLMatrix4 &a, const LLMatrix4 &b); // Return a + b + friend const LLMatrix4& operator-=(LLMatrix4 &a, const LLMatrix4 &b); // Return a - b + friend const LLMatrix4& operator*=(LLMatrix4 &a, const LLMatrix4 &b); // Return a * b + friend const LLMatrix4& operator*=(LLMatrix4 &a, const F32 &b); // Return a * b + + friend std::ostream& operator<<(std::ostream& s, const LLMatrix4 &a); // Stream a +} +#if LL_DARWIN +__attribute__ ((aligned (16))) +#endif +; + + +inline LLMatrix4::LLMatrix4() +{ + identity(); +} + +inline const LLMatrix4& LLMatrix4::identity() +{ + mMatrix[0][0] = 1.f; + mMatrix[0][1] = 0.f; + mMatrix[0][2] = 0.f; + mMatrix[0][3] = 0.f; + + mMatrix[1][0] = 0.f; + mMatrix[1][1] = 1.f; + mMatrix[1][2] = 0.f; + mMatrix[1][3] = 0.f; + + mMatrix[2][0] = 0.f; + mMatrix[2][1] = 0.f; + mMatrix[2][2] = 1.f; + mMatrix[2][3] = 0.f; + + mMatrix[3][0] = 0.f; + mMatrix[3][1] = 0.f; + mMatrix[3][2] = 0.f; + mMatrix[3][3] = 1.f; + return (*this); +} + +inline LLVector3 operator*(const LLVector3 &a, const LLMatrix4 &b) +{ + // Converts a to LLVector4 and applies full transformation + // Operates "to the left" on row-vector a + LLVector3 vec; + vec.mV[VX] = a.mV[VX] * b.mMatrix[VX][VX] + + a.mV[VY] * b.mMatrix[VY][VX] + + a.mV[VZ] * b.mMatrix[VZ][VX] + + b.mMatrix[VW][VX]; + + vec.mV[VY] = a.mV[VX] * b.mMatrix[VX][VY] + + a.mV[VY] * b.mMatrix[VY][VY] + + a.mV[VZ] * b.mMatrix[VZ][VY] + + b.mMatrix[VW][VY]; + + vec.mV[VZ] = a.mV[VX] * b.mMatrix[VX][VZ] + + a.mV[VY] * b.mMatrix[VY][VZ] + + a.mV[VZ] * b.mMatrix[VZ][VZ] + + b.mMatrix[VW][VZ]; + return vec; +} + +/* +inline LLMatrix4 operator*(const LLMatrix4 &a, const LLMatrix4 &b) +{ + U32 i, j; + LLMatrix4 mat; + for (i = 0; i < NUM_VALUES_IN_MAT4; i++) + { + for (j = 0; j < NUM_VALUES_IN_MAT4; j++) + { + mat.mMatrix[j][i] = a.mMatrix[j][0] * b.mMatrix[0][i] + + a.mMatrix[j][1] * b.mMatrix[1][i] + + a.mMatrix[j][2] * b.mMatrix[2][i] + + a.mMatrix[j][3] * b.mMatrix[3][i]; + } + } + return mat; +} +*/ + + +inline const LLMatrix4& operator*=(LLMatrix4 &a, const LLMatrix4 &b) +{ + U32 i, j; + LLMatrix4 mat; + for (i = 0; i < NUM_VALUES_IN_MAT4; i++) + { + for (j = 0; j < NUM_VALUES_IN_MAT4; j++) + { + mat.mMatrix[j][i] = a.mMatrix[j][0] * b.mMatrix[0][i] + + a.mMatrix[j][1] * b.mMatrix[1][i] + + a.mMatrix[j][2] * b.mMatrix[2][i] + + a.mMatrix[j][3] * b.mMatrix[3][i]; + } + } + a = mat; + return a; +} + +inline const LLMatrix4& operator*=(LLMatrix4 &a, const F32 &b) +{ + U32 i, j; + LLMatrix4 mat; + for (i = 0; i < NUM_VALUES_IN_MAT4; i++) + { + for (j = 0; j < NUM_VALUES_IN_MAT4; j++) + { + mat.mMatrix[j][i] = a.mMatrix[j][i] * b; + } + } + a = mat; + return a; +} + +inline const LLMatrix4& operator+=(LLMatrix4 &a, const LLMatrix4 &b) +{ + LLMatrix4 mat; + U32 i, j; + for (i = 0; i < NUM_VALUES_IN_MAT4; i++) + { + for (j = 0; j < NUM_VALUES_IN_MAT4; j++) + { + mat.mMatrix[j][i] = a.mMatrix[j][i] + b.mMatrix[j][i]; + } + } + a = mat; + return a; +} + +inline const LLMatrix4& operator-=(LLMatrix4 &a, const LLMatrix4 &b) +{ + LLMatrix4 mat; + U32 i, j; + for (i = 0; i < NUM_VALUES_IN_MAT4; i++) + { + for (j = 0; j < NUM_VALUES_IN_MAT4; j++) + { + mat.mMatrix[j][i] = a.mMatrix[j][i] - b.mMatrix[j][i]; + } + } + a = mat; + return a; +} + +#endif + + + diff --git a/indra/llmath/raytrace.cpp b/indra/llmath/raytrace.cpp new file mode 100644 index 0000000000..dfee5d09fd --- /dev/null +++ b/indra/llmath/raytrace.cpp @@ -0,0 +1,1256 @@ +/** + * @file raytrace.cpp + * @brief Functions called by box object scripts. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "math.h" +//#include "vmath.h" +#include "v3math.h" +#include "llquaternion.h" +#include "m3math.h" +#include "raytrace.h" + + +BOOL line_plane(const LLVector3 &line_point, const LLVector3 &line_direction, + const LLVector3 &plane_point, const LLVector3 plane_normal, + LLVector3 &intersection) +{ + F32 N = line_direction * plane_normal; + if (0.0f == N) + { + // line is perpendicular to plane normal + // so it is either entirely on plane, or not on plane at all + return FALSE; + } + // Ax + By, + Cz + D = 0 + // D = - (plane_point * plane_normal) + // N = line_direction * plane_normal + // intersection = line_point - ((D + plane_normal * line_point) / N) * line_direction + intersection = line_point - ((plane_normal * line_point - plane_point * plane_normal) / N) * line_direction; + return TRUE; +} + + +BOOL ray_plane(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &plane_point, const LLVector3 plane_normal, + LLVector3 &intersection) +{ + F32 N = ray_direction * plane_normal; + if (0.0f == N) + { + // ray is perpendicular to plane normal + // so it is either entirely on plane, or not on plane at all + return FALSE; + } + // Ax + By, + Cz + D = 0 + // D = - (plane_point * plane_normal) + // N = ray_direction * plane_normal + // intersection = ray_point - ((D + plane_normal * ray_point) / N) * ray_direction + F32 alpha = -(plane_normal * ray_point - plane_point * plane_normal) / N; + if (alpha < 0.0f) + { + // ray points away from plane + return FALSE; + } + intersection = ray_point + alpha * ray_direction; + return TRUE; +} + + +BOOL ray_circle(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &circle_center, const LLVector3 plane_normal, F32 circle_radius, + LLVector3 &intersection) +{ + if (ray_plane(ray_point, ray_direction, circle_center, plane_normal, intersection)) + { + if (circle_radius >= (intersection - circle_center).magVec()) + { + return TRUE; + } + } + return FALSE; +} + + +BOOL ray_triangle(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &point_0, const LLVector3 &point_1, const LLVector3 &point_2, + LLVector3 &intersection, LLVector3 &intersection_normal) +{ + LLVector3 side_01 = point_1 - point_0; + LLVector3 side_12 = point_2 - point_1; + + intersection_normal = side_01 % side_12; + intersection_normal.normVec(); + + if (ray_plane(ray_point, ray_direction, point_0, intersection_normal, intersection)) + { + LLVector3 side_20 = point_0 - point_2; + if (intersection_normal * (side_01 % (intersection - point_0)) >= 0.0f && + intersection_normal * (side_12 % (intersection - point_1)) >= 0.0f && + intersection_normal * (side_20 % (intersection - point_2)) >= 0.0f) + { + return TRUE; + } + } + return FALSE; +} + + +// assumes a parallelogram +BOOL ray_quadrangle(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &point_0, const LLVector3 &point_1, const LLVector3 &point_2, + LLVector3 &intersection, LLVector3 &intersection_normal) +{ + LLVector3 side_01 = point_1 - point_0; + LLVector3 side_12 = point_2 - point_1; + + intersection_normal = side_01 % side_12; + intersection_normal.normVec(); + + if (ray_plane(ray_point, ray_direction, point_0, intersection_normal, intersection)) + { + LLVector3 point_3 = point_0 + (side_12); + LLVector3 side_23 = point_3 - point_2; + LLVector3 side_30 = point_0 - point_3; + if (intersection_normal * (side_01 % (intersection - point_0)) >= 0.0f && + intersection_normal * (side_12 % (intersection - point_1)) >= 0.0f && + intersection_normal * (side_23 % (intersection - point_2)) >= 0.0f && + intersection_normal * (side_30 % (intersection - point_3)) >= 0.0f) + { + return TRUE; + } + } + return FALSE; +} + + +BOOL ray_sphere(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &sphere_center, F32 sphere_radius, + LLVector3 &intersection, LLVector3 &intersection_normal) +{ + LLVector3 ray_to_sphere = sphere_center - ray_point; + F32 dot = ray_to_sphere * ray_direction; + + LLVector3 closest_approach = dot * ray_direction - ray_to_sphere; + + F32 shortest_distance = closest_approach.magVecSquared(); + F32 radius_squared = sphere_radius * sphere_radius; + if (shortest_distance > radius_squared) + { + return FALSE; + } + + F32 half_chord = (F32) sqrt(radius_squared - shortest_distance); + closest_approach = sphere_center + closest_approach; // closest_approach now in absolute coordinates + intersection = closest_approach + half_chord * ray_direction; + dot = ray_direction * (intersection - ray_point); + if (dot < 0.0f) + { + // ray shoots away from sphere and is not inside it + return FALSE; + } + + shortest_distance = ray_direction * ((closest_approach - half_chord * ray_direction) - ray_point); + if (shortest_distance > 0.0f) + { + // ray enters sphere + intersection = intersection - (2.0f * half_chord) * ray_direction; + } + else + { + // do nothing + // ray starts inside sphere and intersects as it leaves the sphere + } + + intersection_normal = intersection - sphere_center; + if (sphere_radius > 0.0f) + { + intersection_normal *= 1.0f / sphere_radius; + } + else + { + intersection_normal.setVec(0.0f, 0.0f, 0.0f); + } + + return TRUE; +} + + +BOOL ray_cylinder(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &cyl_center, const LLVector3 &cyl_scale, const LLQuaternion &cyl_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal) +{ + // calculate the centers of the cylinder caps in the absolute frame + LLVector3 cyl_top(0.0f, 0.0f, 0.5f * cyl_scale.mV[VZ]); + LLVector3 cyl_bottom(0.0f, 0.0f, -cyl_top.mV[VZ]); + cyl_top = (cyl_top * cyl_rotation) + cyl_center; + cyl_bottom = (cyl_bottom * cyl_rotation) + cyl_center; + + // we only handle cylinders with circular cross-sections at the moment + F32 cyl_radius = 0.5f * llmax(cyl_scale.mV[VX], cyl_scale.mV[VY]); // HACK until scaled cylinders are supported + + // This implementation is based on the intcyl() function from Graphics_Gems_IV, page 361 + LLVector3 cyl_axis; // axis direction (bottom toward top) + LLVector3 ray_to_cyl; // ray_point to cyl_top + F32 shortest_distance; // shortest distance from ray to axis + F32 cyl_length; + LLVector3 shortest_direction; + LLVector3 temp_vector; + + cyl_axis = cyl_bottom - cyl_top; + cyl_length = cyl_axis.normVec(); + ray_to_cyl = ray_point - cyl_bottom; + shortest_direction = ray_direction % cyl_axis; + shortest_distance = shortest_direction.normVec(); // recycle shortest_distance + + // check for ray parallel to cylinder axis + if (0.0f == shortest_distance) + { + // ray is parallel to cylinder axis + temp_vector = ray_to_cyl - (ray_to_cyl * cyl_axis) * cyl_axis; + shortest_distance = temp_vector.magVec(); + if (shortest_distance <= cyl_radius) + { + shortest_distance = ray_to_cyl * cyl_axis; + F32 dot = ray_direction * cyl_axis; + + if (shortest_distance > 0.0) + { + if (dot > 0.0f) + { + // ray points away from cylinder bottom + return FALSE; + } + // ray hit bottom of cylinder from outside + intersection = ray_point - shortest_distance * cyl_axis; + intersection_normal = cyl_axis; + + } + else if (shortest_distance > -cyl_length) + { + // ray starts inside cylinder + if (dot < 0.0f) + { + // ray hit top from inside + intersection = ray_point - (cyl_length + shortest_distance) * cyl_axis; + intersection_normal = -cyl_axis; + } + else + { + // ray hit bottom from inside + intersection = ray_point - shortest_distance * cyl_axis; + intersection_normal = cyl_axis; + } + } + else + { + if (dot < 0.0f) + { + // ray points away from cylinder bottom + return FALSE; + } + // ray hit top from outside + intersection = ray_point - (shortest_distance + cyl_length) * cyl_axis; + intersection_normal = -cyl_axis; + } + return TRUE; + } + return FALSE; + } + + // check for intersection with infinite cylinder + shortest_distance = (F32) fabs(ray_to_cyl * shortest_direction); + if (shortest_distance <= cyl_radius) + { + F32 dist_to_closest_point; // dist from ray_point to closest_point + F32 half_chord_length; // half length of intersection chord + F32 in, out; // distances to entering/exiting points + temp_vector = ray_to_cyl % cyl_axis; + dist_to_closest_point = - (temp_vector * shortest_direction); + temp_vector = shortest_direction % cyl_axis; + temp_vector.normVec(); + half_chord_length = (F32) fabs( sqrt(cyl_radius*cyl_radius - shortest_distance * shortest_distance) / + (ray_direction * temp_vector) ); + + out = dist_to_closest_point + half_chord_length; // dist to exiting point + if (out < 0.0f) + { + // cylinder is behind the ray, so we return FALSE + return FALSE; + } + + in = dist_to_closest_point - half_chord_length; // dist to entering point + if (in < 0.0f) + { + // ray_point is inside the cylinder + // so we store the exiting intersection + intersection = ray_point + out * ray_direction; + shortest_distance = out; + } + else + { + // ray hit cylinder from outside + // so we store the entering intersection + intersection = ray_point + in * ray_direction; + shortest_distance = in; + } + + // calculate the normal at intersection + if (0.0f == cyl_radius) + { + intersection_normal.setVec(0.0f, 0.0f, 0.0f); + } + else + { + temp_vector = intersection - cyl_bottom; + intersection_normal = temp_vector - (temp_vector * cyl_axis) * cyl_axis; + intersection_normal.normVec(); + } + + // check for intersection with end caps + // calculate intersection of ray and top plane + if (line_plane(ray_point, ray_direction, cyl_top, -cyl_axis, temp_vector)) // NOTE side-effect: changing temp_vector + { + shortest_distance = (temp_vector - ray_point).magVec(); + if ( (ray_direction * cyl_axis) > 0.0f) + { + // ray potentially enters the cylinder at top + if (shortest_distance > out) + { + // ray missed the finite cylinder + return FALSE; + } + if (shortest_distance > in) + { + // ray intersects cylinder at top plane + intersection = temp_vector; + intersection_normal = -cyl_axis; + return TRUE; + } + } + else + { + // ray potentially exits the cylinder at top + if (shortest_distance < in) + { + // missed the finite cylinder + return FALSE; + } + } + + // calculate intersection of ray and bottom plane + line_plane(ray_point, ray_direction, cyl_bottom, cyl_axis, temp_vector); // NOTE side-effect: changing temp_vector + shortest_distance = (temp_vector - ray_point).magVec(); + if ( (ray_direction * cyl_axis) < 0.0) + { + // ray potentially enters the cylinder at bottom + if (shortest_distance > out) + { + // ray missed the finite cylinder + return FALSE; + } + if (shortest_distance > in) + { + // ray intersects cylinder at bottom plane + intersection = temp_vector; + intersection_normal = cyl_axis; + return TRUE; + } + } + else + { + // ray potentially exits the cylinder at bottom + if (shortest_distance < in) + { + // ray missed the finite cylinder + return FALSE; + } + } + + } + else + { + // ray is parallel to end cap planes + temp_vector = cyl_bottom - ray_point; + shortest_distance = temp_vector * cyl_axis; + if (shortest_distance < 0.0f || shortest_distance > cyl_length) + { + // ray missed finite cylinder + return FALSE; + } + } + + return TRUE; + } + + return FALSE; +} + + +U32 ray_box(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &box_center, const LLVector3 &box_scale, const LLQuaternion &box_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal) +{ + + // Need to rotate into box frame + LLQuaternion into_box_frame(box_rotation); // rotates things from box frame to absolute + into_box_frame.conjQuat(); // now rotates things into box frame + LLVector3 line_point = (ray_point - box_center) * into_box_frame; + LLVector3 line_direction = ray_direction * into_box_frame; + + // Suppose we have a plane: Ax + By + Cz + D = 0 + // then, assuming [A, B, C] is a unit vector: + // + // plane_normal = [A, B, C] + // D = - (plane_normal * plane_point) + // + // Suppose we have a line: X = line_point + alpha * line_direction + // + // the intersection of the plane and line determines alpha + // + // alpha = - (D + plane_normal * line_point) / (plane_normal * line_direction) + + LLVector3 line_plane_intersection; + + F32 pointX = line_point.mV[VX]; + F32 pointY = line_point.mV[VY]; + F32 pointZ = line_point.mV[VZ]; + + F32 dirX = line_direction.mV[VX]; + F32 dirY = line_direction.mV[VY]; + F32 dirZ = line_direction.mV[VZ]; + + // we'll be using the half-scales of the box + F32 boxX = 0.5f * box_scale.mV[VX]; + F32 boxY = 0.5f * box_scale.mV[VY]; + F32 boxZ = 0.5f * box_scale.mV[VZ]; + + // check to see if line_point is OUTSIDE the box + if (pointX < -boxX || + pointX > boxX || + pointY < -boxY || + pointY > boxY || + pointZ < -boxZ || + pointZ > boxZ) + { + // -------------- point is OUTSIDE the box ---------------- + + // front + if (pointX > 0.0f && dirX < 0.0f) + { + // plane_normal = [ 1, 0, 0] + // plane_normal*line_point = pointX + // plane_normal*line_direction = dirX + // D = -boxX + // alpha = - (-boxX + pointX) / dirX + line_plane_intersection = line_point - ((pointX - boxX) / dirX) * line_direction; + if (line_plane_intersection.mV[VY] < boxY && + line_plane_intersection.mV[VY] > -boxY && + line_plane_intersection.mV[VZ] < boxZ && + line_plane_intersection.mV[VZ] > -boxZ ) + { + intersection = (line_plane_intersection * box_rotation) + box_center; + intersection_normal = LLVector3(1.0f, 0.0f, 0.0f) * box_rotation; + return FRONT_SIDE; + } + } + + // back + if (pointX < 0.0f && dirX > 0.0f) + { + // plane_normal = [ -1, 0, 0] + // plane_normal*line_point = -pX + // plane_normal*line_direction = -direction.mV[VX] + // D = -bX + // alpha = - (-bX - pX) / (-dirX) + line_plane_intersection = line_point - ((boxX + pointX)/ dirX) * line_direction; + if (line_plane_intersection.mV[VY] < boxY && + line_plane_intersection.mV[VY] > -boxY && + line_plane_intersection.mV[VZ] < boxZ && + line_plane_intersection.mV[VZ] > -boxZ ) + { + intersection = (line_plane_intersection * box_rotation) + box_center; + intersection_normal = LLVector3(-1.0f, 0.0f, 0.0f) * box_rotation; + return BACK_SIDE; + } + } + + // left + if (pointY > 0.0f && dirY < 0.0f) + { + // plane_normal = [0, 1, 0] + // plane_normal*line_point = pointY + // plane_normal*line_direction = dirY + // D = -boxY + // alpha = - (-boxY + pointY) / dirY + line_plane_intersection = line_point + ((boxY - pointY)/dirY) * line_direction; + + if (line_plane_intersection.mV[VX] < boxX && + line_plane_intersection.mV[VX] > -boxX && + line_plane_intersection.mV[VZ] < boxZ && + line_plane_intersection.mV[VZ] > -boxZ ) + { + intersection = (line_plane_intersection * box_rotation) + box_center; + intersection_normal = LLVector3(0.0f, 1.0f, 0.0f) * box_rotation; + return LEFT_SIDE; + } + } + + // right + if (pointY < 0.0f && dirY > 0.0f) + { + // plane_normal = [0, -1, 0] + // plane_normal*line_point = -pointY + // plane_normal*line_direction = -dirY + // D = -boxY + // alpha = - (-boxY - pointY) / (-dirY) + line_plane_intersection = line_point - ((boxY + pointY)/dirY) * line_direction; + if (line_plane_intersection.mV[VX] < boxX && + line_plane_intersection.mV[VX] > -boxX && + line_plane_intersection.mV[VZ] < boxZ && + line_plane_intersection.mV[VZ] > -boxZ ) + { + intersection = (line_plane_intersection * box_rotation) + box_center; + intersection_normal = LLVector3(0.0f, -1.0f, 0.0f) * box_rotation; + return RIGHT_SIDE; + } + } + + // top + if (pointZ > 0.0f && dirZ < 0.0f) + { + // plane_normal = [0, 0, 1] + // plane_normal*line_point = pointZ + // plane_normal*line_direction = dirZ + // D = -boxZ + // alpha = - (-boxZ + pointZ) / dirZ + line_plane_intersection = line_point - ((pointZ - boxZ)/dirZ) * line_direction; + if (line_plane_intersection.mV[VX] < boxX && + line_plane_intersection.mV[VX] > -boxX && + line_plane_intersection.mV[VY] < boxY && + line_plane_intersection.mV[VY] > -boxY ) + { + intersection = (line_plane_intersection * box_rotation) + box_center; + intersection_normal = LLVector3(0.0f, 0.0f, 1.0f) * box_rotation; + return TOP_SIDE; + } + } + + // bottom + if (pointZ < 0.0f && dirZ > 0.0f) + { + // plane_normal = [0, 0, -1] + // plane_normal*line_point = -pointZ + // plane_normal*line_direction = -dirZ + // D = -boxZ + // alpha = - (-boxZ - pointZ) / (-dirZ) + line_plane_intersection = line_point - ((boxZ + pointZ)/dirZ) * line_direction; + if (line_plane_intersection.mV[VX] < boxX && + line_plane_intersection.mV[VX] > -boxX && + line_plane_intersection.mV[VY] < boxY && + line_plane_intersection.mV[VY] > -boxY ) + { + intersection = (line_plane_intersection * box_rotation) + box_center; + intersection_normal = LLVector3(0.0f, 0.0f, -1.0f) * box_rotation; + return BOTTOM_SIDE; + } + } + return NO_SIDE; + } + + // -------------- point is INSIDE the box ---------------- + + // front + if (dirX > 0.0f) + { + // plane_normal = [ 1, 0, 0] + // plane_normal*line_point = pointX + // plane_normal*line_direction = dirX + // D = -boxX + // alpha = - (-boxX + pointX) / dirX + line_plane_intersection = line_point - ((pointX - boxX) / dirX) * line_direction; + if (line_plane_intersection.mV[VY] < boxY && + line_plane_intersection.mV[VY] > -boxY && + line_plane_intersection.mV[VZ] < boxZ && + line_plane_intersection.mV[VZ] > -boxZ ) + { + intersection = (line_plane_intersection * box_rotation) + box_center; + intersection_normal = LLVector3(1.0f, 0.0f, 0.0f) * box_rotation; + return FRONT_SIDE; + } + } + + // back + if (dirX < 0.0f) + { + // plane_normal = [ -1, 0, 0] + // plane_normal*line_point = -pX + // plane_normal*line_direction = -direction.mV[VX] + // D = -bX + // alpha = - (-bX - pX) / (-dirX) + line_plane_intersection = line_point - ((boxX + pointX)/ dirX) * line_direction; + if (line_plane_intersection.mV[VY] < boxY && + line_plane_intersection.mV[VY] > -boxY && + line_plane_intersection.mV[VZ] < boxZ && + line_plane_intersection.mV[VZ] > -boxZ ) + { + intersection = (line_plane_intersection * box_rotation) + box_center; + intersection_normal = LLVector3(-1.0f, 0.0f, 0.0f) * box_rotation; + return BACK_SIDE; + } + } + + // left + if (dirY > 0.0f) + { + // plane_normal = [0, 1, 0] + // plane_normal*line_point = pointY + // plane_normal*line_direction = dirY + // D = -boxY + // alpha = - (-boxY + pointY) / dirY + line_plane_intersection = line_point + ((boxY - pointY)/dirY) * line_direction; + + if (line_plane_intersection.mV[VX] < boxX && + line_plane_intersection.mV[VX] > -boxX && + line_plane_intersection.mV[VZ] < boxZ && + line_plane_intersection.mV[VZ] > -boxZ ) + { + intersection = (line_plane_intersection * box_rotation) + box_center; + intersection_normal = LLVector3(0.0f, 1.0f, 0.0f) * box_rotation; + return LEFT_SIDE; + } + } + + // right + if (dirY < 0.0f) + { + // plane_normal = [0, -1, 0] + // plane_normal*line_point = -pointY + // plane_normal*line_direction = -dirY + // D = -boxY + // alpha = - (-boxY - pointY) / (-dirY) + line_plane_intersection = line_point - ((boxY + pointY)/dirY) * line_direction; + if (line_plane_intersection.mV[VX] < boxX && + line_plane_intersection.mV[VX] > -boxX && + line_plane_intersection.mV[VZ] < boxZ && + line_plane_intersection.mV[VZ] > -boxZ ) + { + intersection = (line_plane_intersection * box_rotation) + box_center; + intersection_normal = LLVector3(0.0f, -1.0f, 0.0f) * box_rotation; + return RIGHT_SIDE; + } + } + + // top + if (dirZ > 0.0f) + { + // plane_normal = [0, 0, 1] + // plane_normal*line_point = pointZ + // plane_normal*line_direction = dirZ + // D = -boxZ + // alpha = - (-boxZ + pointZ) / dirZ + line_plane_intersection = line_point - ((pointZ - boxZ)/dirZ) * line_direction; + if (line_plane_intersection.mV[VX] < boxX && + line_plane_intersection.mV[VX] > -boxX && + line_plane_intersection.mV[VY] < boxY && + line_plane_intersection.mV[VY] > -boxY ) + { + intersection = (line_plane_intersection * box_rotation) + box_center; + intersection_normal = LLVector3(0.0f, 0.0f, 1.0f) * box_rotation; + return TOP_SIDE; + } + } + + // bottom + if (dirZ < 0.0f) + { + // plane_normal = [0, 0, -1] + // plane_normal*line_point = -pointZ + // plane_normal*line_direction = -dirZ + // D = -boxZ + // alpha = - (-boxZ - pointZ) / (-dirZ) + line_plane_intersection = line_point - ((boxZ + pointZ)/dirZ) * line_direction; + if (line_plane_intersection.mV[VX] < boxX && + line_plane_intersection.mV[VX] > -boxX && + line_plane_intersection.mV[VY] < boxY && + line_plane_intersection.mV[VY] > -boxY ) + { + intersection = (line_plane_intersection * box_rotation) + box_center; + intersection_normal = LLVector3(0.0f, 0.0f, -1.0f) * box_rotation; + return BOTTOM_SIDE; + } + } + + // should never get here unless line instersects at tangent point on edge or corner + // however such cases will be EXTREMELY rare + return NO_SIDE; +} + + +BOOL ray_prism(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &prism_center, const LLVector3 &prism_scale, const LLQuaternion &prism_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal) +{ + // (0) Z + // /| \ . + // (1)| \ /|\ _.Y + // | \ \ | /| + // | |\ \ | / + // | | \(0)\ | / + // | | \ \ |/ + // | | \ \ (*)----> X + // |(3)---\---(2) + // |/ \ / + // (4)-------(5) + + // need to calculate the points of the prism so we can run ray tests with each face + F32 x = prism_scale.mV[VX]; + F32 y = prism_scale.mV[VY]; + F32 z = prism_scale.mV[VZ]; + + F32 tx = x * 2.0f / 3.0f; + F32 ty = y * 0.5f; + F32 tz = z * 2.0f / 3.0f; + + LLVector3 point0(tx-x, ty, tz); + LLVector3 point1(tx-x, -ty, tz); + LLVector3 point2(tx, ty, tz-z); + LLVector3 point3(tx-x, ty, tz-z); + LLVector3 point4(tx-x, -ty, tz-z); + LLVector3 point5(tx, -ty, tz-z); + + // transform these points into absolute frame + point0 = (point0 * prism_rotation) + prism_center; + point1 = (point1 * prism_rotation) + prism_center; + point2 = (point2 * prism_rotation) + prism_center; + point3 = (point3 * prism_rotation) + prism_center; + point4 = (point4 * prism_rotation) + prism_center; + point5 = (point5 * prism_rotation) + prism_center; + + // test ray intersection for each face + BOOL b_hit = FALSE; + LLVector3 face_intersection, face_normal; + F32 distance_squared = 0.0f; + F32 temp; + + // face 0 + if (ray_direction * ( (point0 - point2) % (point5 - point2)) < 0.0f && + ray_quadrangle(ray_point, ray_direction, point5, point2, point0, intersection, intersection_normal)) + { + distance_squared = (ray_point - intersection).magVecSquared(); + b_hit = TRUE; + } + + // face 1 + if (ray_direction * ( (point0 - point3) % (point2 - point3)) < 0.0f && + ray_triangle(ray_point, ray_direction, point2, point3, point0, face_intersection, face_normal)) + { + if (TRUE == b_hit) + { + temp = (ray_point - face_intersection).magVecSquared(); + if (temp < distance_squared) + { + distance_squared = temp; + intersection = face_intersection; + intersection_normal = face_normal; + } + } + else + { + distance_squared = (ray_point - face_intersection).magVecSquared(); + intersection = face_intersection; + intersection_normal = face_normal; + b_hit = TRUE; + } + } + + // face 2 + if (ray_direction * ( (point1 - point4) % (point3 - point4)) < 0.0f && + ray_quadrangle(ray_point, ray_direction, point3, point4, point1, face_intersection, face_normal)) + { + if (TRUE == b_hit) + { + temp = (ray_point - face_intersection).magVecSquared(); + if (temp < distance_squared) + { + distance_squared = temp; + intersection = face_intersection; + intersection_normal = face_normal; + } + } + else + { + distance_squared = (ray_point - face_intersection).magVecSquared(); + intersection = face_intersection; + intersection_normal = face_normal; + b_hit = TRUE; + } + } + + // face 3 + if (ray_direction * ( (point5 - point4) % (point1 - point4)) < 0.0f && + ray_triangle(ray_point, ray_direction, point1, point4, point5, face_intersection, face_normal)) + { + if (TRUE == b_hit) + { + temp = (ray_point - face_intersection).magVecSquared(); + if (temp < distance_squared) + { + distance_squared = temp; + intersection = face_intersection; + intersection_normal = face_normal; + } + } + else + { + distance_squared = (ray_point - face_intersection).magVecSquared(); + intersection = face_intersection; + intersection_normal = face_normal; + b_hit = TRUE; + } + } + + // face 4 + if (ray_direction * ( (point4 - point5) % (point2 - point5)) < 0.0f && + ray_quadrangle(ray_point, ray_direction, point2, point5, point4, face_intersection, face_normal)) + { + if (TRUE == b_hit) + { + temp = (ray_point - face_intersection).magVecSquared(); + if (temp < distance_squared) + { + distance_squared = temp; + intersection = face_intersection; + intersection_normal = face_normal; + } + } + else + { + distance_squared = (ray_point - face_intersection).magVecSquared(); + intersection = face_intersection; + intersection_normal = face_normal; + b_hit = TRUE; + } + } + + return b_hit; +} + + +BOOL ray_tetrahedron(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &t_center, const LLVector3 &t_scale, const LLQuaternion &t_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal) +{ + F32 a = 0.5f * F_SQRT3; // height of unit triangle + F32 b = 1.0f / F_SQRT3; // distance of center of unit triangle to each point + F32 c = F_SQRT2 / F_SQRT3; // height of unit tetrahedron + F32 d = 0.5f * F_SQRT3 / F_SQRT2; // distance of center of tetrahedron to each point + + // if we want the tetrahedron to have unit height (c = 1.0) then we need to divide + // each constant by hieght of a unit tetrahedron + F32 oo_c = 1.0f / c; + a = a * oo_c; + b = b * oo_c; + c = 1.0f; + d = d * oo_c; + F32 e = 0.5f * oo_c; + + LLVector3 point0( 0.0f, 0.0f, t_scale.mV[VZ] * d); + LLVector3 point1(t_scale.mV[VX] * b, 0.0f, t_scale.mV[VZ] * (d-c)); + LLVector3 point2(t_scale.mV[VX] * (b-a), e * t_scale.mV[VY], t_scale.mV[VZ] * (d-c)); + LLVector3 point3(t_scale.mV[VX] * (b-a), -e * t_scale.mV[VY], t_scale.mV[VZ] * (d-c)); + + // transform these points into absolute frame + point0 = (point0 * t_rotation) + t_center; + point1 = (point1 * t_rotation) + t_center; + point2 = (point2 * t_rotation) + t_center; + point3 = (point3 * t_rotation) + t_center; + + // test ray intersection for each face + BOOL b_hit = FALSE; + LLVector3 face_intersection, face_normal; + F32 distance_squared = 1.0e12f; + F32 temp; + + // face 0 + if (ray_direction * ( (point2 - point1) % (point0 - point1)) < 0.0f && + ray_triangle(ray_point, ray_direction, point1, point2, point0, intersection, intersection_normal)) + { + distance_squared = (ray_point - intersection).magVecSquared(); + b_hit = TRUE; + } + + // face 1 + if (ray_direction * ( (point3 - point2) % (point0 - point2)) < 0.0f && + ray_triangle(ray_point, ray_direction, point2, point3, point0, face_intersection, face_normal)) + { + if (TRUE == b_hit) + { + temp = (ray_point - face_intersection).magVecSquared(); + if (temp < distance_squared) + { + distance_squared = temp; + intersection = face_intersection; + intersection_normal = face_normal; + } + } + else + { + distance_squared = (ray_point - face_intersection).magVecSquared(); + intersection = face_intersection; + intersection_normal = face_normal; + b_hit = TRUE; + } + } + + // face 2 + if (ray_direction * ( (point1 - point3) % (point0 - point3)) < 0.0f && + ray_triangle(ray_point, ray_direction, point3, point1, point0, face_intersection, face_normal)) + { + if (TRUE == b_hit) + { + temp = (ray_point - face_intersection).magVecSquared(); + if (temp < distance_squared) + { + distance_squared = temp; + intersection = face_intersection; + intersection_normal = face_normal; + } + } + else + { + distance_squared = (ray_point - face_intersection).magVecSquared(); + intersection = face_intersection; + intersection_normal = face_normal; + b_hit = TRUE; + } + } + + // face 3 + if (ray_direction * ( (point2 - point3) % (point1 - point3)) < 0.0f && + ray_triangle(ray_point, ray_direction, point3, point2, point1, face_intersection, face_normal)) + { + if (TRUE == b_hit) + { + temp = (ray_point - face_intersection).magVecSquared(); + if (temp < distance_squared) + { + intersection = face_intersection; + intersection_normal = face_normal; + } + } + else + { + intersection = face_intersection; + intersection_normal = face_normal; + b_hit = TRUE; + } + } + + return b_hit; +} + + +BOOL ray_pyramid(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &p_center, const LLVector3 &p_scale, const LLQuaternion &p_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal) +{ + // center of mass of pyramid is located 1/4 its height from the base + F32 x = 0.5f * p_scale.mV[VX]; + F32 y = 0.5f * p_scale.mV[VY]; + F32 z = 0.25f * p_scale.mV[VZ]; + + LLVector3 point0(0.0f, 0.0f, p_scale.mV[VZ] - z); + LLVector3 point1( x, y, -z); + LLVector3 point2(-x, y, -z); + LLVector3 point3(-x, -y, -z); + LLVector3 point4( x, -y, -z); + + // transform these points into absolute frame + point0 = (point0 * p_rotation) + p_center; + point1 = (point1 * p_rotation) + p_center; + point2 = (point2 * p_rotation) + p_center; + point3 = (point3 * p_rotation) + p_center; + point4 = (point4 * p_rotation) + p_center; + + // test ray intersection for each face + BOOL b_hit = FALSE; + LLVector3 face_intersection, face_normal; + F32 distance_squared = 1.0e12f; + F32 temp; + + // face 0 + if (ray_direction * ( (point1 - point4) % (point0 - point4)) < 0.0f && + ray_triangle(ray_point, ray_direction, point4, point1, point0, intersection, intersection_normal)) + { + distance_squared = (ray_point - intersection).magVecSquared(); + b_hit = TRUE; + } + + // face 1 + if (ray_direction * ( (point2 - point1) % (point0 - point1)) < 0.0f && + ray_triangle(ray_point, ray_direction, point1, point2, point0, face_intersection, face_normal)) + { + if (TRUE == b_hit) + { + temp = (ray_point - face_intersection).magVecSquared(); + if (temp < distance_squared) + { + distance_squared = temp; + intersection = face_intersection; + intersection_normal = face_normal; + } + } + else + { + distance_squared = (ray_point - face_intersection).magVecSquared(); + intersection = face_intersection; + intersection_normal = face_normal; + b_hit = TRUE; + } + } + + // face 2 + if (ray_direction * ( (point3 - point2) % (point0 - point2)) < 0.0f && + ray_triangle(ray_point, ray_direction, point2, point3, point0, face_intersection, face_normal)) + { + if (TRUE == b_hit) + { + temp = (ray_point - face_intersection).magVecSquared(); + if (temp < distance_squared) + { + distance_squared = temp; + intersection = face_intersection; + intersection_normal = face_normal; + } + } + else + { + distance_squared = (ray_point - face_intersection).magVecSquared(); + intersection = face_intersection; + intersection_normal = face_normal; + b_hit = TRUE; + } + } + + // face 3 + if (ray_direction * ( (point4 - point3) % (point0 - point3)) < 0.0f && + ray_triangle(ray_point, ray_direction, point3, point4, point0, face_intersection, face_normal)) + { + if (TRUE == b_hit) + { + temp = (ray_point - face_intersection).magVecSquared(); + if (temp < distance_squared) + { + distance_squared = temp; + intersection = face_intersection; + intersection_normal = face_normal; + } + } + else + { + distance_squared = (ray_point - face_intersection).magVecSquared(); + intersection = face_intersection; + intersection_normal = face_normal; + b_hit = TRUE; + } + } + + // face 4 + if (ray_direction * ( (point3 - point4) % (point2 - point4)) < 0.0f && + ray_quadrangle(ray_point, ray_direction, point4, point3, point2, face_intersection, face_normal)) + { + if (TRUE == b_hit) + { + temp = (ray_point - face_intersection).magVecSquared(); + if (temp < distance_squared) + { + intersection = face_intersection; + intersection_normal = face_normal; + } + } + else + { + intersection = face_intersection; + intersection_normal = face_normal; + b_hit = TRUE; + } + } + + return b_hit; +} + + +BOOL linesegment_circle(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &circle_center, const LLVector3 plane_normal, F32 circle_radius, + LLVector3 &intersection) +{ + LLVector3 ray_direction = point_b - point_a; + F32 segment_length = ray_direction.normVec(); + + if (ray_circle(point_a, ray_direction, circle_center, plane_normal, circle_radius, intersection)) + { + if (segment_length >= (point_a - intersection).magVec()) + { + return TRUE; + } + } + return FALSE; +} + + +BOOL linesegment_triangle(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &point_0, const LLVector3 &point_1, const LLVector3 &point_2, + LLVector3 &intersection, LLVector3 &intersection_normal) +{ + LLVector3 ray_direction = point_b - point_a; + F32 segment_length = ray_direction.normVec(); + + if (ray_triangle(point_a, ray_direction, point_0, point_1, point_2, intersection, intersection_normal)) + { + if (segment_length >= (point_a - intersection).magVec()) + { + return TRUE; + } + } + return FALSE; +} + + +BOOL linesegment_quadrangle(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &point_0, const LLVector3 &point_1, const LLVector3 &point_2, + LLVector3 &intersection, LLVector3 &intersection_normal) +{ + LLVector3 ray_direction = point_b - point_a; + F32 segment_length = ray_direction.normVec(); + + if (ray_quadrangle(point_a, ray_direction, point_0, point_1, point_2, intersection, intersection_normal)) + { + if (segment_length >= (point_a - intersection).magVec()) + { + return TRUE; + } + } + return FALSE; +} + + +BOOL linesegment_sphere(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &sphere_center, F32 sphere_radius, + LLVector3 &intersection, LLVector3 &intersection_normal) +{ + LLVector3 ray_direction = point_b - point_a; + F32 segment_length = ray_direction.normVec(); + + if (ray_sphere(point_a, ray_direction, sphere_center, sphere_radius, intersection, intersection_normal)) + { + if (segment_length >= (point_a - intersection).magVec()) + { + return TRUE; + } + } + return FALSE; +} + + +BOOL linesegment_cylinder(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &cyl_center, const LLVector3 &cyl_scale, const LLQuaternion &cyl_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal) +{ + LLVector3 ray_direction = point_b - point_a; + F32 segment_length = ray_direction.normVec(); + + if (ray_cylinder(point_a, ray_direction, cyl_center, cyl_scale, cyl_rotation, intersection, intersection_normal)) + { + if (segment_length >= (point_a - intersection).magVec()) + { + return TRUE; + } + } + return FALSE; +} + + +U32 linesegment_box(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &box_center, const LLVector3 &box_scale, const LLQuaternion &box_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal) +{ + LLVector3 direction = point_b - point_a; + if (direction.isNull()) + { + return NO_SIDE; + } + + F32 segment_length = direction.normVec(); + U32 box_side = ray_box(point_a, direction, box_center, box_scale, box_rotation, intersection, intersection_normal); + if (NO_SIDE == box_side || segment_length < (intersection - point_a).magVec()) + { + return NO_SIDE; + } + + return box_side; +} + + +BOOL linesegment_prism(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &prism_center, const LLVector3 &prism_scale, const LLQuaternion &prism_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal) +{ + LLVector3 ray_direction = point_b - point_a; + F32 segment_length = ray_direction.normVec(); + + if (ray_prism(point_a, ray_direction, prism_center, prism_scale, prism_rotation, intersection, intersection_normal)) + { + if (segment_length >= (point_a - intersection).magVec()) + { + return TRUE; + } + } + return FALSE; +} + + +BOOL linesegment_tetrahedron(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &t_center, const LLVector3 &t_scale, const LLQuaternion &t_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal) +{ + LLVector3 ray_direction = point_b - point_a; + F32 segment_length = ray_direction.normVec(); + + if (ray_tetrahedron(point_a, ray_direction, t_center, t_scale, t_rotation, intersection, intersection_normal)) + { + if (segment_length >= (point_a - intersection).magVec()) + { + return TRUE; + } + } + return FALSE; +} + + +BOOL linesegment_pyramid(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &p_center, const LLVector3 &p_scale, const LLQuaternion &p_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal) +{ + LLVector3 ray_direction = point_b - point_a; + F32 segment_length = ray_direction.normVec(); + + if (ray_pyramid(point_a, ray_direction, p_center, p_scale, p_rotation, intersection, intersection_normal)) + { + if (segment_length >= (point_a - intersection).magVec()) + { + return TRUE; + } + } + return FALSE; +} + + + + + diff --git a/indra/llmath/raytrace.h b/indra/llmath/raytrace.h new file mode 100644 index 0000000000..d4f93647b8 --- /dev/null +++ b/indra/llmath/raytrace.h @@ -0,0 +1,214 @@ +/** + * @file raytrace.h + * @brief Ray intersection tests for primitives. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_RAYTRACE_H +#define LL_RAYTRACE_H + +class LLVector3; +class LLQuaternion; + +// All functions produce results in the same reference frame as the arguments. +// +// Any arguments of the form "foo_direction" or "foo_normal" are assumed to +// be normalized, or normalized vectors are stored in them. +// +// Vector arguments of the form "shape_scale" represent the scale of the +// object along the three axes. +// +// All functions return the expected TRUE or FALSE, unless otherwise noted. +// When FALSE is returned, any resulting values that might have been stored +// are undefined. +// +// Rays are defined by a "ray_point" and a "ray_direction" (unit). +// +// Lines are defined by a "line_point" and a "line_direction" (unit). +// +// Line segements are defined by "point_a" and "point_b", and for intersection +// purposes are assumed to point from "point_a" to "point_b". +// +// A ray is different from a line in that it starts at a point and extends +// in only one direction. +// +// Intersection normals always point outside the object, normal to the object's +// surface at the point of intersection. +// +// Object rotations passed as quaternions are expected to rotate from the +// object's local frame to the absolute frame. So, if "foo" is a vector in +// the object's local frame, then "foo * object_rotation" is in the absolute +// frame. + + +// returns TRUE iff line is not parallel to plane. +BOOL line_plane(const LLVector3 &line_point, const LLVector3 &line_direction, + const LLVector3 &plane_point, const LLVector3 plane_normal, + LLVector3 &intersection); + + +// returns TRUE iff line is not parallel to plane. +BOOL ray_plane(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &plane_point, const LLVector3 plane_normal, + LLVector3 &intersection); + + +BOOL ray_circle(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &circle_center, const LLVector3 plane_normal, F32 circle_radius, + LLVector3 &intersection); + +// point_0 through point_2 define the plane_normal via the right-hand rule: +// circle from point_0 to point_2 with fingers ==> thumb points in direction of normal +BOOL ray_triangle(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &point_0, const LLVector3 &point_1, const LLVector3 &point_2, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +// point_0 is the lower-left corner, point_1 is the lower-right, point_2 is the upper-right +// right-hand-rule... curl fingers from lower-left toward lower-right then toward upper-right +// ==> thumb points in direction of normal +// assumes a parallelogram, so point_3 is determined by the other points +BOOL ray_quadrangle(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &point_0, const LLVector3 &point_1, const LLVector3 &point_2, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +BOOL ray_sphere(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &sphere_center, F32 sphere_radius, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +// finite right cylinder is defined by end centers: "cyl_top", "cyl_bottom", +// and by the cylinder radius "cyl_radius" +BOOL ray_cylinder(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &cyl_center, const LLVector3 &cyl_scale, const LLQuaternion &cyl_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +// this function doesn't just return a BOOL because the return is currently +// used to decide how to break up boxes that have been hit by shots... +// a hack that will probably be changed later +// +// returns a number representing the side of the box that was hit by the ray, +// or NO_SIDE if intersection test failed. +U32 ray_box(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &box_center, const LLVector3 &box_scale, const LLQuaternion &box_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +/* TODO +BOOL ray_ellipsoid(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &e_center, const LLVector3 &e_scale, const LLQuaternion &e_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +BOOL ray_cone(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &cone_tip, const LLVector3 &cone_bottom, + const LLVector3 &cone_scale, const LLQuaternion &cone_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal); +*/ + + +BOOL ray_prism(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &prism_center, const LLVector3 &prism_scale, const LLQuaternion &prism_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +BOOL ray_tetrahedron(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &t_center, const LLVector3 &t_scale, const LLQuaternion &t_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +BOOL ray_pyramid(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &p_center, const LLVector3 &p_scale, const LLQuaternion &p_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal); + + + +/* TODO +BOOL ray_hemiellipsoid(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &e_center, const LLVector3 &e_scale, const LLQuaternion &e_rotation, + const LLVector3 &e_cut_normal, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +BOOL ray_hemisphere(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &sphere_center, F32 sphere_radius, const LLVector3 &sphere_cut_normal, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +BOOL ray_hemicylinder(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &cyl_top, const LLVector3 &cyl_bottom, F32 cyl_radius, + const LLVector3 &cyl_cut_normal, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +BOOL ray_hemicone(const LLVector3 &ray_point, const LLVector3 &ray_direction, + const LLVector3 &cone_tip, const LLVector3 &cone_bottom, + const LLVector3 &cone_scale, const LLVector3 &cyl_cut_normal, + LLVector3 &intersection, LLVector3 &intersection_normal); +*/ + + +BOOL linesegment_circle(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &circle_center, const LLVector3 plane_normal, F32 circle_radius, + LLVector3 &intersection); + +// point_0 through point_2 define the plane_normal via the right-hand rule: +// circle from point_0 to point_2 with fingers ==> thumb points in direction of normal +BOOL linesegment_triangle(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &point_0, const LLVector3 &point_1, const LLVector3 &point_2, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +// point_0 is the lower-left corner, point_1 is the lower-right, point_2 is the upper-right +// right-hand-rule... curl fingers from lower-left toward lower-right then toward upper-right +// ==> thumb points in direction of normal +// assumes a parallelogram, so point_3 is determined by the other points +BOOL linesegment_quadrangle(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &point_0, const LLVector3 &point_1, const LLVector3 &point_2, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +BOOL linesegment_sphere(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &sphere_center, F32 sphere_radius, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +// finite right cylinder is defined by end centers: "cyl_top", "cyl_bottom", +// and by the cylinder radius "cyl_radius" +BOOL linesegment_cylinder(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &cyl_center, const LLVector3 &cyl_scale, const LLQuaternion &cyl_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +// this function doesn't just return a BOOL because the return is currently +// used to decide how to break up boxes that have been hit by shots... +// a hack that will probably be changed later +// +// returns a number representing the side of the box that was hit by the ray, +// or NO_SIDE if intersection test failed. +U32 linesegment_box(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &box_center, const LLVector3 &box_scale, const LLQuaternion &box_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +BOOL linesegment_prism(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &prism_center, const LLVector3 &prism_scale, const LLQuaternion &prism_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +BOOL linesegment_tetrahedron(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &t_center, const LLVector3 &t_scale, const LLQuaternion &t_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +BOOL linesegment_pyramid(const LLVector3 &point_a, const LLVector3 &point_b, + const LLVector3 &p_center, const LLVector3 &p_scale, const LLQuaternion &p_rotation, + LLVector3 &intersection, LLVector3 &intersection_normal); + + +#endif + diff --git a/indra/llmath/v2math.cpp b/indra/llmath/v2math.cpp new file mode 100644 index 0000000000..d4cff60f62 --- /dev/null +++ b/indra/llmath/v2math.cpp @@ -0,0 +1,92 @@ +/** + * @file v2math.cpp + * @brief LLVector2 class implementation. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +//#include "vmath.h" +#include "v2math.h" +#include "v4math.h" +#include "m4math.h" +#include "m3math.h" +#include "llquaternion.h" + +// LLVector2 + +LLVector2 LLVector2::zero(0,0); + + +// Non-member functions + +// Sets all values to absolute value of their original values +// Returns TRUE if data changed +BOOL LLVector2::abs() +{ + BOOL ret = FALSE; + + if (mV[0] < 0.f) { mV[0] = -mV[0]; ret = TRUE; } + if (mV[1] < 0.f) { mV[1] = -mV[1]; ret = TRUE; } + + return ret; +} + + +F32 angle_between(const LLVector2& a, const LLVector2& b) +{ + LLVector2 an = a; + LLVector2 bn = b; + an.normVec(); + bn.normVec(); + F32 cosine = an * bn; + F32 angle = (cosine >= 1.0f) ? 0.0f : + (cosine <= -1.0f) ? F_PI : + acos(cosine); + return angle; +} + +BOOL are_parallel(const LLVector2 &a, const LLVector2 &b, float epsilon) +{ + LLVector2 an = a; + LLVector2 bn = b; + an.normVec(); + bn.normVec(); + F32 dot = an * bn; + if ( (1.0f - fabs(dot)) < epsilon) + { + return TRUE; + } + return FALSE; +} + + +F32 dist_vec(const LLVector2 &a, const LLVector2 &b) +{ + F32 x = a.mV[0] - b.mV[0]; + F32 y = a.mV[1] - b.mV[1]; + return fsqrtf( x*x + y*y ); +} + +F32 dist_vec_squared(const LLVector2 &a, const LLVector2 &b) +{ + F32 x = a.mV[0] - b.mV[0]; + F32 y = a.mV[1] - b.mV[1]; + return x*x + y*y; +} + +F32 dist_vec_squared2D(const LLVector2 &a, const LLVector2 &b) +{ + F32 x = a.mV[0] - b.mV[0]; + F32 y = a.mV[1] - b.mV[1]; + return x*x + y*y; +} + +LLVector2 lerp(const LLVector2 &a, const LLVector2 &b, F32 u) +{ + return LLVector2( + a.mV[VX] + (b.mV[VX] - a.mV[VX]) * u, + a.mV[VY] + (b.mV[VY] - a.mV[VY]) * u ); +} diff --git a/indra/llmath/v2math.h b/indra/llmath/v2math.h new file mode 100644 index 0000000000..c94333e2a9 --- /dev/null +++ b/indra/llmath/v2math.h @@ -0,0 +1,307 @@ +/** + * @file v2math.h + * @brief LLVector2 class header file. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_V2MATH_H +#define LL_V2MATH_H + +#include + +#include "llmath.h" + +class LLVector4; +class LLMatrix3; +class LLQuaternion; + +// Llvector2 = |x y z w| + +static const U32 LENGTHOFVECTOR2 = 2; + +class LLVector2 +{ + public: + F32 mV[LENGTHOFVECTOR2]; + + static LLVector2 zero; + + LLVector2(); // Initializes LLVector2 to (0, 0) + LLVector2(F32 x, F32 y); // Initializes LLVector2 to (x. y) + LLVector2(const F32 *vec); // Initializes LLVector2 to (vec[0]. vec[1]) + + // Clears LLVector2 to (0, 0). DEPRECATED - prefer zeroVec. + void clearVec(); + + // Zero LLVector2 to (0, 0) + void zeroVec(); + + void setVec(F32 x, F32 y); // Sets LLVector2 to (x, y) + void setVec(const LLVector2 &vec); // Sets LLVector2 to vec + void setVec(const F32 *vec); // Sets LLVector2 to vec + + F32 magVec() const; // Returns magnitude of LLVector2 + F32 magVecSquared() const; // Returns magnitude squared of LLVector2 + F32 normVec(); // Normalizes and returns the magnitude of LLVector2 + + BOOL abs(); // sets all values to absolute value of original value (first octant), returns TRUE if changed + + const LLVector2& scaleVec(const LLVector2& vec); // scales per component by vec + + BOOL isNull(); // Returns TRUE if vector has a _very_small_ length + BOOL isExactlyZero() const { return !mV[VX] && !mV[VY]; } + + F32 operator[](int idx) const { return mV[idx]; } + F32 &operator[](int idx) { return mV[idx]; } + + friend bool operator<(const LLVector2 &a, const LLVector2 &b); // For sorting. x is "more significant" than y + friend LLVector2 operator+(const LLVector2 &a, const LLVector2 &b); // Return vector a + b + friend LLVector2 operator-(const LLVector2 &a, const LLVector2 &b); // Return vector a minus b + friend F32 operator*(const LLVector2 &a, const LLVector2 &b); // Return a dot b + friend LLVector2 operator%(const LLVector2 &a, const LLVector2 &b); // Return a cross b + friend LLVector2 operator/(const LLVector2 &a, F32 k); // Return a divided by scaler k + friend LLVector2 operator*(const LLVector2 &a, F32 k); // Return a times scaler k + friend LLVector2 operator*(F32 k, const LLVector2 &a); // Return a times scaler k + friend bool operator==(const LLVector2 &a, const LLVector2 &b); // Return a == b + friend bool operator!=(const LLVector2 &a, const LLVector2 &b); // Return a != b + + friend const LLVector2& operator+=(LLVector2 &a, const LLVector2 &b); // Return vector a + b + friend const LLVector2& operator-=(LLVector2 &a, const LLVector2 &b); // Return vector a minus b + friend const LLVector2& operator%=(LLVector2 &a, const LLVector2 &b); // Return a cross b + friend const LLVector2& operator*=(LLVector2 &a, F32 k); // Return a times scaler k + friend const LLVector2& operator/=(LLVector2 &a, F32 k); // Return a divided by scaler k + + friend LLVector2 operator-(const LLVector2 &a); // Return vector -a + + friend std::ostream& operator<<(std::ostream& s, const LLVector2 &a); // Stream a +}; + + +// Non-member functions + +F32 angle_between(const LLVector2 &a, const LLVector2 &b); // Returns angle (radians) between a and b +BOOL are_parallel(const LLVector2 &a, const LLVector2 &b, F32 epsilon=F_APPROXIMATELY_ZERO); // Returns TRUE if a and b are very close to parallel +F32 dist_vec(const LLVector2 &a, const LLVector2 &b); // Returns distance between a and b +F32 dist_vec_squared(const LLVector2 &a, const LLVector2 &b);// Returns distance sqaured between a and b +F32 dist_vec_squared2D(const LLVector2 &a, const LLVector2 &b);// Returns distance sqaured between a and b ignoring Z component +LLVector2 lerp(const LLVector2 &a, const LLVector2 &b, F32 u); // Returns a vector that is a linear interpolation between a and b + +// Constructors + +inline LLVector2::LLVector2(void) +{ + mV[VX] = 0.f; + mV[VY] = 0.f; +} + +inline LLVector2::LLVector2(F32 x, F32 y) +{ + mV[VX] = x; + mV[VY] = y; +} + +inline LLVector2::LLVector2(const F32 *vec) +{ + mV[VX] = vec[VX]; + mV[VY] = vec[VY]; +} + + +// Clear and Assignment Functions + +inline void LLVector2::clearVec(void) +{ + mV[VX] = 0.f; + mV[VY] = 0.f; +} + +inline void LLVector2::zeroVec(void) +{ + mV[VX] = 0.f; + mV[VY] = 0.f; +} + +inline void LLVector2::setVec(F32 x, F32 y) +{ + mV[VX] = x; + mV[VY] = y; +} + +inline void LLVector2::setVec(const LLVector2 &vec) +{ + mV[VX] = vec.mV[VX]; + mV[VY] = vec.mV[VY]; +} + +inline void LLVector2::setVec(const F32 *vec) +{ + mV[VX] = vec[VX]; + mV[VY] = vec[VY]; +} + +// LLVector2 Magnitude and Normalization Functions + +inline F32 LLVector2::magVec(void) const +{ + return fsqrtf(mV[0]*mV[0] + mV[1]*mV[1]); +} + +inline F32 LLVector2::magVecSquared(void) const +{ + return mV[0]*mV[0] + mV[1]*mV[1]; +} + +inline F32 LLVector2::normVec(void) +{ + F32 mag = fsqrtf(mV[0]*mV[0] + mV[1]*mV[1]); + F32 oomag; + + if (mag > FP_MAG_THRESHOLD) + { + oomag = 1.f/mag; + mV[0] *= oomag; + mV[1] *= oomag; + } + else + { + mV[0] = 0.f; + mV[1] = 0.f; + mag = 0; + } + return (mag); +} + +inline const LLVector2& LLVector2::scaleVec(const LLVector2& vec) +{ + mV[VX] *= vec.mV[VX]; + mV[VY] *= vec.mV[VY]; + + return *this; +} + +inline BOOL LLVector2::isNull() +{ + if ( F_APPROXIMATELY_ZERO > mV[VX]*mV[VX] + mV[VY]*mV[VY] ) + { + return TRUE; + } + return FALSE; +} + + +// LLVector2 Operators + +// For sorting. By convention, x is "more significant" than y. +inline bool operator<(const LLVector2 &a, const LLVector2 &b) +{ + if( a.mV[VX] == b.mV[VX] ) + { + return a.mV[VY] < b.mV[VY]; + } + else + { + return a.mV[VX] < b.mV[VX]; + } +} + + +inline LLVector2 operator+(const LLVector2 &a, const LLVector2 &b) +{ + LLVector2 c(a); + return c += b; +} + +inline LLVector2 operator-(const LLVector2 &a, const LLVector2 &b) +{ + LLVector2 c(a); + return c -= b; +} + +inline F32 operator*(const LLVector2 &a, const LLVector2 &b) +{ + return (a.mV[0]*b.mV[0] + a.mV[1]*b.mV[1]); +} + +inline LLVector2 operator%(const LLVector2 &a, const LLVector2 &b) +{ + return LLVector2(a.mV[0]*b.mV[1] - b.mV[0]*a.mV[1], a.mV[1]*b.mV[0] - b.mV[1]*a.mV[0]); +} + +inline LLVector2 operator/(const LLVector2 &a, F32 k) +{ + F32 t = 1.f / k; + return LLVector2( a.mV[0] * t, a.mV[1] * t ); +} + +inline LLVector2 operator*(const LLVector2 &a, F32 k) +{ + return LLVector2( a.mV[0] * k, a.mV[1] * k ); +} + +inline LLVector2 operator*(F32 k, const LLVector2 &a) +{ + return LLVector2( a.mV[0] * k, a.mV[1] * k ); +} + +inline bool operator==(const LLVector2 &a, const LLVector2 &b) +{ + return ( (a.mV[0] == b.mV[0]) + &&(a.mV[1] == b.mV[1])); +} + +inline bool operator!=(const LLVector2 &a, const LLVector2 &b) +{ + return ( (a.mV[0] != b.mV[0]) + ||(a.mV[1] != b.mV[1])); +} + +inline const LLVector2& operator+=(LLVector2 &a, const LLVector2 &b) +{ + a.mV[0] += b.mV[0]; + a.mV[1] += b.mV[1]; + return a; +} + +inline const LLVector2& operator-=(LLVector2 &a, const LLVector2 &b) +{ + a.mV[0] -= b.mV[0]; + a.mV[1] -= b.mV[1]; + return a; +} + +inline const LLVector2& operator%=(LLVector2 &a, const LLVector2 &b) +{ + LLVector2 ret(a.mV[0]*b.mV[1] - b.mV[0]*a.mV[1], a.mV[1]*b.mV[0] - b.mV[1]*a.mV[0]); + a = ret; + return a; +} + +inline const LLVector2& operator*=(LLVector2 &a, F32 k) +{ + a.mV[0] *= k; + a.mV[1] *= k; + return a; +} + +inline const LLVector2& operator/=(LLVector2 &a, F32 k) +{ + F32 t = 1.f / k; + a.mV[0] *= t; + a.mV[1] *= t; + return a; +} + +inline LLVector2 operator-(const LLVector2 &a) +{ + return LLVector2( -a.mV[0], -a.mV[1] ); +} + +inline std::ostream& operator<<(std::ostream& s, const LLVector2 &a) +{ + s << "{ " << a.mV[VX] << ", " << a.mV[VY] << " }"; + return s; +} + +#endif diff --git a/indra/llmath/v3color.cpp b/indra/llmath/v3color.cpp new file mode 100644 index 0000000000..2dbc368d98 --- /dev/null +++ b/indra/llmath/v3color.cpp @@ -0,0 +1,44 @@ +/** + * @file v3color.cpp + * @brief LLColor3 class implementation. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "v3color.h" +#include "v4color.h" + +LLColor3 LLColor3::white(1.0f, 1.0f, 1.0f); +LLColor3 LLColor3::black(0.0f, 0.0f, 0.0f); +LLColor3 LLColor3::grey (0.5f, 0.5f, 0.5f); + +LLColor3::LLColor3(const LLColor4 &a) +{ + mV[0] = a.mV[0]; + mV[1] = a.mV[1]; + mV[2] = a.mV[2]; +} + +LLColor3::LLColor3(const LLSD &sd) +{ + mV[0] = (F32) sd[0].asReal(); + mV[1] = (F32) sd[1].asReal(); + mV[2] = (F32) sd[2].asReal(); +} + +const LLColor3& LLColor3::operator=(const LLColor4 &a) +{ + mV[0] = a.mV[0]; + mV[1] = a.mV[1]; + mV[2] = a.mV[2]; + return (*this); +} + +std::ostream& operator<<(std::ostream& s, const LLColor3 &a) +{ + s << "{ " << a.mV[VX] << ", " << a.mV[VY] << ", " << a.mV[VZ] << " }"; + return s; +} diff --git a/indra/llmath/v3color.h b/indra/llmath/v3color.h new file mode 100644 index 0000000000..3777c00054 --- /dev/null +++ b/indra/llmath/v3color.h @@ -0,0 +1,362 @@ +/** + * @file v3color.h + * @brief LLColor3 class header file. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_V3COLOR_H +#define LL_V3COLOR_H + +class LLColor4; + +#include "llerror.h" +#include "llmath.h" +#include "llsd.h" + +// LLColor3 = |r g b| + +static const U32 LENGTHOFCOLOR3 = 3; + +class LLColor3 +{ +public: + F32 mV[LENGTHOFCOLOR3]; + + static LLColor3 white; + static LLColor3 black; + static LLColor3 grey; + +public: + LLColor3(); // Initializes LLColor3 to (0, 0, 0) + LLColor3(F32 r, F32 g, F32 b); // Initializes LLColor3 to (r, g, b) + LLColor3(const F32 *vec); // Initializes LLColor3 to (vec[0]. vec[1], vec[2]) + LLColor3(char *color_string); // html format color ie "#FFDDEE" + explicit LLColor3(const LLColor4& color4); // "explicit" to avoid automatic conversion + LLColor3(const LLSD& sd); + + + LLSD getValue() const + { + LLSD ret; + ret[0] = mV[0]; + ret[1] = mV[1]; + ret[2] = mV[2]; + return ret; + } + + void setValue(const LLSD& sd) + { + mV[0] = (F32) sd[0].asReal();; + mV[1] = (F32) sd[1].asReal();; + mV[2] = (F32) sd[2].asReal();; + } + + const LLColor3& setToBlack(); // Clears LLColor3 to (0, 0, 0) + const LLColor3& setToWhite(); // Zero LLColor3 to (0, 0, 0) + const LLColor3& setVec(F32 x, F32 y, F32 z); // Sets LLColor3 to (x, y, z) + const LLColor3& setVec(const LLColor3 &vec); // Sets LLColor3 to vec + const LLColor3& setVec(const F32 *vec); // Sets LLColor3 to vec + + F32 magVec() const; // Returns magnitude of LLColor3 + F32 magVecSquared() const; // Returns magnitude squared of LLColor3 + F32 normVec(); // Normalizes and returns the magnitude of LLColor3 + + const LLColor3& operator=(const LLColor4 &a); + + friend std::ostream& operator<<(std::ostream& s, const LLColor3 &a); // Print a + friend LLColor3 operator+(const LLColor3 &a, const LLColor3 &b); // Return vector a + b + friend LLColor3 operator-(const LLColor3 &a, const LLColor3 &b); // Return vector a minus b + + friend const LLColor3& operator+=(LLColor3 &a, const LLColor3 &b); // Return vector a + b + friend const LLColor3& operator-=(LLColor3 &a, const LLColor3 &b); // Return vector a minus b + friend const LLColor3& operator*=(LLColor3 &a, const LLColor3 &b); + + friend LLColor3 operator*(const LLColor3 &a, const LLColor3 &b); // Return a dot b + friend LLColor3 operator*(const LLColor3 &a, F32 k); // Return a times scaler k + friend LLColor3 operator*(F32 k, const LLColor3 &a); // Return a times scaler k + + friend bool operator==(const LLColor3 &a, const LLColor3 &b); // Return a == b + friend bool operator!=(const LLColor3 &a, const LLColor3 &b); // Return a != b + + friend const LLColor3& operator*=(LLColor3 &a, F32 k); // Return a times scaler k + + friend LLColor3 operator-(const LLColor3 &a); // Return vector 1-rgb (inverse) + + inline void clamp(); + inline void exp(); // Do an exponential on the color +}; + +LLColor3 lerp(const LLColor3 &a, const LLColor3 &b, F32 u); + + +void LLColor3::clamp() +{ + // Clamp the color... + if (mV[0] < 0.f) + { + mV[0] = 0.f; + } + else if (mV[0] > 1.f) + { + mV[0] = 1.f; + } + if (mV[1] < 0.f) + { + mV[1] = 0.f; + } + else if (mV[1] > 1.f) + { + mV[1] = 1.f; + } + if (mV[2] < 0.f) + { + mV[2] = 0.f; + } + else if (mV[2] > 1.f) + { + mV[2] = 1.f; + } +} + +// Non-member functions +F32 distVec(const LLColor3 &a, const LLColor3 &b); // Returns distance between a and b +F32 distVec_squared(const LLColor3 &a, const LLColor3 &b);// Returns distance sqaured between a and b + +inline LLColor3::LLColor3(void) +{ + mV[0] = 0.f; + mV[1] = 0.f; + mV[2] = 0.f; +} + +inline LLColor3::LLColor3(F32 r, F32 g, F32 b) +{ + mV[VX] = r; + mV[VY] = g; + mV[VZ] = b; +} + +inline LLColor3::LLColor3(const F32 *vec) +{ + mV[VX] = vec[VX]; + mV[VY] = vec[VY]; + mV[VZ] = vec[VZ]; +} + +inline LLColor3::LLColor3(char* color_string) // takes a string of format "RRGGBB" where RR is hex 00..FF +{ + if (strlen(color_string) < 6) + { + mV[0] = 0.f; + mV[1] = 0.f; + mV[2] = 0.f; + return; + } + + static char tempstr[7]; + strncpy(tempstr,color_string,6); + tempstr[6] = '\0'; + mV[VZ] = (F32)strtol(&tempstr[4],NULL,16)/255.f; + tempstr[4] = '\0'; + mV[VY] = (F32)strtol(&tempstr[2],NULL,16)/255.f; + tempstr[2] = '\0'; + mV[VX] = (F32)strtol(&tempstr[0],NULL,16)/255.f; +} + +inline const LLColor3& LLColor3::setToBlack(void) +{ + mV[0] = 0.f; + mV[1] = 0.f; + mV[2] = 0.f; + return (*this); +} + +inline const LLColor3& LLColor3::setToWhite(void) +{ + mV[0] = 1.f; + mV[1] = 1.f; + mV[2] = 1.f; + return (*this); +} + +inline const LLColor3& LLColor3::setVec(F32 r, F32 g, F32 b) +{ + mV[0] = r; + mV[1] = g; + mV[2] = b; + return (*this); +} + +inline const LLColor3& LLColor3::setVec(const LLColor3 &vec) +{ + mV[0] = vec.mV[0]; + mV[1] = vec.mV[1]; + mV[2] = vec.mV[2]; + return (*this); +} + +inline const LLColor3& LLColor3::setVec(const F32 *vec) +{ + mV[0] = vec[0]; + mV[1] = vec[1]; + mV[2] = vec[2]; + return (*this); +} + +inline F32 LLColor3::magVec(void) const +{ + return fsqrtf(mV[0]*mV[0] + mV[1]*mV[1] + mV[2]*mV[2]); +} + +inline F32 LLColor3::magVecSquared(void) const +{ + return mV[0]*mV[0] + mV[1]*mV[1] + mV[2]*mV[2]; +} + +inline F32 LLColor3::normVec(void) +{ + F32 mag = fsqrtf(mV[0]*mV[0] + mV[1]*mV[1] + mV[2]*mV[2]); + F32 oomag; + + if (mag) + { + oomag = 1.f/mag; + mV[0] *= oomag; + mV[1] *= oomag; + mV[2] *= oomag; + } + return (mag); +} + +inline void LLColor3::exp() +{ +#if 0 + mV[0] = ::exp(mV[0]); + mV[1] = ::exp(mV[1]); + mV[2] = ::exp(mV[2]); +#else + mV[0] = (F32)LL_FAST_EXP(mV[0]); + mV[1] = (F32)LL_FAST_EXP(mV[1]); + mV[2] = (F32)LL_FAST_EXP(mV[2]); +#endif +} + + +inline LLColor3 operator+(const LLColor3 &a, const LLColor3 &b) +{ + return LLColor3( + a.mV[0] + b.mV[0], + a.mV[1] + b.mV[1], + a.mV[2] + b.mV[2]); +} + +inline LLColor3 operator-(const LLColor3 &a, const LLColor3 &b) +{ + return LLColor3( + a.mV[0] - b.mV[0], + a.mV[1] - b.mV[1], + a.mV[2] - b.mV[2]); +} + +inline LLColor3 operator*(const LLColor3 &a, const LLColor3 &b) +{ + return LLColor3( + a.mV[0] * b.mV[0], + a.mV[1] * b.mV[1], + a.mV[2] * b.mV[2]); +} + +inline LLColor3 operator*(const LLColor3 &a, F32 k) +{ + return LLColor3( a.mV[0] * k, a.mV[1] * k, a.mV[2] * k ); +} + +inline LLColor3 operator*(F32 k, const LLColor3 &a) +{ + return LLColor3( a.mV[0] * k, a.mV[1] * k, a.mV[2] * k ); +} + +inline bool operator==(const LLColor3 &a, const LLColor3 &b) +{ + return ( (a.mV[0] == b.mV[0]) + &&(a.mV[1] == b.mV[1]) + &&(a.mV[2] == b.mV[2])); +} + +inline bool operator!=(const LLColor3 &a, const LLColor3 &b) +{ + return ( (a.mV[0] != b.mV[0]) + ||(a.mV[1] != b.mV[1]) + ||(a.mV[2] != b.mV[2])); +} + +inline const LLColor3 &operator*=(LLColor3 &a, const LLColor3 &b) +{ + a.mV[0] *= b.mV[0]; + a.mV[1] *= b.mV[1]; + a.mV[2] *= b.mV[2]; + return a; +} + +inline const LLColor3& operator+=(LLColor3 &a, const LLColor3 &b) +{ + a.mV[0] += b.mV[0]; + a.mV[1] += b.mV[1]; + a.mV[2] += b.mV[2]; + return a; +} + +inline const LLColor3& operator-=(LLColor3 &a, const LLColor3 &b) +{ + a.mV[0] -= b.mV[0]; + a.mV[1] -= b.mV[1]; + a.mV[2] -= b.mV[2]; + return a; +} + +inline const LLColor3& operator*=(LLColor3 &a, F32 k) +{ + a.mV[0] *= k; + a.mV[1] *= k; + a.mV[2] *= k; + return a; +} + +inline LLColor3 operator-(const LLColor3 &a) +{ + return LLColor3( + 1.f - a.mV[0], + 1.f - a.mV[1], + 1.f - a.mV[2] ); +} + +// Non-member functions + +inline F32 distVec(const LLColor3 &a, const LLColor3 &b) +{ + F32 x = a.mV[0] - b.mV[0]; + F32 y = a.mV[1] - b.mV[1]; + F32 z = a.mV[2] - b.mV[2]; + return fsqrtf( x*x + y*y + z*z ); +} + +inline F32 distVec_squared(const LLColor3 &a, const LLColor3 &b) +{ + F32 x = a.mV[0] - b.mV[0]; + F32 y = a.mV[1] - b.mV[1]; + F32 z = a.mV[2] - b.mV[2]; + return x*x + y*y + z*z; +} + +inline LLColor3 lerp(const LLColor3 &a, const LLColor3 &b, F32 u) +{ + return LLColor3( + a.mV[VX] + (b.mV[VX] - a.mV[VX]) * u, + a.mV[VY] + (b.mV[VY] - a.mV[VY]) * u, + a.mV[VZ] + (b.mV[VZ] - a.mV[VZ]) * u); +} + + +#endif diff --git a/indra/llmath/v3dmath.cpp b/indra/llmath/v3dmath.cpp new file mode 100644 index 0000000000..0e12389acd --- /dev/null +++ b/indra/llmath/v3dmath.cpp @@ -0,0 +1,129 @@ +/** + * @file v3dmath.cpp + * @brief LLVector3d class implementation. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +//#include // gcc 2.95.2 doesn't support sstream + +#include "v3dmath.h" + +//#include "vmath.h" +#include "v4math.h" +#include "m4math.h" +#include "m3math.h" +#include "llquaternion.h" +#include "llquantize.h" + +// LLVector3d +// WARNING: Don't use these for global const definitions! +// For example: +// const LLQuaternion(0.5f * F_PI, LLVector3d::zero); +// at the top of a *.cpp file might not give you what you think. +const LLVector3d LLVector3d::zero(0,0,0); +const LLVector3d LLVector3d::x_axis(1, 0, 0); +const LLVector3d LLVector3d::y_axis(0, 1, 0); +const LLVector3d LLVector3d::z_axis(0, 0, 1); +const LLVector3d LLVector3d::x_axis_neg(-1, 0, 0); +const LLVector3d LLVector3d::y_axis_neg(0, -1, 0); +const LLVector3d LLVector3d::z_axis_neg(0, 0, -1); + + +// Clamps each values to range (min,max). +// Returns TRUE if data changed. +BOOL LLVector3d::clamp(F64 min, F64 max) +{ + BOOL ret = FALSE; + + if (mdV[0] < min) { mdV[0] = min; ret = TRUE; } + if (mdV[1] < min) { mdV[1] = min; ret = TRUE; } + if (mdV[2] < min) { mdV[2] = min; ret = TRUE; } + + if (mdV[0] > max) { mdV[0] = max; ret = TRUE; } + if (mdV[1] > max) { mdV[1] = max; ret = TRUE; } + if (mdV[2] > max) { mdV[2] = max; ret = TRUE; } + + return ret; +} + +// Sets all values to absolute value of their original values +// Returns TRUE if data changed +BOOL LLVector3d::abs() +{ + BOOL ret = FALSE; + + if (mdV[0] < 0.0) { mdV[0] = -mdV[0]; ret = TRUE; } + if (mdV[1] < 0.0) { mdV[1] = -mdV[1]; ret = TRUE; } + if (mdV[2] < 0.0) { mdV[2] = -mdV[2]; ret = TRUE; } + + return ret; +} + +std::ostream& operator<<(std::ostream& s, const LLVector3d &a) +{ + s << "{ " << a.mdV[VX] << ", " << a.mdV[VY] << ", " << a.mdV[VZ] << " }"; + return s; +} + +const LLVector3d& LLVector3d::operator=(const LLVector4 &a) +{ + mdV[0] = a.mV[0]; + mdV[1] = a.mV[1]; + mdV[2] = a.mV[2]; + return *this; +} + +const LLVector3d& LLVector3d::rotVec(const LLMatrix3 &mat) +{ + *this = *this * mat; + return *this; +} + +const LLVector3d& LLVector3d::rotVec(const LLQuaternion &q) +{ + *this = *this * q; + return *this; +} + +const LLVector3d& LLVector3d::rotVec(F64 angle, const LLVector3d &vec) +{ + if ( !vec.isExactlyZero() && angle ) + { + *this = *this * LLMatrix3((F32)angle, vec); + } + return *this; +} + +const LLVector3d& LLVector3d::rotVec(F64 angle, F64 x, F64 y, F64 z) +{ + LLVector3d vec(x, y, z); + if ( !vec.isExactlyZero() && angle ) + { + *this = *this * LLMatrix3((F32)angle, vec); + } + return *this; +} + + +BOOL LLVector3d::parseVector3d(const char* buf, LLVector3d* value) +{ + if( buf == NULL || buf[0] == '\0' || value == NULL) + { + return FALSE; + } + + LLVector3d v; + S32 count = sscanf( buf, "%lf %lf %lf", v.mdV + 0, v.mdV + 1, v.mdV + 2 ); + if( 3 == count ) + { + value->setVec( v ); + return TRUE; + } + + return FALSE; +} + diff --git a/indra/llmath/v3dmath.h b/indra/llmath/v3dmath.h new file mode 100644 index 0000000000..d8feb10757 --- /dev/null +++ b/indra/llmath/v3dmath.h @@ -0,0 +1,409 @@ +/** + * @file v3dmath.h + * @brief High precision 3 dimensional vector. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_V3DMATH_H +#define LL_V3DMATH_H + +#include "llerror.h" +#include "v3math.h" + +class LLVector3d +{ + public: + F64 mdV[3]; + + const static LLVector3d zero; + const static LLVector3d x_axis; + const static LLVector3d y_axis; + const static LLVector3d z_axis; + const static LLVector3d x_axis_neg; + const static LLVector3d y_axis_neg; + const static LLVector3d z_axis_neg; + + inline LLVector3d(); // Initializes LLVector3d to (0, 0, 0) + inline LLVector3d(const F64 x, const F64 y, const F64 z); // Initializes LLVector3d to (x. y, z) + inline explicit LLVector3d(const F64 *vec); // Initializes LLVector3d to (vec[0]. vec[1], vec[2]) + inline explicit LLVector3d(const LLVector3 &vec); + LLVector3d(const LLSD& sd) + { + setValue(sd); + } + + void setValue(const LLSD& sd) + { + mdV[0] = sd[0].asReal(); + mdV[1] = sd[1].asReal(); + mdV[2] = sd[2].asReal(); + } + + const LLVector3d& operator=(const LLSD& sd) + { + setValue(sd); + return *this; + } + + LLSD getValue() const + { + LLSD ret; + ret[0] = mdV[0]; + ret[1] = mdV[1]; + ret[2] = mdV[2]; + return ret; + } + + inline BOOL isFinite() const; // checks to see if all values of LLVector3d are finite + BOOL clamp(const F64 min, const F64 max); // Clamps all values to (min,max), returns TRUE if data changed + BOOL abs(); // sets all values to absolute value of original value (first octant), returns TRUE if changed + + inline const LLVector3d& clearVec(); // Clears LLVector3d to (0, 0, 0, 1) + inline const LLVector3d& zeroVec(); // Zero LLVector3d to (0, 0, 0, 0) + inline const LLVector3d& setVec(const F64 x, const F64 y, const F64 z); // Sets LLVector3d to (x, y, z, 1) + inline const LLVector3d& setVec(const LLVector3d &vec); // Sets LLVector3d to vec + inline const LLVector3d& setVec(const F64 *vec); // Sets LLVector3d to vec + inline const LLVector3d& setVec(const LLVector3 &vec); + + F64 magVec() const; // Returns magnitude of LLVector3d + F64 magVecSquared() const; // Returns magnitude squared of LLVector3d + inline F64 normVec(); // Normalizes and returns the magnitude of LLVector3d + + const LLVector3d& rotVec(const F64 angle, const LLVector3d &vec); // Rotates about vec by angle radians + const LLVector3d& rotVec(const F64 angle, const F64 x, const F64 y, const F64 z); // Rotates about x,y,z by angle radians + const LLVector3d& rotVec(const LLMatrix3 &mat); // Rotates by LLMatrix4 mat + const LLVector3d& rotVec(const LLQuaternion &q); // Rotates by LLQuaternion q + + BOOL isNull() const; // Returns TRUE if vector has a _very_small_ length + BOOL isExactlyZero() const { return !mdV[VX] && !mdV[VY] && !mdV[VZ]; } + + const LLVector3d& operator=(const LLVector4 &a); + + F64 operator[](int idx) const { return mdV[idx]; } + F64 &operator[](int idx) { return mdV[idx]; } + + friend LLVector3d operator+(const LLVector3d &a, const LLVector3d &b); // Return vector a + b + friend LLVector3d operator-(const LLVector3d &a, const LLVector3d &b); // Return vector a minus b + friend F64 operator*(const LLVector3d &a, const LLVector3d &b); // Return a dot b + friend LLVector3d operator%(const LLVector3d &a, const LLVector3d &b); // Return a cross b + friend LLVector3d operator*(const LLVector3d &a, const F64 k); // Return a times scaler k + friend LLVector3d operator/(const LLVector3d &a, const F64 k); // Return a divided by scaler k + friend LLVector3d operator*(const F64 k, const LLVector3d &a); // Return a times scaler k + friend bool operator==(const LLVector3d &a, const LLVector3d &b); // Return a == b + friend bool operator!=(const LLVector3d &a, const LLVector3d &b); // Return a != b + + friend const LLVector3d& operator+=(LLVector3d &a, const LLVector3d &b); // Return vector a + b + friend const LLVector3d& operator-=(LLVector3d &a, const LLVector3d &b); // Return vector a minus b + friend const LLVector3d& operator%=(LLVector3d &a, const LLVector3d &b); // Return a cross b + friend const LLVector3d& operator*=(LLVector3d &a, const F64 k); // Return a times scaler k + friend const LLVector3d& operator/=(LLVector3d &a, const F64 k); // Return a divided by scaler k + + friend LLVector3d operator-(const LLVector3d &a); // Return vector -a + + friend std::ostream& operator<<(std::ostream& s, const LLVector3d &a); // Stream a + + static BOOL parseVector3d(const char* buf, LLVector3d* value); + +}; + +typedef LLVector3d LLGlobalVec; + +const LLVector3d &LLVector3d::setVec(const LLVector3 &vec) +{ + mdV[0] = vec.mV[0]; + mdV[1] = vec.mV[1]; + mdV[2] = vec.mV[2]; + return *this; +} + + +inline LLVector3d::LLVector3d(void) +{ + mdV[0] = 0.f; + mdV[1] = 0.f; + mdV[2] = 0.f; +} + +inline LLVector3d::LLVector3d(const F64 x, const F64 y, const F64 z) +{ + mdV[VX] = x; + mdV[VY] = y; + mdV[VZ] = z; +} + +inline LLVector3d::LLVector3d(const F64 *vec) +{ + mdV[VX] = vec[VX]; + mdV[VY] = vec[VY]; + mdV[VZ] = vec[VZ]; +} + +inline LLVector3d::LLVector3d(const LLVector3 &vec) +{ + mdV[VX] = vec.mV[VX]; + mdV[VY] = vec.mV[VY]; + mdV[VZ] = vec.mV[VZ]; +} + +/* +inline LLVector3d::LLVector3d(const LLVector3d ©) +{ + mdV[VX] = copy.mdV[VX]; + mdV[VY] = copy.mdV[VY]; + mdV[VZ] = copy.mdV[VZ]; +} +*/ + +// Destructors + +// checker +inline BOOL LLVector3d::isFinite() const +{ + return (llfinite(mdV[VX]) && llfinite(mdV[VY]) && llfinite(mdV[VZ])); +} + + +// Clear and Assignment Functions + +inline const LLVector3d& LLVector3d::clearVec(void) +{ + mdV[0] = 0.f; + mdV[1] = 0.f; + mdV[2]= 0.f; + return (*this); +} + +inline const LLVector3d& LLVector3d::zeroVec(void) +{ + mdV[0] = 0.f; + mdV[1] = 0.f; + mdV[2] = 0.f; + return (*this); +} + +inline const LLVector3d& LLVector3d::setVec(const F64 x, const F64 y, const F64 z) +{ + mdV[VX] = x; + mdV[VY] = y; + mdV[VZ] = z; + return (*this); +} + +inline const LLVector3d& LLVector3d::setVec(const LLVector3d &vec) +{ + mdV[0] = vec.mdV[0]; + mdV[1] = vec.mdV[1]; + mdV[2] = vec.mdV[2]; + return (*this); +} + +inline const LLVector3d& LLVector3d::setVec(const F64 *vec) +{ + mdV[0] = vec[0]; + mdV[1] = vec[1]; + mdV[2] = vec[2]; + return (*this); +} + +inline F64 LLVector3d::normVec(void) +{ + F64 mag = fsqrtf(mdV[0]*mdV[0] + mdV[1]*mdV[1] + mdV[2]*mdV[2]); + F64 oomag; + + if (mag > FP_MAG_THRESHOLD) + { + oomag = 1.f/mag; + mdV[0] *= oomag; + mdV[1] *= oomag; + mdV[2] *= oomag; + } + else + { + mdV[0] = 0.f; + mdV[1] = 0.f; + mdV[2] = 0.f; + mag = 0; + } + return (mag); +} + +// LLVector3d Magnitude and Normalization Functions + +inline F64 LLVector3d::magVec(void) const +{ + return fsqrtf(mdV[0]*mdV[0] + mdV[1]*mdV[1] + mdV[2]*mdV[2]); +} + +inline F64 LLVector3d::magVecSquared(void) const +{ + return mdV[0]*mdV[0] + mdV[1]*mdV[1] + mdV[2]*mdV[2]; +} + +inline LLVector3d operator+(const LLVector3d &a, const LLVector3d &b) +{ + LLVector3d c(a); + return c += b; +} + +inline LLVector3d operator-(const LLVector3d &a, const LLVector3d &b) +{ + LLVector3d c(a); + return c -= b; +} + +inline F64 operator*(const LLVector3d &a, const LLVector3d &b) +{ + return (a.mdV[0]*b.mdV[0] + a.mdV[1]*b.mdV[1] + a.mdV[2]*b.mdV[2]); +} + +inline LLVector3d operator%(const LLVector3d &a, const LLVector3d &b) +{ + return LLVector3d( a.mdV[1]*b.mdV[2] - b.mdV[1]*a.mdV[2], a.mdV[2]*b.mdV[0] - b.mdV[2]*a.mdV[0], a.mdV[0]*b.mdV[1] - b.mdV[0]*a.mdV[1] ); +} + +inline LLVector3d operator/(const LLVector3d &a, const F64 k) +{ + F64 t = 1.f / k; + return LLVector3d( a.mdV[0] * t, a.mdV[1] * t, a.mdV[2] * t ); +} + +inline LLVector3d operator*(const LLVector3d &a, const F64 k) +{ + return LLVector3d( a.mdV[0] * k, a.mdV[1] * k, a.mdV[2] * k ); +} + +inline LLVector3d operator*(F64 k, const LLVector3d &a) +{ + return LLVector3d( a.mdV[0] * k, a.mdV[1] * k, a.mdV[2] * k ); +} + +inline bool operator==(const LLVector3d &a, const LLVector3d &b) +{ + return ( (a.mdV[0] == b.mdV[0]) + &&(a.mdV[1] == b.mdV[1]) + &&(a.mdV[2] == b.mdV[2])); +} + +inline bool operator!=(const LLVector3d &a, const LLVector3d &b) +{ + return ( (a.mdV[0] != b.mdV[0]) + ||(a.mdV[1] != b.mdV[1]) + ||(a.mdV[2] != b.mdV[2])); +} + +inline const LLVector3d& operator+=(LLVector3d &a, const LLVector3d &b) +{ + a.mdV[0] += b.mdV[0]; + a.mdV[1] += b.mdV[1]; + a.mdV[2] += b.mdV[2]; + return a; +} + +inline const LLVector3d& operator-=(LLVector3d &a, const LLVector3d &b) +{ + a.mdV[0] -= b.mdV[0]; + a.mdV[1] -= b.mdV[1]; + a.mdV[2] -= b.mdV[2]; + return a; +} + +inline const LLVector3d& operator%=(LLVector3d &a, const LLVector3d &b) +{ + LLVector3d ret( a.mdV[1]*b.mdV[2] - b.mdV[1]*a.mdV[2], a.mdV[2]*b.mdV[0] - b.mdV[2]*a.mdV[0], a.mdV[0]*b.mdV[1] - b.mdV[0]*a.mdV[1]); + a = ret; + return a; +} + +inline const LLVector3d& operator*=(LLVector3d &a, const F64 k) +{ + a.mdV[0] *= k; + a.mdV[1] *= k; + a.mdV[2] *= k; + return a; +} + +inline const LLVector3d& operator/=(LLVector3d &a, const F64 k) +{ + F64 t = 1.f / k; + a.mdV[0] *= t; + a.mdV[1] *= t; + a.mdV[2] *= t; + return a; +} + +inline LLVector3d operator-(const LLVector3d &a) +{ + return LLVector3d( -a.mdV[0], -a.mdV[1], -a.mdV[2] ); +} + +inline F64 dist_vec(const LLVector3d &a, const LLVector3d &b) +{ + F64 x = a.mdV[0] - b.mdV[0]; + F64 y = a.mdV[1] - b.mdV[1]; + F64 z = a.mdV[2] - b.mdV[2]; + return fsqrtf( x*x + y*y + z*z ); +} + +inline F64 dist_vec_squared(const LLVector3d &a, const LLVector3d &b) +{ + F64 x = a.mdV[0] - b.mdV[0]; + F64 y = a.mdV[1] - b.mdV[1]; + F64 z = a.mdV[2] - b.mdV[2]; + return x*x + y*y + z*z; +} + +inline F64 dist_vec_squared2D(const LLVector3d &a, const LLVector3d &b) +{ + F64 x = a.mdV[0] - b.mdV[0]; + F64 y = a.mdV[1] - b.mdV[1]; + return x*x + y*y; +} + +inline LLVector3d lerp(const LLVector3d &a, const LLVector3d &b, const F64 u) +{ + return LLVector3d( + a.mdV[VX] + (b.mdV[VX] - a.mdV[VX]) * u, + a.mdV[VY] + (b.mdV[VY] - a.mdV[VY]) * u, + a.mdV[VZ] + (b.mdV[VZ] - a.mdV[VZ]) * u); +} + + +inline BOOL LLVector3d::isNull() const +{ + if ( F_APPROXIMATELY_ZERO > mdV[VX]*mdV[VX] + mdV[VY]*mdV[VY] + mdV[VZ]*mdV[VZ] ) + { + return TRUE; + } + return FALSE; +} + + +inline F64 angle_between(const LLVector3d& a, const LLVector3d& b) +{ + LLVector3d an = a; + LLVector3d bn = b; + an.normVec(); + bn.normVec(); + F64 cosine = an * bn; + F64 angle = (cosine >= 1.0f) ? 0.0f : + (cosine <= -1.0f) ? F_PI : + acos(cosine); + return angle; +} + +inline BOOL are_parallel(const LLVector3d &a, const LLVector3d &b, const F64 epsilon) +{ + LLVector3d an = a; + LLVector3d bn = b; + an.normVec(); + bn.normVec(); + F64 dot = an * bn; + if ( (1.0f - fabs(dot)) < epsilon) + { + return TRUE; + } + return FALSE; +} +#endif // LL_V3DMATH_H diff --git a/indra/llmath/v3math.cpp b/indra/llmath/v3math.cpp new file mode 100644 index 0000000000..f254f4112e --- /dev/null +++ b/indra/llmath/v3math.cpp @@ -0,0 +1,213 @@ +/** + * @file v3math.cpp + * @brief LLVector3 class implementation. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "v3math.h" + +//#include "vmath.h" +#include "v4math.h" +#include "m4math.h" +#include "m3math.h" +#include "llquaternion.h" +#include "llquantize.h" +#include "v3dmath.h" + +// LLVector3 +// WARNING: Don't use these for global const definitions! +// For example: +// const LLQuaternion(0.5f * F_PI, LLVector3::zero); +// at the top of a *.cpp file might not give you what you think. +const LLVector3 LLVector3::zero(0,0,0); +const LLVector3 LLVector3::x_axis(1.f, 0, 0); +const LLVector3 LLVector3::y_axis(0, 1.f, 0); +const LLVector3 LLVector3::z_axis(0, 0, 1.f); +const LLVector3 LLVector3::x_axis_neg(-1.f, 0, 0); +const LLVector3 LLVector3::y_axis_neg(0, -1.f, 0); +const LLVector3 LLVector3::z_axis_neg(0, 0, -1.f); +const LLVector3 LLVector3::all_one(1.f,1.f,1.f); + + +// Clamps each values to range (min,max). +// Returns TRUE if data changed. +BOOL LLVector3::clamp(F32 min, F32 max) +{ + BOOL ret = FALSE; + + if (mV[0] < min) { mV[0] = min; ret = TRUE; } + if (mV[1] < min) { mV[1] = min; ret = TRUE; } + if (mV[2] < min) { mV[2] = min; ret = TRUE; } + + if (mV[0] > max) { mV[0] = max; ret = TRUE; } + if (mV[1] > max) { mV[1] = max; ret = TRUE; } + if (mV[2] > max) { mV[2] = max; ret = TRUE; } + + return ret; +} + +// Sets all values to absolute value of their original values +// Returns TRUE if data changed +BOOL LLVector3::abs() +{ + BOOL ret = FALSE; + + if (mV[0] < 0.f) { mV[0] = -mV[0]; ret = TRUE; } + if (mV[1] < 0.f) { mV[1] = -mV[1]; ret = TRUE; } + if (mV[2] < 0.f) { mV[2] = -mV[2]; ret = TRUE; } + + return ret; +} + +// Quatizations +void LLVector3::quantize16(F32 lowerxy, F32 upperxy, F32 lowerz, F32 upperz) +{ + F32 x = mV[VX]; + F32 y = mV[VY]; + F32 z = mV[VZ]; + + x = U16_to_F32(F32_to_U16(x, lowerxy, upperxy), lowerxy, upperxy); + y = U16_to_F32(F32_to_U16(y, lowerxy, upperxy), lowerxy, upperxy); + z = U16_to_F32(F32_to_U16(z, lowerz, upperz), lowerz, upperz); + + mV[VX] = x; + mV[VY] = y; + mV[VZ] = z; +} + +void LLVector3::quantize8(F32 lowerxy, F32 upperxy, F32 lowerz, F32 upperz) +{ + mV[VX] = U8_to_F32(F32_to_U8(mV[VX], lowerxy, upperxy), lowerxy, upperxy);; + mV[VY] = U8_to_F32(F32_to_U8(mV[VY], lowerxy, upperxy), lowerxy, upperxy); + mV[VZ] = U8_to_F32(F32_to_U8(mV[VZ], lowerz, upperz), lowerz, upperz); +} + + +void LLVector3::snap(S32 sig_digits) +{ + mV[VX] = snap_to_sig_figs(mV[VX], sig_digits); + mV[VY] = snap_to_sig_figs(mV[VY], sig_digits); + mV[VZ] = snap_to_sig_figs(mV[VZ], sig_digits); +} + + +std::ostream& operator<<(std::ostream& s, const LLVector3 &a) +{ + s << "{ " << a.mV[VX] << ", " << a.mV[VY] << ", " << a.mV[VZ] << " }"; + return s; +} + + +const LLVector3& LLVector3::rotVec(const LLMatrix3 &mat) +{ + *this = *this * mat; + return *this; +} + +const LLVector3& LLVector3::rotVec(const LLQuaternion &q) +{ + *this = *this * q; + return *this; +} + +const LLVector3& LLVector3::rotVec(F32 angle, const LLVector3 &vec) +{ + if ( !vec.isExactlyZero() && angle ) + { + *this = *this * LLMatrix3(angle, vec); + } + return *this; +} + +const LLVector3& LLVector3::rotVec(F32 angle, F32 x, F32 y, F32 z) +{ + LLVector3 vec(x, y, z); + if ( !vec.isExactlyZero() && angle ) + { + *this = *this * LLMatrix3(angle, vec); + } + return *this; +} + +const LLVector3& LLVector3::scaleVec(const LLVector3& vec) +{ + mV[VX] *= vec.mV[VX]; + mV[VY] *= vec.mV[VY]; + mV[VZ] *= vec.mV[VZ]; + + return *this; +} + +LLVector3 LLVector3::scaledVec(const LLVector3& vec) const +{ + LLVector3 ret = LLVector3(*this); + ret.scaleVec(vec); + return ret; +} + +const LLVector3& LLVector3::setVec(const LLVector3d &vec) +{ + mV[0] = (F32)vec.mdV[0]; + mV[1] = (F32)vec.mdV[1]; + mV[2] = (F32)vec.mdV[2]; + return (*this); +} + +const LLVector3& LLVector3::setVec(const LLVector4 &vec) +{ + mV[0] = vec.mV[0]; + mV[1] = vec.mV[1]; + mV[2] = vec.mV[2]; + return (*this); +} + +LLVector3::LLVector3(const LLVector3d &vec) +{ + mV[VX] = (F32)vec.mdV[VX]; + mV[VY] = (F32)vec.mdV[VY]; + mV[VZ] = (F32)vec.mdV[VZ]; +} + +LLVector3::LLVector3(const LLVector4 &vec) +{ + mV[VX] = (F32)vec.mV[VX]; + mV[VY] = (F32)vec.mV[VY]; + mV[VZ] = (F32)vec.mV[VZ]; +} + +const LLVector3& operator*=(LLVector3 &a, const LLQuaternion &rot) +{ + const F32 rw = - rot.mQ[VX] * a.mV[VX] - rot.mQ[VY] * a.mV[VY] - rot.mQ[VZ] * a.mV[VZ]; + const F32 rx = rot.mQ[VW] * a.mV[VX] + rot.mQ[VY] * a.mV[VZ] - rot.mQ[VZ] * a.mV[VY]; + const F32 ry = rot.mQ[VW] * a.mV[VY] + rot.mQ[VZ] * a.mV[VX] - rot.mQ[VX] * a.mV[VZ]; + const F32 rz = rot.mQ[VW] * a.mV[VZ] + rot.mQ[VX] * a.mV[VY] - rot.mQ[VY] * a.mV[VX]; + + a.mV[VX] = - rw * rot.mQ[VX] + rx * rot.mQ[VW] - ry * rot.mQ[VZ] + rz * rot.mQ[VY]; + a.mV[VY] = - rw * rot.mQ[VY] + ry * rot.mQ[VW] - rz * rot.mQ[VX] + rx * rot.mQ[VZ]; + a.mV[VZ] = - rw * rot.mQ[VZ] + rz * rot.mQ[VW] - rx * rot.mQ[VY] + ry * rot.mQ[VX]; + + return a; +} + +// static +BOOL LLVector3::parseVector3(const char* buf, LLVector3* value) +{ + if( buf == NULL || buf[0] == '\0' || value == NULL) + { + return FALSE; + } + + LLVector3 v; + S32 count = sscanf( buf, "%f %f %f", v.mV + 0, v.mV + 1, v.mV + 2 ); + if( 3 == count ) + { + value->setVec( v ); + return TRUE; + } + + return FALSE; +} diff --git a/indra/llmath/v3math.h b/indra/llmath/v3math.h new file mode 100644 index 0000000000..09042b6dc3 --- /dev/null +++ b/indra/llmath/v3math.h @@ -0,0 +1,446 @@ +/** + * @file v3math.h + * @brief LLVector3 class header file. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_V3MATH_H +#define LL_V3MATH_H + +#include "llerror.h" +#include "llmath.h" + +#include "llsd.h" +class LLVector4; +class LLMatrix3; +class LLVector3d; +class LLQuaternion; + +// Llvector3 = |x y z w| + +static const U32 LENGTHOFVECTOR3 = 3; + +class LLVector3 +{ + public: + F32 mV[LENGTHOFVECTOR3]; + + static const LLVector3 zero; + static const LLVector3 x_axis; + static const LLVector3 y_axis; + static const LLVector3 z_axis; + static const LLVector3 x_axis_neg; + static const LLVector3 y_axis_neg; + static const LLVector3 z_axis_neg; + static const LLVector3 all_one; + + inline LLVector3(); // Initializes LLVector3 to (0, 0, 0) + inline LLVector3(const F32 x, const F32 y, const F32 z); // Initializes LLVector3 to (x. y, z) + inline explicit LLVector3(const F32 *vec); // Initializes LLVector3 to (vec[0]. vec[1], vec[2]) + explicit LLVector3(const LLVector3d &vec); // Initializes LLVector3 to (vec[0]. vec[1], vec[2]) + explicit LLVector3(const LLVector4 &vec); // Initializes LLVector4 to (vec[0]. vec[1], vec[2]) + LLVector3(const LLSD& sd) + { + setValue(sd); + } + + LLSD getValue() const + { + LLSD ret; + ret[0] = mV[0]; + ret[1] = mV[1]; + ret[2] = mV[2]; + return ret; + } + + void setValue(const LLSD& sd) + { + mV[0] = (F32) sd[0].asReal(); + mV[1] = (F32) sd[1].asReal(); + mV[2] = (F32) sd[2].asReal(); + } + + const LLVector3& operator=(const LLSD& sd) + { + setValue(sd); + return *this; + } + + inline BOOL isFinite() const; // checks to see if all values of LLVector3 are finite + BOOL clamp(F32 min, F32 max); // Clamps all values to (min,max), returns TRUE if data changed + + void quantize16(F32 lowerxy, F32 upperxy, F32 lowerz, F32 upperz); // changes the vector to reflect quatization + void quantize8(F32 lowerxy, F32 upperxy, F32 lowerz, F32 upperz); // changes the vector to reflect quatization + void snap(S32 sig_digits); // snaps x,y,z to sig_digits decimal places + + BOOL abs(); // sets all values to absolute value of original value (first octant), returns TRUE if changed + + inline void clearVec(); // Clears LLVector3 to (0, 0, 0, 1) + inline void zeroVec(); // Zero LLVector3 to (0, 0, 0, 0) + inline void setVec(F32 x, F32 y, F32 z); // Sets LLVector3 to (x, y, z, 1) + inline void setVec(const LLVector3 &vec); // Sets LLVector3 to vec + inline void setVec(const F32 *vec); // Sets LLVector3 to vec + + const LLVector3& setVec(const LLVector4 &vec); + const LLVector3& setVec(const LLVector3d &vec); // Sets LLVector3 to vec + + F32 magVec() const; // Returns magnitude of LLVector3 + F32 magVecSquared() const; // Returns magnitude squared of LLVector3 + inline F32 normVec(); // Normalizes and returns the magnitude of LLVector3 + + const LLVector3& rotVec(F32 angle, const LLVector3 &vec); // Rotates about vec by angle radians + const LLVector3& rotVec(F32 angle, F32 x, F32 y, F32 z); // Rotates about x,y,z by angle radians + const LLVector3& rotVec(const LLMatrix3 &mat); // Rotates by LLMatrix4 mat + const LLVector3& rotVec(const LLQuaternion &q); // Rotates by LLQuaternion q + + const LLVector3& scaleVec(const LLVector3& vec); // scales per component by vec + LLVector3 scaledVec(const LLVector3& vec) const; // get a copy of this vector scaled by vec + + BOOL isNull() const; // Returns TRUE if vector has a _very_small_ length + BOOL isExactlyZero() const { return !mV[VX] && !mV[VY] && !mV[VZ]; } + + F32 operator[](int idx) const { return mV[idx]; } + F32 &operator[](int idx) { return mV[idx]; } + + friend LLVector3 operator+(const LLVector3 &a, const LLVector3 &b); // Return vector a + b + friend LLVector3 operator-(const LLVector3 &a, const LLVector3 &b); // Return vector a minus b + friend F32 operator*(const LLVector3 &a, const LLVector3 &b); // Return a dot b + friend LLVector3 operator%(const LLVector3 &a, const LLVector3 &b); // Return a cross b + friend LLVector3 operator*(const LLVector3 &a, F32 k); // Return a times scaler k + friend LLVector3 operator/(const LLVector3 &a, F32 k); // Return a divided by scaler k + friend LLVector3 operator*(F32 k, const LLVector3 &a); // Return a times scaler k + friend bool operator==(const LLVector3 &a, const LLVector3 &b); // Return a == b + friend bool operator!=(const LLVector3 &a, const LLVector3 &b); // Return a != b + // less than operator useful for using vectors as std::map keys + friend bool operator<(const LLVector3 &a, const LLVector3 &b); // Return a < b + + friend const LLVector3& operator+=(LLVector3 &a, const LLVector3 &b); // Return vector a + b + friend const LLVector3& operator-=(LLVector3 &a, const LLVector3 &b); // Return vector a minus b + friend const LLVector3& operator%=(LLVector3 &a, const LLVector3 &b); // Return a cross b + friend const LLVector3& operator*=(LLVector3 &a, const LLVector3 &b); // Returns a * b; + friend const LLVector3& operator*=(LLVector3 &a, F32 k); // Return a times scaler k + friend const LLVector3& operator/=(LLVector3 &a, F32 k); // Return a divided by scaler k + friend const LLVector3& operator*=(LLVector3 &a, const LLQuaternion &b); // Returns a * b; + + friend LLVector3 operator-(const LLVector3 &a); // Return vector -a + + friend std::ostream& operator<<(std::ostream& s, const LLVector3 &a); // Stream a + + static BOOL parseVector3(const char* buf, LLVector3* value); +}; + +typedef LLVector3 LLSimLocalVec; + +// Non-member functions + +F32 angle_between(const LLVector3 &a, const LLVector3 &b); // Returns angle (radians) between a and b +BOOL are_parallel(const LLVector3 &a, const LLVector3 &b, F32 epsilon=F_APPROXIMATELY_ZERO); // Returns TRUE if a and b are very close to parallel +F32 dist_vec(const LLVector3 &a, const LLVector3 &b); // Returns distance between a and b +F32 dist_vec_squared(const LLVector3 &a, const LLVector3 &b);// Returns distance sqaured between a and b +F32 dist_vec_squared2D(const LLVector3 &a, const LLVector3 &b);// Returns distance sqaured between a and b ignoring Z component +LLVector3 projected_vec(const LLVector3 &a, const LLVector3 &b); // Returns vector a projected on vector b +LLVector3 lerp(const LLVector3 &a, const LLVector3 &b, F32 u); // Returns a vector that is a linear interpolation between a and b + +inline LLVector3::LLVector3(void) +{ + mV[0] = 0.f; + mV[1] = 0.f; + mV[2] = 0.f; +} + +inline LLVector3::LLVector3(const F32 x, const F32 y, const F32 z) +{ + mV[VX] = x; + mV[VY] = y; + mV[VZ] = z; +} + +inline LLVector3::LLVector3(const F32 *vec) +{ + mV[VX] = vec[VX]; + mV[VY] = vec[VY]; + mV[VZ] = vec[VZ]; +} + +/* +inline LLVector3::LLVector3(const LLVector3 ©) +{ + mV[VX] = copy.mV[VX]; + mV[VY] = copy.mV[VY]; + mV[VZ] = copy.mV[VZ]; +} +*/ + +// Destructors + +// checker +inline BOOL LLVector3::isFinite() const +{ + return (llfinite(mV[VX]) && llfinite(mV[VY]) && llfinite(mV[VZ])); +} + + +// Clear and Assignment Functions + +inline void LLVector3::clearVec(void) +{ + mV[0] = 0.f; + mV[1] = 0.f; + mV[2] = 0.f; +} + +inline void LLVector3::zeroVec(void) +{ + mV[0] = 0.f; + mV[1] = 0.f; + mV[2] = 0.f; +} + +inline void LLVector3::setVec(F32 x, F32 y, F32 z) +{ + mV[VX] = x; + mV[VY] = y; + mV[VZ] = z; +} + +inline void LLVector3::setVec(const LLVector3 &vec) +{ + mV[0] = vec.mV[0]; + mV[1] = vec.mV[1]; + mV[2] = vec.mV[2]; +} + +inline void LLVector3::setVec(const F32 *vec) +{ + mV[0] = vec[0]; + mV[1] = vec[1]; + mV[2] = vec[2]; +} + +inline F32 LLVector3::normVec(void) +{ + F32 mag = fsqrtf(mV[0]*mV[0] + mV[1]*mV[1] + mV[2]*mV[2]); + F32 oomag; + + if (mag > FP_MAG_THRESHOLD) + { + oomag = 1.f/mag; + mV[0] *= oomag; + mV[1] *= oomag; + mV[2] *= oomag; + } + else + { + mV[0] = 0.f; + mV[1] = 0.f; + mV[2] = 0.f; + mag = 0; + } + return (mag); +} + +// LLVector3 Magnitude and Normalization Functions + +inline F32 LLVector3::magVec(void) const +{ + return fsqrtf(mV[0]*mV[0] + mV[1]*mV[1] + mV[2]*mV[2]); +} + +inline F32 LLVector3::magVecSquared(void) const +{ + return mV[0]*mV[0] + mV[1]*mV[1] + mV[2]*mV[2]; +} + +inline LLVector3 operator+(const LLVector3 &a, const LLVector3 &b) +{ + LLVector3 c(a); + return c += b; +} + +inline LLVector3 operator-(const LLVector3 &a, const LLVector3 &b) +{ + LLVector3 c(a); + return c -= b; +} + +inline F32 operator*(const LLVector3 &a, const LLVector3 &b) +{ + return (a.mV[0]*b.mV[0] + a.mV[1]*b.mV[1] + a.mV[2]*b.mV[2]); +} + +inline LLVector3 operator%(const LLVector3 &a, const LLVector3 &b) +{ + return LLVector3( a.mV[1]*b.mV[2] - b.mV[1]*a.mV[2], a.mV[2]*b.mV[0] - b.mV[2]*a.mV[0], a.mV[0]*b.mV[1] - b.mV[0]*a.mV[1] ); +} + +inline LLVector3 operator/(const LLVector3 &a, F32 k) +{ + F32 t = 1.f / k; + return LLVector3( a.mV[0] * t, a.mV[1] * t, a.mV[2] * t ); +} + +inline LLVector3 operator*(const LLVector3 &a, F32 k) +{ + return LLVector3( a.mV[0] * k, a.mV[1] * k, a.mV[2] * k ); +} + +inline LLVector3 operator*(F32 k, const LLVector3 &a) +{ + return LLVector3( a.mV[0] * k, a.mV[1] * k, a.mV[2] * k ); +} + +inline bool operator==(const LLVector3 &a, const LLVector3 &b) +{ + return ( (a.mV[0] == b.mV[0]) + &&(a.mV[1] == b.mV[1]) + &&(a.mV[2] == b.mV[2])); +} + +inline bool operator!=(const LLVector3 &a, const LLVector3 &b) +{ + return ( (a.mV[0] != b.mV[0]) + ||(a.mV[1] != b.mV[1]) + ||(a.mV[2] != b.mV[2])); +} + +inline bool operator<(const LLVector3 &a, const LLVector3 &b) +{ + return (a.mV[0] < b.mV[0] + || (a.mV[0] == b.mV[0] + && (a.mV[1] < b.mV[1] + || (a.mV[1] == b.mV[1]) + && a.mV[2] < b.mV[2]))); +} + +inline const LLVector3& operator+=(LLVector3 &a, const LLVector3 &b) +{ + a.mV[0] += b.mV[0]; + a.mV[1] += b.mV[1]; + a.mV[2] += b.mV[2]; + return a; +} + +inline const LLVector3& operator-=(LLVector3 &a, const LLVector3 &b) +{ + a.mV[0] -= b.mV[0]; + a.mV[1] -= b.mV[1]; + a.mV[2] -= b.mV[2]; + return a; +} + +inline const LLVector3& operator%=(LLVector3 &a, const LLVector3 &b) +{ + LLVector3 ret( a.mV[1]*b.mV[2] - b.mV[1]*a.mV[2], a.mV[2]*b.mV[0] - b.mV[2]*a.mV[0], a.mV[0]*b.mV[1] - b.mV[0]*a.mV[1]); + a = ret; + return a; +} + +inline const LLVector3& operator*=(LLVector3 &a, F32 k) +{ + a.mV[0] *= k; + a.mV[1] *= k; + a.mV[2] *= k; + return a; +} + +inline const LLVector3& operator*=(LLVector3 &a, const LLVector3 &b) +{ + a.mV[0] *= b.mV[0]; + a.mV[1] *= b.mV[1]; + a.mV[2] *= b.mV[2]; + return a; +} + +inline const LLVector3& operator/=(LLVector3 &a, F32 k) +{ + F32 t = 1.f / k; + a.mV[0] *= t; + a.mV[1] *= t; + a.mV[2] *= t; + return a; +} + +inline LLVector3 operator-(const LLVector3 &a) +{ + return LLVector3( -a.mV[0], -a.mV[1], -a.mV[2] ); +} + +inline F32 dist_vec(const LLVector3 &a, const LLVector3 &b) +{ + F32 x = a.mV[0] - b.mV[0]; + F32 y = a.mV[1] - b.mV[1]; + F32 z = a.mV[2] - b.mV[2]; + return fsqrtf( x*x + y*y + z*z ); +} + +inline F32 dist_vec_squared(const LLVector3 &a, const LLVector3 &b) +{ + F32 x = a.mV[0] - b.mV[0]; + F32 y = a.mV[1] - b.mV[1]; + F32 z = a.mV[2] - b.mV[2]; + return x*x + y*y + z*z; +} + +inline F32 dist_vec_squared2D(const LLVector3 &a, const LLVector3 &b) +{ + F32 x = a.mV[0] - b.mV[0]; + F32 y = a.mV[1] - b.mV[1]; + return x*x + y*y; +} + +inline LLVector3 projected_vec(const LLVector3 &a, const LLVector3 &b) +{ + LLVector3 project_axis = b; + project_axis.normVec(); + return project_axis * (a * project_axis); +} + +inline LLVector3 lerp(const LLVector3 &a, const LLVector3 &b, F32 u) +{ + return LLVector3( + a.mV[VX] + (b.mV[VX] - a.mV[VX]) * u, + a.mV[VY] + (b.mV[VY] - a.mV[VY]) * u, + a.mV[VZ] + (b.mV[VZ] - a.mV[VZ]) * u); +} + + +inline BOOL LLVector3::isNull() const +{ + if ( F_APPROXIMATELY_ZERO > mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ] ) + { + return TRUE; + } + return FALSE; +} + + +inline F32 angle_between(const LLVector3& a, const LLVector3& b) +{ + LLVector3 an = a; + LLVector3 bn = b; + an.normVec(); + bn.normVec(); + F32 cosine = an * bn; + F32 angle = (cosine >= 1.0f) ? 0.0f : + (cosine <= -1.0f) ? F_PI : + (F32)acos(cosine); + return angle; +} + +inline BOOL are_parallel(const LLVector3 &a, const LLVector3 &b, F32 epsilon) +{ + LLVector3 an = a; + LLVector3 bn = b; + an.normVec(); + bn.normVec(); + F32 dot = an * bn; + if ( (1.0f - fabs(dot)) < epsilon) + { + return TRUE; + } + return FALSE; +} + +#endif diff --git a/indra/llmath/v4color.cpp b/indra/llmath/v4color.cpp new file mode 100644 index 0000000000..83870941df --- /dev/null +++ b/indra/llmath/v4color.cpp @@ -0,0 +1,561 @@ +/** + * @file v4color.cpp + * @brief LLColor4 class implementation. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llboost.h" + +#include "v4color.h" +#include "v4coloru.h" +#include "v3color.h" +//#include "vmath.h" +#include "llmath.h" + +// LLColor4 + +////////////////////////////////////////////////////////////////////////////// + +LLColor4 LLColor4::red( 1.f, 0.f, 0.f, 1.f); +LLColor4 LLColor4::green( 0.f, 1.f, 0.f, 1.f); +LLColor4 LLColor4::blue( 0.f, 0.f, 1.f, 1.f); +LLColor4 LLColor4::black( 0.f, 0.f, 0.f, 1.f); +LLColor4 LLColor4::yellow( 1.f, 1.f, 0.f, 1.f); +LLColor4 LLColor4::magenta( 1.0f, 0.0f, 1.0f, 1.0f); +LLColor4 LLColor4::cyan( 0.0f, 1.0f, 1.0f, 1.0f); +LLColor4 LLColor4::white( 1.f, 1.f, 1.f, 1.f); +LLColor4 LLColor4::smoke( 0.5f, 0.5f, 0.5f, 0.5f); +LLColor4 LLColor4::grey( 0.5f, 0.5f, 0.5f, 1.0f); +LLColor4 LLColor4::orange( 1.f, 0.5, 0.f, 1.f ); +LLColor4 LLColor4::purple( 0.6f, 0.2f, 0.8f, 1.0f); +LLColor4 LLColor4::pink( 1.0f, 0.5f, 0.8f, 1.0f); +LLColor4 LLColor4::transparent( 0.f, 0.f, 0.f, 0.f ); + +////////////////////////////////////////////////////////////////////////////// + +LLColor4 LLColor4::grey1(0.8f, 0.8f, 0.8f, 1.0f); +LLColor4 LLColor4::grey2(0.6f, 0.6f, 0.6f, 1.0f); +LLColor4 LLColor4::grey3(0.4f, 0.4f, 0.4f, 1.0f); +LLColor4 LLColor4::grey4(0.3f, 0.3f, 0.3f, 1.0f); + +LLColor4 LLColor4::red1(1.0f, 0.0f, 0.0f, 1.0f); +LLColor4 LLColor4::red2(0.6f, 0.0f, 0.0f, 1.0f); +LLColor4 LLColor4::red3(1.0f, 0.2f, 0.2f, 1.0f); +LLColor4 LLColor4::red4(0.5f, 0.1f, 0.1f, 1.0f); +LLColor4 LLColor4::red5(0.8f, 0.1f, 0.0f, 1.0f); + +LLColor4 LLColor4::green1(0.0f, 1.0f, 0.0f, 1.0f); +LLColor4 LLColor4::green2(0.0f, 0.6f, 0.0f, 1.0f); +LLColor4 LLColor4::green3(0.0f, 0.4f, 0.0f, 1.0f); +LLColor4 LLColor4::green4(0.0f, 1.0f, 0.4f, 1.0f); +LLColor4 LLColor4::green5(0.2f, 0.6f, 0.4f, 1.0f); +LLColor4 LLColor4::green6(0.4f, 0.6f, 0.2f, 1.0f); + +LLColor4 LLColor4::blue1(0.0f, 0.0f, 1.0f, 1.0f); +LLColor4 LLColor4::blue2(0.0f, 0.4f, 1.0f, 1.0f); +LLColor4 LLColor4::blue3(0.2f, 0.2f, 0.8f, 1.0f); +LLColor4 LLColor4::blue4(0.0f, 0.0f, 0.6f, 1.0f); +LLColor4 LLColor4::blue5(0.4f, 0.2f, 1.0f, 1.0f); +LLColor4 LLColor4::blue6(0.4f, 0.5f, 1.0f, 1.0f); + +LLColor4 LLColor4::yellow1(1.0f, 1.0f, 0.0f, 1.0f); +LLColor4 LLColor4::yellow2(0.6f, 0.6f, 0.0f, 1.0f); +LLColor4 LLColor4::yellow3(0.8f, 1.0f, 0.2f, 1.0f); +LLColor4 LLColor4::yellow4(1.0f, 1.0f, 0.4f, 1.0f); +LLColor4 LLColor4::yellow5(0.6f, 0.4f, 0.2f, 1.0f); +LLColor4 LLColor4::yellow6(1.0f, 0.8f, 0.4f, 1.0f); +LLColor4 LLColor4::yellow7(0.8f, 0.8f, 0.0f, 1.0f); +LLColor4 LLColor4::yellow8(0.8f, 0.8f, 0.2f, 1.0f); +LLColor4 LLColor4::yellow9(0.8f, 0.8f, 0.4f, 1.0f); + +LLColor4 LLColor4::orange1(1.0f, 0.8f, 0.0f, 1.0f); +LLColor4 LLColor4::orange2(1.0f, 0.6f, 0.0f, 1.0f); +LLColor4 LLColor4::orange3(1.0f, 0.4f, 0.2f, 1.0f); +LLColor4 LLColor4::orange4(0.8f, 0.4f, 0.0f, 1.0f); +LLColor4 LLColor4::orange5(0.9f, 0.5f, 0.0f, 1.0f); +LLColor4 LLColor4::orange6(1.0f, 0.8f, 0.2f, 1.0f); + +LLColor4 LLColor4::magenta1(1.0f, 0.0f, 1.0f, 1.0f); +LLColor4 LLColor4::magenta2(0.6f, 0.2f, 0.4f, 1.0f); +LLColor4 LLColor4::magenta3(1.0f, 0.4f, 0.6f, 1.0f); +LLColor4 LLColor4::magenta4(1.0f, 0.2f, 0.8f, 1.0f); + +LLColor4 LLColor4::purple1(0.6f, 0.2f, 0.8f, 1.0f); +LLColor4 LLColor4::purple2(0.8f, 0.2f, 1.0f, 1.0f); +LLColor4 LLColor4::purple3(0.6f, 0.0f, 1.0f, 1.0f); +LLColor4 LLColor4::purple4(0.4f, 0.0f, 0.8f, 1.0f); +LLColor4 LLColor4::purple5(0.6f, 0.0f, 0.8f, 1.0f); +LLColor4 LLColor4::purple6(0.8f, 0.0f, 0.6f, 1.0f); + +LLColor4 LLColor4::pink1(1.0f, 0.5f, 0.8f, 1.0f); +LLColor4 LLColor4::pink2(1.0f, 0.8f, 0.9f, 1.0f); + +LLColor4 LLColor4::cyan1(0.0f, 1.0f, 1.0f, 1.0f); +LLColor4 LLColor4::cyan2(0.4f, 0.8f, 0.8f, 1.0f); +LLColor4 LLColor4::cyan3(0.0f, 1.0f, 0.6f, 1.0f); +LLColor4 LLColor4::cyan4(0.6f, 1.0f, 1.0f, 1.0f); +LLColor4 LLColor4::cyan5(0.2f, 0.6f, 1.0f, 1.0f); +LLColor4 LLColor4::cyan6(0.2f, 0.6f, 0.6f, 1.0f); + +////////////////////////////////////////////////////////////////////////////// + +// conversion +LLColor4::operator const LLColor4U() const +{ + return LLColor4U( + (U8)llclampb(llround(mV[VRED]*255.f)), + (U8)llclampb(llround(mV[VGREEN]*255.f)), + (U8)llclampb(llround(mV[VBLUE]*255.f)), + (U8)llclampb(llround(mV[VALPHA]*255.f))); +} + +LLColor4::LLColor4(const LLColor3 &vec, F32 a) +{ + mV[VX] = vec.mV[VX]; + mV[VY] = vec.mV[VY]; + mV[VZ] = vec.mV[VZ]; + mV[VW] = a; +} + +LLColor4::LLColor4(const LLColor4U& color4u) +{ + const F32 SCALE = 1.f/255.f; + mV[VX] = color4u.mV[VX] * SCALE; + mV[VY] = color4u.mV[VY] * SCALE; + mV[VZ] = color4u.mV[VZ] * SCALE; + mV[VW] = color4u.mV[VW] * SCALE; +} + +const LLColor4& LLColor4::setVec(const LLColor4U& color4u) +{ + const F32 SCALE = 1.f/255.f; + mV[VX] = color4u.mV[VX] * SCALE; + mV[VY] = color4u.mV[VY] * SCALE; + mV[VZ] = color4u.mV[VZ] * SCALE; + mV[VW] = color4u.mV[VW] * SCALE; + return (*this); +} + +const LLColor4& LLColor4::setVec(const LLColor3 &vec) +{ + mV[VX] = vec.mV[VX]; + mV[VY] = vec.mV[VY]; + mV[VZ] = vec.mV[VZ]; + +// no change to alpha! +// mV[VW] = 1.f; + + return (*this); +} + +const LLColor4& LLColor4::setVec(const LLColor3 &vec, F32 a) +{ + mV[VX] = vec.mV[VX]; + mV[VY] = vec.mV[VY]; + mV[VZ] = vec.mV[VZ]; + mV[VW] = a; + return (*this); +} + +const LLColor4& LLColor4::operator=(const LLColor3 &a) +{ + mV[VX] = a.mV[VX]; + mV[VY] = a.mV[VY]; + mV[VZ] = a.mV[VZ]; + +// converting from an rgb sets a=1 (opaque) + mV[VW] = 1.f; + return (*this); +} + + +std::ostream& operator<<(std::ostream& s, const LLColor4 &a) +{ + s << "{ " << a.mV[VX] << ", " << a.mV[VY] << ", " << a.mV[VZ] << ", " << a.mV[VW] << " }"; + return s; +} + +bool operator==(const LLColor4 &a, const LLColor3 &b) +{ + return ( (a.mV[VX] == b.mV[VX]) + &&(a.mV[VY] == b.mV[VY]) + &&(a.mV[VZ] == b.mV[VZ])); +} + +bool operator!=(const LLColor4 &a, const LLColor3 &b) +{ + return ( (a.mV[VX] != b.mV[VX]) + ||(a.mV[VY] != b.mV[VY]) + ||(a.mV[VZ] != b.mV[VZ])); +} + +LLColor3 vec4to3(const LLColor4 &vec) +{ + LLColor3 temp(vec.mV[VX], vec.mV[VY], vec.mV[VZ]); + return temp; +} + +LLColor4 vec3to4(const LLColor3 &vec) +{ + LLColor3 temp(vec.mV[VX], vec.mV[VY], vec.mV[VZ]); + return temp; +} + +// static +BOOL LLColor4::parseColor(const char* buf, LLColor4* color) +{ + if( buf == NULL || buf[0] == '\0' || color == NULL) + { + return FALSE; + } + + LLString full_string(buf); + + boost_tokenizer tokens(full_string, boost::char_separator(", ")); + boost_tokenizer::iterator token_iter = tokens.begin(); + if (token_iter == tokens.end()) + { + return FALSE; + } + + // Grab the first token into a string, since we don't know + // if this is a float or a color name. + LLString color_name( (*token_iter) ); + ++token_iter; + + if (token_iter != tokens.end()) + { + // There are more tokens to read. This must be a vector. + LLColor4 v; + LLString::convertToF32( color_name, v.mV[VX] ); + LLString::convertToF32( *token_iter, v.mV[VY] ); + v.mV[VZ] = 0.0f; + v.mV[VW] = 1.0f; + + ++token_iter; + if (token_iter == tokens.end()) + { + // This is a malformed vector. + llwarns << "LLColor4::parseColor() malformed color " << full_string << llendl; + } + else + { + // There is a z-component. + LLString::convertToF32( *token_iter, v.mV[VZ] ); + + ++token_iter; + if (token_iter != tokens.end()) + { + // There is an alpha component. + LLString::convertToF32( *token_iter, v.mV[VW] ); + } + } + + // Make sure all values are between 0 and 1. + if (v.mV[VX] > 1.f || v.mV[VY] > 1.f || v.mV[VZ] > 1.f || v.mV[VW] > 1.f) + { + v = v * (1.f / 255.f); + } + color->setVec( v ); + } + else // Single value. Read as a named color. + { + // We have a color name + if ( "red" == color_name ) + { + color->setVec(LLColor4::red); + } + else if ( "red1" == color_name ) + { + color->setVec(LLColor4::red1); + } + else if ( "red2" == color_name ) + { + color->setVec(LLColor4::red2); + } + else if ( "red3" == color_name ) + { + color->setVec(LLColor4::red3); + } + else if ( "red4" == color_name ) + { + color->setVec(LLColor4::red4); + } + else if ( "red5" == color_name ) + { + color->setVec(LLColor4::red5); + } + else if( "green" == color_name ) + { + color->setVec(LLColor4::green); + } + else if( "green1" == color_name ) + { + color->setVec(LLColor4::green1); + } + else if( "green2" == color_name ) + { + color->setVec(LLColor4::green2); + } + else if( "green3" == color_name ) + { + color->setVec(LLColor4::green3); + } + else if( "green4" == color_name ) + { + color->setVec(LLColor4::green4); + } + else if( "green5" == color_name ) + { + color->setVec(LLColor4::green5); + } + else if( "green6" == color_name ) + { + color->setVec(LLColor4::green6); + } + else if( "blue" == color_name ) + { + color->setVec(LLColor4::blue); + } + else if( "blue1" == color_name ) + { + color->setVec(LLColor4::blue1); + } + else if( "blue2" == color_name ) + { + color->setVec(LLColor4::blue2); + } + else if( "blue3" == color_name ) + { + color->setVec(LLColor4::blue3); + } + else if( "blue4" == color_name ) + { + color->setVec(LLColor4::blue4); + } + else if( "blue5" == color_name ) + { + color->setVec(LLColor4::blue5); + } + else if( "blue6" == color_name ) + { + color->setVec(LLColor4::blue6); + } + else if( "black" == color_name ) + { + color->setVec(LLColor4::black); + } + else if( "white" == color_name ) + { + color->setVec(LLColor4::white); + } + else if( "yellow" == color_name ) + { + color->setVec(LLColor4::yellow); + } + else if( "yellow1" == color_name ) + { + color->setVec(LLColor4::yellow1); + } + else if( "yellow2" == color_name ) + { + color->setVec(LLColor4::yellow2); + } + else if( "yellow3" == color_name ) + { + color->setVec(LLColor4::yellow3); + } + else if( "yellow4" == color_name ) + { + color->setVec(LLColor4::yellow4); + } + else if( "yellow5" == color_name ) + { + color->setVec(LLColor4::yellow5); + } + else if( "yellow6" == color_name ) + { + color->setVec(LLColor4::yellow6); + } + else if( "magenta" == color_name ) + { + color->setVec(LLColor4::magenta); + } + else if( "magenta1" == color_name ) + { + color->setVec(LLColor4::magenta1); + } + else if( "magenta2" == color_name ) + { + color->setVec(LLColor4::magenta2); + } + else if( "magenta3" == color_name ) + { + color->setVec(LLColor4::magenta3); + } + else if( "magenta4" == color_name ) + { + color->setVec(LLColor4::magenta4); + } + else if( "purple" == color_name ) + { + color->setVec(LLColor4::purple); + } + else if( "purple1" == color_name ) + { + color->setVec(LLColor4::purple1); + } + else if( "purple2" == color_name ) + { + color->setVec(LLColor4::purple2); + } + else if( "purple3" == color_name ) + { + color->setVec(LLColor4::purple3); + } + else if( "purple4" == color_name ) + { + color->setVec(LLColor4::purple4); + } + else if( "purple5" == color_name ) + { + color->setVec(LLColor4::purple5); + } + else if( "purple6" == color_name ) + { + color->setVec(LLColor4::purple6); + } + else if( "pink" == color_name ) + { + color->setVec(LLColor4::pink); + } + else if( "pink1" == color_name ) + { + color->setVec(LLColor4::pink1); + } + else if( "pink2" == color_name ) + { + color->setVec(LLColor4::pink2); + } + else if( "cyan" == color_name ) + { + color->setVec(LLColor4::cyan); + } + else if( "cyan1" == color_name ) + { + color->setVec(LLColor4::cyan1); + } + else if( "cyan2" == color_name ) + { + color->setVec(LLColor4::cyan2); + } + else if( "cyan3" == color_name ) + { + color->setVec(LLColor4::cyan3); + } + else if( "cyan4" == color_name ) + { + color->setVec(LLColor4::cyan4); + } + else if( "cyan5" == color_name ) + { + color->setVec(LLColor4::cyan5); + } + else if( "cyan6" == color_name ) + { + color->setVec(LLColor4::cyan6); + } + else if( "smoke" == color_name ) + { + color->setVec(LLColor4::smoke); + } + else if( "grey" == color_name ) + { + color->setVec(LLColor4::grey); + } + else if( "grey1" == color_name ) + { + color->setVec(LLColor4::grey1); + } + else if( "grey2" == color_name ) + { + color->setVec(LLColor4::grey2); + } + else if( "grey3" == color_name ) + { + color->setVec(LLColor4::grey3); + } + else if( "grey4" == color_name ) + { + color->setVec(LLColor4::grey4); + } + else if( "orange" == color_name ) + { + color->setVec(LLColor4::orange); + } + else if( "orange1" == color_name ) + { + color->setVec(LLColor4::orange1); + } + else if( "orange2" == color_name ) + { + color->setVec(LLColor4::orange2); + } + else if( "orange3" == color_name ) + { + color->setVec(LLColor4::orange3); + } + else if( "orange4" == color_name ) + { + color->setVec(LLColor4::orange4); + } + else if( "orange5" == color_name ) + { + color->setVec(LLColor4::orange5); + } + else if( "orange6" == color_name ) + { + color->setVec(LLColor4::orange6); + } + else if ( "clear" == color_name ) + { + color->setVec(0.f, 0.f, 0.f, 0.f); + } + else + { + llwarns << "invalid color " << color_name << llendl; + } + } + + return TRUE; +} + +// static +BOOL LLColor4::parseColor4(const char* buf, LLColor4* value) +{ + if( buf == NULL || buf[0] == '\0' || value == NULL) + { + return FALSE; + } + + LLColor4 v; + S32 count = sscanf( buf, "%f, %f, %f, %f", v.mV + 0, v.mV + 1, v.mV + 2, v.mV + 3 ); + if (1 == count ) + { + // try this format + count = sscanf( buf, "%f %f %f %f", v.mV + 0, v.mV + 1, v.mV + 2, v.mV + 3 ); + } + if( 4 == count ) + { + value->setVec( v ); + return TRUE; + } + + return FALSE; +} + +// EOF diff --git a/indra/llmath/v4color.h b/indra/llmath/v4color.h new file mode 100644 index 0000000000..8fe5846e26 --- /dev/null +++ b/indra/llmath/v4color.h @@ -0,0 +1,539 @@ +/** + * @file v4color.h + * @brief LLColor4 class header file. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_V4COLOR_H +#define LL_V4COLOR_H + +#include "llerror.h" +//#include "vmath.h" +#include "llmath.h" +#include "llsd.h" + +class LLColor3; +class LLColor4U; + +// LLColor4 = |x y z w| + +static const U32 LENGTHOFCOLOR4 = 4; + +static const U32 MAX_LENGTH_OF_COLOR_NAME = 15; //Give plenty of room for additional colors... + +class LLColor4 +{ + public: + F32 mV[LENGTHOFCOLOR4]; + LLColor4(); // Initializes LLColor4 to (0, 0, 0, 1) + LLColor4(F32 r, F32 g, F32 b); // Initializes LLColor4 to (r, g, b, 1) + LLColor4(F32 r, F32 g, F32 b, F32 a); // Initializes LLColor4 to (r. g, b, a) + LLColor4(U32 clr); // Initializes LLColor4 to (r=clr>>24, etc)) + LLColor4(const F32 *vec); // Initializes LLColor4 to (vec[0]. vec[1], vec[2], 1) + LLColor4(const LLColor3 &vec, F32 a = 1.f); // Initializes LLColor4 to (vec, a) + LLColor4(const LLSD& sd); + explicit LLColor4(const LLColor4U& color4u); // "explicit" to avoid automatic conversion + + LLSD getValue() const + { + LLSD ret; + ret[0] = mV[0]; + ret[1] = mV[1]; + ret[2] = mV[2]; + ret[3] = mV[3]; + return ret; + } + + void setValue(const LLSD& sd) + { + mV[0] = (F32) sd[0].asReal(); + mV[1] = (F32) sd[1].asReal(); + mV[2] = (F32) sd[2].asReal(); + mV[3] = (F32) sd[3].asReal(); + } + + const LLColor4& setToBlack(); // zero LLColor4 to (0, 0, 0, 1) + const LLColor4& setToWhite(); // zero LLColor4 to (0, 0, 0, 1) + + const LLColor4& setVec(F32 r, F32 g, F32 b, F32 a); // Sets LLColor4 to (r, g, b, a) + const LLColor4& setVec(F32 r, F32 g, F32 b); // Sets LLColor4 to (r, g, b) (no change in a) + const LLColor4& setVec(const LLColor4 &vec); // Sets LLColor4 to vec + const LLColor4& setVec(const LLColor3 &vec); // Sets LLColor4 to LLColor3 vec (no change in alpha) + const LLColor4& setVec(const LLColor3 &vec, F32 a); // Sets LLColor4 to LLColor3 vec, with alpha specified + const LLColor4& setVec(const F32 *vec); // Sets LLColor4 to vec + const LLColor4& setVec(const LLColor4U& color4u); // Sets LLColor4 to color4u, rescaled. + + + const LLColor4& setAlpha(F32 a); + + F32 magVec() const; // Returns magnitude of LLColor4 + F32 magVecSquared() const; // Returns magnitude squared of LLColor4 + F32 normVec(); // Normalizes and returns the magnitude of LLColor4 + const BOOL isOpaque() { return mV[VALPHA] == 1.f; } + + F32 operator[](int idx) const { return mV[idx]; } + F32 &operator[](int idx) { return mV[idx]; } + + const LLColor4& operator=(const LLColor3 &a); // Assigns vec3 to vec4 and returns vec4 + const LLColor4& operator=(const LLSD& sd); + + friend std::ostream& operator<<(std::ostream& s, const LLColor4 &a); // Print a + friend LLColor4 operator+(const LLColor4 &a, const LLColor4 &b); // Return vector a + b + friend LLColor4 operator-(const LLColor4 &a, const LLColor4 &b); // Return vector a minus b + friend LLColor4 operator*(const LLColor4 &a, const LLColor4 &b); // Return a * b + friend LLColor4 operator*(const LLColor4 &a, F32 k); // Return rgb times scaler k (no alpha change) + friend LLColor4 operator*(F32 k, const LLColor4 &a); // Return rgb times scaler k (no alpha change) + friend LLColor4 operator%(const LLColor4 &a, F32 k); // Return alpha times scaler k (no rgb change) + friend LLColor4 operator%(F32 k, const LLColor4 &a); // Return alpha times scaler k (no rgb change) + friend bool operator==(const LLColor4 &a, const LLColor4 &b); // Return a == b + friend bool operator!=(const LLColor4 &a, const LLColor4 &b); // Return a != b + + friend bool operator==(const LLColor4 &a, const LLColor3 &b); // Return a == b + friend bool operator!=(const LLColor4 &a, const LLColor3 &b); // Return a != b + + friend const LLColor4& operator+=(LLColor4 &a, const LLColor4 &b); // Return vector a + b + friend const LLColor4& operator-=(LLColor4 &a, const LLColor4 &b); // Return vector a minus b + friend const LLColor4& operator*=(LLColor4 &a, F32 k); // Return rgb times scaler k (no alpha change) + friend const LLColor4& operator%=(LLColor4 &a, F32 k); // Return alpha times scaler k (no rgb change) + + friend const LLColor4& operator*=(LLColor4 &a, const LLColor4 &b); // Doesn't multiply alpha! (for lighting) + + // conversion + operator const LLColor4U() const; + + // Basic color values. + static LLColor4 red; + static LLColor4 green; + static LLColor4 blue; + static LLColor4 black; + static LLColor4 white; + static LLColor4 yellow; + static LLColor4 magenta; + static LLColor4 cyan; + static LLColor4 smoke; + static LLColor4 grey; + static LLColor4 orange; + static LLColor4 purple; + static LLColor4 pink; + static LLColor4 transparent; + + // Extra color values. + static LLColor4 grey1; + static LLColor4 grey2; + static LLColor4 grey3; + static LLColor4 grey4; + + static LLColor4 red1; + static LLColor4 red2; + static LLColor4 red3; + static LLColor4 red4; + static LLColor4 red5; + + static LLColor4 green1; + static LLColor4 green2; + static LLColor4 green3; + static LLColor4 green4; + static LLColor4 green5; + static LLColor4 green6; + + static LLColor4 blue1; + static LLColor4 blue2; + static LLColor4 blue3; + static LLColor4 blue4; + static LLColor4 blue5; + static LLColor4 blue6; + + static LLColor4 yellow1; + static LLColor4 yellow2; + static LLColor4 yellow3; + static LLColor4 yellow4; + static LLColor4 yellow5; + static LLColor4 yellow6; + static LLColor4 yellow7; + static LLColor4 yellow8; + static LLColor4 yellow9; + + static LLColor4 orange1; + static LLColor4 orange2; + static LLColor4 orange3; + static LLColor4 orange4; + static LLColor4 orange5; + static LLColor4 orange6; + + static LLColor4 magenta1; + static LLColor4 magenta2; + static LLColor4 magenta3; + static LLColor4 magenta4; + + static LLColor4 purple1; + static LLColor4 purple2; + static LLColor4 purple3; + static LLColor4 purple4; + static LLColor4 purple5; + static LLColor4 purple6; + + static LLColor4 pink1; + static LLColor4 pink2; + + static LLColor4 cyan1; + static LLColor4 cyan2; + static LLColor4 cyan3; + static LLColor4 cyan4; + static LLColor4 cyan5; + static LLColor4 cyan6; + + static BOOL parseColor(const char* buf, LLColor4* color); + static BOOL parseColor4(const char* buf, LLColor4* color); + + inline void clamp(); +}; + + +// Non-member functions +F32 distVec(const LLColor4 &a, const LLColor4 &b); // Returns distance between a and b +F32 distVec_squared(const LLColor4 &a, const LLColor4 &b); // Returns distance squared between a and b +LLColor3 vec4to3(const LLColor4 &vec); +LLColor4 vec3to4(const LLColor3 &vec); +LLColor4 lerp(const LLColor4 &a, const LLColor4 &b, F32 u); + +inline LLColor4::LLColor4(void) +{ + mV[VX] = 0.f; + mV[VY] = 0.f; + mV[VZ] = 0.f; + mV[VW] = 1.f; +} + +inline LLColor4::LLColor4(const LLSD& sd) +{ + *this = sd; +} + +inline LLColor4::LLColor4(F32 r, F32 g, F32 b) +{ + mV[VX] = r; + mV[VY] = g; + mV[VZ] = b; + mV[VW] = 1.f; +} + +inline LLColor4::LLColor4(F32 r, F32 g, F32 b, F32 a) +{ + mV[VX] = r; + mV[VY] = g; + mV[VZ] = b; + mV[VW] = a; +} + +inline LLColor4::LLColor4(U32 clr) +{ + mV[VX] = (clr&0xff) * (1.0f/255.0f); + mV[VY] = ((clr>>8)&0xff) * (1.0f/255.0f); + mV[VZ] = ((clr>>16)&0xff) * (1.0f/255.0f); + mV[VW] = (clr>>24) * (1.0f/255.0f); +} + +inline LLColor4::LLColor4(const F32 *vec) +{ + mV[VX] = vec[VX]; + mV[VY] = vec[VY]; + mV[VZ] = vec[VZ]; + mV[VW] = vec[VW]; +} + +inline const LLColor4& LLColor4::setToBlack(void) +{ + mV[VX] = 0.f; + mV[VY] = 0.f; + mV[VZ] = 0.f; + mV[VW] = 1.f; + return (*this); +} + +inline const LLColor4& LLColor4::setToWhite(void) +{ + mV[VX] = 1.f; + mV[VY] = 1.f; + mV[VZ] = 1.f; + mV[VW] = 1.f; + return (*this); +} + +inline const LLColor4& LLColor4::setVec(F32 x, F32 y, F32 z) +{ + mV[VX] = x; + mV[VY] = y; + mV[VZ] = z; + +// no change to alpha! +// mV[VW] = 1.f; + + return (*this); +} + +inline const LLColor4& LLColor4::setVec(F32 x, F32 y, F32 z, F32 a) +{ + mV[VX] = x; + mV[VY] = y; + mV[VZ] = z; + mV[VW] = a; + return (*this); +} + +inline const LLColor4& LLColor4::setVec(const LLColor4 &vec) +{ + mV[VX] = vec.mV[VX]; + mV[VY] = vec.mV[VY]; + mV[VZ] = vec.mV[VZ]; + mV[VW] = vec.mV[VW]; + return (*this); +} + + +inline const LLColor4& LLColor4::setVec(const F32 *vec) +{ + mV[VX] = vec[VX]; + mV[VY] = vec[VY]; + mV[VZ] = vec[VZ]; + mV[VW] = vec[VW]; + return (*this); +} + +inline const LLColor4& LLColor4::setAlpha(F32 a) +{ + mV[VW] = a; + return (*this); +} + +// LLColor4 Magnitude and Normalization Functions + +inline F32 LLColor4::magVec(void) const +{ + return fsqrtf(mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]); +} + +inline F32 LLColor4::magVecSquared(void) const +{ + return mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]; +} + +inline F32 LLColor4::normVec(void) +{ + F32 mag = fsqrtf(mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]); + F32 oomag; + + if (mag) + { + oomag = 1.f/mag; + mV[VX] *= oomag; + mV[VY] *= oomag; + mV[VZ] *= oomag; + } + return (mag); +} + +// LLColor4 Operators + + +inline LLColor4 operator+(const LLColor4 &a, const LLColor4 &b) +{ + return LLColor4( + a.mV[VX] + b.mV[VX], + a.mV[VY] + b.mV[VY], + a.mV[VZ] + b.mV[VZ], + a.mV[VW] + b.mV[VW]); +} + +inline LLColor4 operator-(const LLColor4 &a, const LLColor4 &b) +{ + return LLColor4( + a.mV[VX] - b.mV[VX], + a.mV[VY] - b.mV[VY], + a.mV[VZ] - b.mV[VZ], + a.mV[VW] - b.mV[VW]); +} + +inline LLColor4 operator*(const LLColor4 &a, const LLColor4 &b) +{ + return LLColor4( + a.mV[VX] * b.mV[VX], + a.mV[VY] * b.mV[VY], + a.mV[VZ] * b.mV[VZ], + a.mV[VW] * b.mV[VW]); +} + +inline LLColor4 operator*(const LLColor4 &a, F32 k) +{ + // only affects rgb (not a!) + return LLColor4( + a.mV[VX] * k, + a.mV[VY] * k, + a.mV[VZ] * k, + a.mV[VW]); +} + +inline LLColor4 operator*(F32 k, const LLColor4 &a) +{ + // only affects rgb (not a!) + return LLColor4( + a.mV[VX] * k, + a.mV[VY] * k, + a.mV[VZ] * k, + a.mV[VW]); +} + +inline LLColor4 operator%(F32 k, const LLColor4 &a) +{ + // only affects alpha (not rgb!) + return LLColor4( + a.mV[VX], + a.mV[VY], + a.mV[VZ], + a.mV[VW] * k); +} + +inline LLColor4 operator%(const LLColor4 &a, F32 k) +{ + // only affects alpha (not rgb!) + return LLColor4( + a.mV[VX], + a.mV[VY], + a.mV[VZ], + a.mV[VW] * k); +} + +inline bool operator==(const LLColor4 &a, const LLColor4 &b) +{ + return ( (a.mV[VX] == b.mV[VX]) + &&(a.mV[VY] == b.mV[VY]) + &&(a.mV[VZ] == b.mV[VZ]) + &&(a.mV[VW] == b.mV[VW])); +} + +inline bool operator!=(const LLColor4 &a, const LLColor4 &b) +{ + return ( (a.mV[VX] != b.mV[VX]) + ||(a.mV[VY] != b.mV[VY]) + ||(a.mV[VZ] != b.mV[VZ]) + ||(a.mV[VW] != b.mV[VW])); +} + +inline const LLColor4& operator+=(LLColor4 &a, const LLColor4 &b) +{ + a.mV[VX] += b.mV[VX]; + a.mV[VY] += b.mV[VY]; + a.mV[VZ] += b.mV[VZ]; + a.mV[VW] += b.mV[VW]; + return a; +} + +inline const LLColor4& operator-=(LLColor4 &a, const LLColor4 &b) +{ + a.mV[VX] -= b.mV[VX]; + a.mV[VY] -= b.mV[VY]; + a.mV[VZ] -= b.mV[VZ]; + a.mV[VW] -= b.mV[VW]; + return a; +} + +inline const LLColor4& operator*=(LLColor4 &a, F32 k) +{ + // only affects rgb (not a!) + a.mV[VX] *= k; + a.mV[VY] *= k; + a.mV[VZ] *= k; + return a; +} + +inline const LLColor4& operator *=(LLColor4 &a, const LLColor4 &b) +{ + a.mV[VX] *= b.mV[VX]; + a.mV[VY] *= b.mV[VY]; + a.mV[VZ] *= b.mV[VZ]; +// a.mV[VW] *= b.mV[VW]; + return a; +} + +inline const LLColor4& operator%=(LLColor4 &a, F32 k) +{ + // only affects alpha (not rgb!) + a.mV[VW] *= k; + return a; +} + + +// Non-member functions + +inline F32 distVec(const LLColor4 &a, const LLColor4 &b) +{ + LLColor4 vec = a - b; + return (vec.magVec()); +} + +inline F32 distVec_squared(const LLColor4 &a, const LLColor4 &b) +{ + LLColor4 vec = a - b; + return (vec.magVecSquared()); +} + +inline LLColor4 lerp(const LLColor4 &a, const LLColor4 &b, F32 u) +{ + return LLColor4( + a.mV[VX] + (b.mV[VX] - a.mV[VX]) * u, + a.mV[VY] + (b.mV[VY] - a.mV[VY]) * u, + a.mV[VZ] + (b.mV[VZ] - a.mV[VZ]) * u, + a.mV[VW] + (b.mV[VW] - a.mV[VW]) * u); +} + + +void LLColor4::clamp() +{ + // Clamp the color... + if (mV[0] < 0.f) + { + mV[0] = 0.f; + } + else if (mV[0] > 1.f) + { + mV[0] = 1.f; + } + if (mV[1] < 0.f) + { + mV[1] = 0.f; + } + else if (mV[1] > 1.f) + { + mV[1] = 1.f; + } + if (mV[2] < 0.f) + { + mV[2] = 0.f; + } + else if (mV[2] > 1.f) + { + mV[2] = 1.f; + } + if (mV[3] < 0.f) + { + mV[3] = 0.f; + } + else if (mV[3] > 1.f) + { + mV[3] = 1.f; + } +} + +inline const LLColor4& LLColor4::operator=(const LLSD& sd) +{ + mV[0] = (F32) sd[0].asReal(); + mV[1] = (F32) sd[1].asReal(); + mV[2] = (F32) sd[2].asReal(); + mV[3] = (F32) sd[3].asReal(); + + return *this; +} + +#endif + diff --git a/indra/llmath/v4coloru.cpp b/indra/llmath/v4coloru.cpp new file mode 100644 index 0000000000..e5a17e1a97 --- /dev/null +++ b/indra/llmath/v4coloru.cpp @@ -0,0 +1,102 @@ +/** + * @file v4coloru.cpp + * @brief LLColor4U class implementation. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +//#include "v3coloru.h" +#include "v4coloru.h" +#include "v4color.h" +//#include "vmath.h" +#include "llmath.h" + +// LLColor4U +LLColor4U LLColor4U::white(255, 255, 255, 255); +LLColor4U LLColor4U::black( 0, 0, 0, 255); +LLColor4U LLColor4U::red (255, 0, 0, 255); +LLColor4U LLColor4U::green( 0, 255, 0, 255); +LLColor4U LLColor4U::blue ( 0, 0, 255, 255); + +// conversion +/* inlined to fix gcc compile link error +LLColor4U::operator LLColor4() +{ + return(LLColor4((F32)mV[VRED]/255.f,(F32)mV[VGREEN]/255.f,(F32)mV[VBLUE]/255.f,(F32)mV[VALPHA]/255.f)); +} +*/ + +// Constructors + + +/* +LLColor4U::LLColor4U(const LLColor3 &vec) +{ + mV[VX] = vec.mV[VX]; + mV[VY] = vec.mV[VY]; + mV[VZ] = vec.mV[VZ]; + mV[VW] = 255; +} +*/ + + +// Clear and Assignment Functions + + + +// LLColor4U Operators + +/* +LLColor4U LLColor4U::operator=(const LLColor3 &a) +{ + mV[VX] = a.mV[VX]; + mV[VY] = a.mV[VY]; + mV[VZ] = a.mV[VZ]; + +// converting from an rgb sets a=1 (opaque) + mV[VW] = 255; + return (*this); +} +*/ + + +std::ostream& operator<<(std::ostream& s, const LLColor4U &a) +{ + s << "{ " << (S32)a.mV[VX] << ", " << (S32)a.mV[VY] << ", " << (S32)a.mV[VZ] << ", " << (S32)a.mV[VW] << " }"; + return s; +} + +// static +BOOL LLColor4U::parseColor4U(const char* buf, LLColor4U* value) +{ + if( buf == NULL || buf[0] == '\0' || value == NULL) + { + return FALSE; + } + + U32 v[4]; + S32 count = sscanf( buf, "%u, %u, %u, %u", v + 0, v + 1, v + 2, v + 3 ); + if (1 == count ) + { + // try this format + count = sscanf( buf, "%u %u %u %u", v + 0, v + 1, v + 2, v + 3 ); + } + if( 4 != count ) + { + return FALSE; + } + + for( S32 i = 0; i < 4; i++ ) + { + if( v[i] > U8_MAX ) + { + return FALSE; + } + } + + value->setVec( U8(v[0]), U8(v[1]), U8(v[2]), U8(v[3]) ); + return TRUE; +} diff --git a/indra/llmath/v4coloru.h b/indra/llmath/v4coloru.h new file mode 100644 index 0000000000..e9cc9b2ab3 --- /dev/null +++ b/indra/llmath/v4coloru.h @@ -0,0 +1,501 @@ +/** + * @file v4coloru.h + * @brief The LLColor4U class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_V4COLORU_H +#define LL_V4COLORU_H + +#include "llerror.h" +//#include "vmath.h" +#include "llmath.h" +//#include "v4color.h" + +#include "v3color.h" +#include "v4color.h" + +//class LLColor3U; +class LLColor4; + +// LLColor4U = | red green blue alpha | + +static const U32 LENGTHOFCOLOR4U = 4; + + +class LLColor4U +{ +public: + + union + { + U8 mV[LENGTHOFCOLOR4U]; + U32 mAll; + LLColor4* mSources; + LLColor4U* mSourcesU; + }; + + + LLColor4U(); // Initializes LLColor4U to (0, 0, 0, 1) + LLColor4U(U8 r, U8 g, U8 b); // Initializes LLColor4U to (r, g, b, 1) + LLColor4U(U8 r, U8 g, U8 b, U8 a); // Initializes LLColor4U to (r. g, b, a) + LLColor4U(const U8 *vec); // Initializes LLColor4U to (vec[0]. vec[1], vec[2], 1) + LLColor4U(const LLSD& sd) + { + setValue(sd); + } + + void setValue(const LLSD& sd) + { + mV[0] = sd[0].asInteger(); + mV[1] = sd[1].asInteger(); + mV[2] = sd[2].asInteger(); + mV[3] = sd[3].asInteger(); + } + + const LLColor4U& operator=(const LLSD& sd) + { + setValue(sd); + return *this; + } + + LLSD getValue() const + { + LLSD ret; + ret[0] = mV[0]; + ret[1] = mV[1]; + ret[2] = mV[2]; + ret[3] = mV[3]; + return ret; + } + + const LLColor4U& setToBlack(); // zero LLColor4U to (0, 0, 0, 1) + const LLColor4U& setToWhite(); // zero LLColor4U to (0, 0, 0, 1) + + const LLColor4U& setVec(U8 r, U8 g, U8 b, U8 a); // Sets LLColor4U to (r, g, b, a) + const LLColor4U& setVec(U8 r, U8 g, U8 b); // Sets LLColor4U to (r, g, b) (no change in a) + const LLColor4U& setVec(const LLColor4U &vec); // Sets LLColor4U to vec + const LLColor4U& setVec(const U8 *vec); // Sets LLColor4U to vec + + const LLColor4U& setAlpha(U8 a); + + F32 magVec() const; // Returns magnitude of LLColor4U + F32 magVecSquared() const; // Returns magnitude squared of LLColor4U + + friend std::ostream& operator<<(std::ostream& s, const LLColor4U &a); // Print a + friend LLColor4U operator+(const LLColor4U &a, const LLColor4U &b); // Return vector a + b + friend LLColor4U operator-(const LLColor4U &a, const LLColor4U &b); // Return vector a minus b + friend LLColor4U operator*(const LLColor4U &a, const LLColor4U &b); // Return a * b + friend bool operator==(const LLColor4U &a, const LLColor4U &b); // Return a == b + friend bool operator!=(const LLColor4U &a, const LLColor4U &b); // Return a != b + + friend const LLColor4U& operator+=(LLColor4U &a, const LLColor4U &b); // Return vector a + b + friend const LLColor4U& operator-=(LLColor4U &a, const LLColor4U &b); // Return vector a minus b + friend const LLColor4U& operator*=(LLColor4U &a, U8 k); // Return rgb times scaler k (no alpha change) + friend const LLColor4U& operator%=(LLColor4U &a, U8 k); // Return alpha times scaler k (no rgb change) + + LLColor4U addClampMax(const LLColor4U &color); // Add and clamp the max + + LLColor4U multAll(const F32 k); // Multiply ALL channels by scalar k + const LLColor4U& combine(); + + inline void setVecScaleClamp(const LLColor3 &color); + inline void setVecScaleClamp(const LLColor4 &color); + + static BOOL parseColor4U(const char* buf, LLColor4U* value); + + static LLColor4U white; + static LLColor4U black; + static LLColor4U red; + static LLColor4U green; + static LLColor4U blue; +}; + + +// Non-member functions +F32 distVec(const LLColor4U &a, const LLColor4U &b); // Returns distance between a and b +F32 distVec_squared(const LLColor4U &a, const LLColor4U &b); // Returns distance squared between a and b + + +inline LLColor4U::LLColor4U() +{ + mV[VX] = 0; + mV[VY] = 0; + mV[VZ] = 0; + mV[VW] = 255; +} + +inline LLColor4U::LLColor4U(U8 r, U8 g, U8 b) +{ + mV[VX] = r; + mV[VY] = g; + mV[VZ] = b; + mV[VW] = 255; +} + +inline LLColor4U::LLColor4U(U8 r, U8 g, U8 b, U8 a) +{ + mV[VX] = r; + mV[VY] = g; + mV[VZ] = b; + mV[VW] = a; +} + +inline LLColor4U::LLColor4U(const U8 *vec) +{ + mV[VX] = vec[VX]; + mV[VY] = vec[VY]; + mV[VZ] = vec[VZ]; + mV[VW] = vec[VW]; +} + +/* +inline LLColor4U::operator LLColor4() +{ + return(LLColor4((F32)mV[VRED]/255.f,(F32)mV[VGREEN]/255.f,(F32)mV[VBLUE]/255.f,(F32)mV[VALPHA]/255.f)); +} +*/ + +inline const LLColor4U& LLColor4U::setToBlack(void) +{ + mV[VX] = 0; + mV[VY] = 0; + mV[VZ] = 0; + mV[VW] = 255; + return (*this); +} + +inline const LLColor4U& LLColor4U::setToWhite(void) +{ + mV[VX] = 255; + mV[VY] = 255; + mV[VZ] = 255; + mV[VW] = 255; + return (*this); +} + +inline const LLColor4U& LLColor4U::setVec(const U8 x, const U8 y, const U8 z) +{ + mV[VX] = x; + mV[VY] = y; + mV[VZ] = z; + +// no change to alpha! +// mV[VW] = 255; + + return (*this); +} + +inline const LLColor4U& LLColor4U::setVec(const U8 r, const U8 g, const U8 b, U8 a) +{ + mV[0] = r; + mV[1] = g; + mV[2] = b; + mV[3] = a; + return (*this); +} + +inline const LLColor4U& LLColor4U::setVec(const LLColor4U &vec) +{ + mV[VX] = vec.mV[VX]; + mV[VY] = vec.mV[VY]; + mV[VZ] = vec.mV[VZ]; + mV[VW] = vec.mV[VW]; + return (*this); +} + +/* +inline const LLColor4U& LLColor4U::setVec(const LLColor4 &vec) +{ + mV[VX] = (U8) (llmin(1.f, vec.mV[VX]) * 255.f); + mV[VY] = (U8) (llmin(1.f, vec.mV[VY]) * 255.f); + mV[VZ] = (U8) (llmin(1.f, vec.mV[VZ]) * 255.f); + mV[VW] = (U8) (llmin(1.f, vec.mV[VW]) * 255.f); + return (*this); +} +*/ + +inline const LLColor4U& LLColor4U::setVec(const U8 *vec) +{ + mV[VX] = vec[VX]; + mV[VY] = vec[VY]; + mV[VZ] = vec[VZ]; + mV[VW] = vec[VW]; + return (*this); +} + +inline const LLColor4U& LLColor4U::setAlpha(U8 a) +{ + mV[VW] = a; + return (*this); +} + +// LLColor4U Magnitude and Normalization Functions +// bookmark + +inline F32 LLColor4U::magVec(void) const +{ + return fsqrtf( ((F32)mV[VX]) * mV[VX] + ((F32)mV[VY]) * mV[VY] + ((F32)mV[VZ]) * mV[VZ] ); +} + +inline F32 LLColor4U::magVecSquared(void) const +{ + return ((F32)mV[VX]) * mV[VX] + ((F32)mV[VY]) * mV[VY] + ((F32)mV[VZ]) * mV[VZ]; +} + +inline LLColor4U operator+(const LLColor4U &a, const LLColor4U &b) +{ + return LLColor4U( + a.mV[VX] + b.mV[VX], + a.mV[VY] + b.mV[VY], + a.mV[VZ] + b.mV[VZ], + a.mV[VW] + b.mV[VW]); +} + +inline LLColor4U operator-(const LLColor4U &a, const LLColor4U &b) +{ + return LLColor4U( + a.mV[VX] - b.mV[VX], + a.mV[VY] - b.mV[VY], + a.mV[VZ] - b.mV[VZ], + a.mV[VW] - b.mV[VW]); +} + +inline LLColor4U operator*(const LLColor4U &a, const LLColor4U &b) +{ + return LLColor4U( + a.mV[VX] * b.mV[VX], + a.mV[VY] * b.mV[VY], + a.mV[VZ] * b.mV[VZ], + a.mV[VW] * b.mV[VW]); +} + +inline LLColor4U LLColor4U::addClampMax(const LLColor4U &color) +{ + return LLColor4U(llmin((S32)mV[VX] + color.mV[VX], 255), + llmin((S32)mV[VY] + color.mV[VY], 255), + llmin((S32)mV[VZ] + color.mV[VZ], 255), + llmin((S32)mV[VW] + color.mV[VW], 255)); +} + +inline LLColor4U LLColor4U::multAll(const F32 k) +{ + // Round to nearest + return LLColor4U( + (U8)llround(mV[VX] * k), + (U8)llround(mV[VY] * k), + (U8)llround(mV[VZ] * k), + (U8)llround(mV[VW] * k)); +} +/* +inline LLColor4U operator*(const LLColor4U &a, U8 k) +{ + // only affects rgb (not a!) + return LLColor4U( + a.mV[VX] * k, + a.mV[VY] * k, + a.mV[VZ] * k, + a.mV[VW]); +} + +inline LLColor4U operator*(U8 k, const LLColor4U &a) +{ + // only affects rgb (not a!) + return LLColor4U( + a.mV[VX] * k, + a.mV[VY] * k, + a.mV[VZ] * k, + a.mV[VW]); +} + +inline LLColor4U operator%(U8 k, const LLColor4U &a) +{ + // only affects alpha (not rgb!) + return LLColor4U( + a.mV[VX], + a.mV[VY], + a.mV[VZ], + a.mV[VW] * k ); +} + +inline LLColor4U operator%(const LLColor4U &a, U8 k) +{ + // only affects alpha (not rgb!) + return LLColor4U( + a.mV[VX], + a.mV[VY], + a.mV[VZ], + a.mV[VW] * k ); +} +*/ + +inline bool operator==(const LLColor4U &a, const LLColor4U &b) +{ + return ( (a.mV[VX] == b.mV[VX]) + &&(a.mV[VY] == b.mV[VY]) + &&(a.mV[VZ] == b.mV[VZ]) + &&(a.mV[VW] == b.mV[VW])); +} + +inline bool operator!=(const LLColor4U &a, const LLColor4U &b) +{ + return ( (a.mV[VX] != b.mV[VX]) + ||(a.mV[VY] != b.mV[VY]) + ||(a.mV[VZ] != b.mV[VZ]) + ||(a.mV[VW] != b.mV[VW])); +} + +inline const LLColor4U& operator+=(LLColor4U &a, const LLColor4U &b) +{ + a.mV[VX] += b.mV[VX]; + a.mV[VY] += b.mV[VY]; + a.mV[VZ] += b.mV[VZ]; + a.mV[VW] += b.mV[VW]; + return a; +} + +inline const LLColor4U& operator-=(LLColor4U &a, const LLColor4U &b) +{ + a.mV[VX] -= b.mV[VX]; + a.mV[VY] -= b.mV[VY]; + a.mV[VZ] -= b.mV[VZ]; + a.mV[VW] -= b.mV[VW]; + return a; +} + +inline const LLColor4U& operator*=(LLColor4U &a, U8 k) +{ + // only affects rgb (not a!) + a.mV[VX] *= k; + a.mV[VY] *= k; + a.mV[VZ] *= k; + return a; +} + +inline const LLColor4U& operator%=(LLColor4U &a, U8 k) +{ + // only affects alpha (not rgb!) + a.mV[VW] *= k; + return a; +} + +inline F32 distVec(const LLColor4U &a, const LLColor4U &b) +{ + LLColor4U vec = a - b; + return (vec.magVec()); +} + +inline F32 distVec_squared(const LLColor4U &a, const LLColor4U &b) +{ + LLColor4U vec = a - b; + return (vec.magVecSquared()); +} + +void LLColor4U::setVecScaleClamp(const LLColor4& color) +{ + F32 color_scale_factor = 255.f; + F32 max_color = llmax(color.mV[0], color.mV[1], color.mV[2]); + if (max_color > 1.f) + { + color_scale_factor /= max_color; + } + const S32 MAX_COLOR = 255; + S32 r = llround(color.mV[0] * color_scale_factor); + if (r > MAX_COLOR) + { + r = MAX_COLOR; + } + else if (r < 0) + { + r = 0; + } + mV[0] = r; + + S32 g = llround(color.mV[1] * color_scale_factor); + if (g > MAX_COLOR) + { + g = MAX_COLOR; + } + else if (g < 0) + { + g = 0; + } + mV[1] = g; + + S32 b = llround(color.mV[2] * color_scale_factor); + if (b > MAX_COLOR) + { + b = MAX_COLOR; + } + else if (b < 0) + { + b = 0; + } + mV[2] = b; + + // Alpha shouldn't be scaled, just clamped... + S32 a = llround(color.mV[3] * MAX_COLOR); + if (a > MAX_COLOR) + { + a = MAX_COLOR; + } + else if (a < 0) + { + a = 0; + } + mV[3] = a; +} + +void LLColor4U::setVecScaleClamp(const LLColor3& color) +{ + F32 color_scale_factor = 255.f; + F32 max_color = llmax(color.mV[0], color.mV[1], color.mV[2]); + if (max_color > 1.f) + { + color_scale_factor /= max_color; + } + + const S32 MAX_COLOR = 255; + S32 r = llround(color.mV[0] * color_scale_factor); + if (r > MAX_COLOR) + { + r = MAX_COLOR; + } + else + if (r < 0) + { + r = 0; + } + mV[0] = r; + + S32 g = llround(color.mV[1] * color_scale_factor); + if (g > MAX_COLOR) + { + g = MAX_COLOR; + } + else + if (g < 0) + { + g = 0; + } + mV[1] = g; + + S32 b = llround(color.mV[2] * color_scale_factor); + if (b > MAX_COLOR) + { + b = MAX_COLOR; + } + if (b < 0) + { + b = 0; + } + mV[2] = b; + + mV[3] = 255; +} + + +#endif + diff --git a/indra/llmath/v4math.cpp b/indra/llmath/v4math.cpp new file mode 100644 index 0000000000..16fbdd24a1 --- /dev/null +++ b/indra/llmath/v4math.cpp @@ -0,0 +1,124 @@ +/** + * @file v4math.cpp + * @brief LLVector4 class implementation. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +//#include "vmath.h" +#include "v3math.h" +#include "v4math.h" +#include "m4math.h" +#include "m3math.h" +#include "llquaternion.h" + +// LLVector4 + +// Axis-Angle rotations + +/* +const LLVector4& LLVector4::rotVec(F32 angle, const LLVector4 &vec) +{ + if ( !vec.isExactlyZero() && angle ) + { + *this = *this * LLMatrix4(angle, vec); + } + return *this; +} + +const LLVector4& LLVector4::rotVec(F32 angle, F32 x, F32 y, F32 z) +{ + LLVector3 vec(x, y, z); + if ( !vec.isExactlyZero() && angle ) + { + *this = *this * LLMatrix4(angle, vec); + } + return *this; +} +*/ + +const LLVector4& LLVector4::rotVec(const LLMatrix4 &mat) +{ + *this = *this * mat; + return *this; +} + +const LLVector4& LLVector4::rotVec(const LLQuaternion &q) +{ + *this = *this * q; + return *this; +} + +const LLVector4& LLVector4::scaleVec(const LLVector4& vec) +{ + mV[VX] *= vec.mV[VX]; + mV[VY] *= vec.mV[VY]; + mV[VZ] *= vec.mV[VZ]; + mV[VW] *= vec.mV[VW]; + + return *this; +} + +// Sets all values to absolute value of their original values +// Returns TRUE if data changed +BOOL LLVector4::abs() +{ + BOOL ret = FALSE; + + if (mV[0] < 0.f) { mV[0] = -mV[0]; ret = TRUE; } + if (mV[1] < 0.f) { mV[1] = -mV[1]; ret = TRUE; } + if (mV[2] < 0.f) { mV[2] = -mV[2]; ret = TRUE; } + if (mV[3] < 0.f) { mV[3] = -mV[3]; ret = TRUE; } + + return ret; +} + + +std::ostream& operator<<(std::ostream& s, const LLVector4 &a) +{ + s << "{ " << a.mV[VX] << ", " << a.mV[VY] << ", " << a.mV[VZ] << ", " << a.mV[VW] << " }"; + return s; +} + + +// Non-member functions + +F32 angle_between( const LLVector4& a, const LLVector4& b ) +{ + LLVector4 an = a; + LLVector4 bn = b; + an.normVec(); + bn.normVec(); + F32 cosine = an * bn; + F32 angle = (cosine >= 1.0f) ? 0.0f : + (cosine <= -1.0f) ? F_PI : + acos(cosine); + return angle; +} + +BOOL are_parallel(const LLVector4 &a, const LLVector4 &b, F32 epsilon) +{ + LLVector4 an = a; + LLVector4 bn = b; + an.normVec(); + bn.normVec(); + F32 dot = an * bn; + if ( (1.0f - fabs(dot)) < epsilon) + return TRUE; + return FALSE; +} + + +LLVector3 vec4to3(const LLVector4 &vec) +{ + return LLVector3( vec.mV[VX], vec.mV[VY], vec.mV[VZ] ); +} + +LLVector4 vec3to4(const LLVector3 &vec) +{ + return LLVector4(vec.mV[VX], vec.mV[VY], vec.mV[VZ]); +} + diff --git a/indra/llmath/v4math.h b/indra/llmath/v4math.h new file mode 100644 index 0000000000..abdc66e0b1 --- /dev/null +++ b/indra/llmath/v4math.h @@ -0,0 +1,385 @@ +/** + * @file v4math.h + * @brief LLVector4 class header file. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_V4MATH_H +#define LL_V4MATH_H + +#include "llerror.h" +#include "llmath.h" +#include "v3math.h" + +class LLMatrix3; +class LLMatrix4; +class LLQuaternion; + +// LLVector4 = |x y z w| + +static const U32 LENGTHOFVECTOR4 = 4; + +#if LL_WINDOWS +__declspec( align(16) ) +#endif + +class LLVector4 +{ + public: + F32 mV[LENGTHOFVECTOR4]; + LLVector4(); // Initializes LLVector4 to (0, 0, 0, 1) + explicit LLVector4(const F32 *vec); // Initializes LLVector4 to (vec[0]. vec[1], vec[2], 1) + explicit LLVector4(const LLVector3 &vec); // Initializes LLVector4 to (vec, 1) + explicit LLVector4(const LLVector3 &vec, F32 w); // Initializes LLVector4 to (vec, w) + LLVector4(F32 x, F32 y, F32 z); // Initializes LLVector4 to (x. y, z, 1) + LLVector4(F32 x, F32 y, F32 z, F32 w); + + LLSD getValue() const + { + LLSD ret; + ret[0] = mV[0]; + ret[1] = mV[1]; + ret[2] = mV[2]; + ret[3] = mV[3]; + return ret; + } + + inline BOOL isFinite() const; // checks to see if all values of LLVector3 are finite + + inline void clearVec(); // Clears LLVector4 to (0, 0, 0, 1) + inline void zeroVec(); // zero LLVector4 to (0, 0, 0, 0) + inline void setVec(F32 x, F32 y, F32 z); // Sets LLVector4 to (x, y, z, 1) + inline void setVec(F32 x, F32 y, F32 z, F32 w); // Sets LLVector4 to (x, y, z, w) + inline void setVec(const LLVector4 &vec); // Sets LLVector4 to vec + inline void setVec(const LLVector3 &vec, F32 w = 1.f); // Sets LLVector4 to LLVector3 vec + inline void setVec(const F32 *vec); // Sets LLVector4 to vec + + F32 magVec() const; // Returns magnitude of LLVector4 + F32 magVecSquared() const; // Returns magnitude squared of LLVector4 + F32 normVec(); // Normalizes and returns the magnitude of LLVector4 + + // Sets all values to absolute value of their original values + // Returns TRUE if data changed + BOOL abs(); + + BOOL isExactlyClear() const { return (mV[VW] == 1.0f) && !mV[VX] && !mV[VY] && !mV[VZ]; } + BOOL isExactlyZero() const { return !mV[VW] && !mV[VX] && !mV[VY] && !mV[VZ]; } + + const LLVector4& rotVec(F32 angle, const LLVector4 &vec); // Rotates about vec by angle radians + const LLVector4& rotVec(F32 angle, F32 x, F32 y, F32 z); // Rotates about x,y,z by angle radians + const LLVector4& rotVec(const LLMatrix4 &mat); // Rotates by MAT4 mat + const LLVector4& rotVec(const LLQuaternion &q); // Rotates by QUAT q + + const LLVector4& scaleVec(const LLVector4& vec); // Scales component-wise by vec + + F32 operator[](int idx) const { return mV[idx]; } + F32 &operator[](int idx) { return mV[idx]; } + + friend std::ostream& operator<<(std::ostream& s, const LLVector4 &a); // Print a + friend LLVector4 operator+(const LLVector4 &a, const LLVector4 &b); // Return vector a + b + friend LLVector4 operator-(const LLVector4 &a, const LLVector4 &b); // Return vector a minus b + friend F32 operator*(const LLVector4 &a, const LLVector4 &b); // Return a dot b + friend LLVector4 operator%(const LLVector4 &a, const LLVector4 &b); // Return a cross b + friend LLVector4 operator/(const LLVector4 &a, F32 k); // Return a divided by scaler k + friend LLVector4 operator*(const LLVector4 &a, F32 k); // Return a times scaler k + friend LLVector4 operator*(F32 k, const LLVector4 &a); // Return a times scaler k + friend bool operator==(const LLVector4 &a, const LLVector4 &b); // Return a == b + friend bool operator!=(const LLVector4 &a, const LLVector4 &b); // Return a != b + + friend const LLVector4& operator+=(LLVector4 &a, const LLVector4 &b); // Return vector a + b + friend const LLVector4& operator-=(LLVector4 &a, const LLVector4 &b); // Return vector a minus b + friend const LLVector4& operator%=(LLVector4 &a, const LLVector4 &b); // Return a cross b + friend const LLVector4& operator*=(LLVector4 &a, F32 k); // Return a times scaler k + friend const LLVector4& operator/=(LLVector4 &a, F32 k); // Return a divided by scaler k + + friend LLVector4 operator-(const LLVector4 &a); // Return vector -a +} +#if LL_DARWIN +__attribute__ ((aligned (16))) +#endif +; + + +// Non-member functions +F32 angle_between(const LLVector4 &a, const LLVector4 &b); // Returns angle (radians) between a and b +BOOL are_parallel(const LLVector4 &a, const LLVector4 &b, F32 epsilon=F_APPROXIMATELY_ZERO); // Returns TRUE if a and b are very close to parallel +F32 dist_vec(const LLVector4 &a, const LLVector4 &b); // Returns distance between a and b +F32 dist_vec_squared(const LLVector4 &a, const LLVector4 &b); // Returns distance squared between a and b +LLVector3 vec4to3(const LLVector4 &vec); +LLVector4 vec3to4(const LLVector3 &vec); +LLVector4 lerp(const LLVector4 &a, const LLVector4 &b, F32 u); // Returns a vector that is a linear interpolation between a and b + +// Constructors + +inline LLVector4::LLVector4(void) +{ + mV[VX] = 0.f; + mV[VY] = 0.f; + mV[VZ] = 0.f; + mV[VW] = 1.f; +} + +inline LLVector4::LLVector4(F32 x, F32 y, F32 z) +{ + mV[VX] = x; + mV[VY] = y; + mV[VZ] = z; + mV[VW] = 1.f; +} + +inline LLVector4::LLVector4(F32 x, F32 y, F32 z, F32 w) +{ + mV[VX] = x; + mV[VY] = y; + mV[VZ] = z; + mV[VW] = w; +} + +inline LLVector4::LLVector4(const F32 *vec) +{ + mV[VX] = vec[VX]; + mV[VY] = vec[VY]; + mV[VZ] = vec[VZ]; + mV[VW] = vec[VW]; +} + +inline LLVector4::LLVector4(const LLVector3 &vec) +{ + mV[VX] = vec.mV[VX]; + mV[VY] = vec.mV[VY]; + mV[VZ] = vec.mV[VZ]; + mV[VW] = 1.f; +} + +inline LLVector4::LLVector4(const LLVector3 &vec, F32 w) +{ + mV[VX] = vec.mV[VX]; + mV[VY] = vec.mV[VY]; + mV[VZ] = vec.mV[VZ]; + mV[VW] = w; +} + + +inline BOOL LLVector4::isFinite() const +{ + return (llfinite(mV[VX]) && llfinite(mV[VY]) && llfinite(mV[VZ]) && llfinite(mV[VW])); +} + +// Clear and Assignment Functions + +inline void LLVector4::clearVec(void) +{ + mV[VX] = 0.f; + mV[VY] = 0.f; + mV[VZ] = 0.f; + mV[VW] = 1.f; +} + +inline void LLVector4::zeroVec(void) +{ + mV[VX] = 0.f; + mV[VY] = 0.f; + mV[VZ] = 0.f; + mV[VW] = 0.f; +} + +inline void LLVector4::setVec(F32 x, F32 y, F32 z) +{ + mV[VX] = x; + mV[VY] = y; + mV[VZ] = z; + mV[VW] = 1.f; +} + +inline void LLVector4::setVec(F32 x, F32 y, F32 z, F32 w) +{ + mV[VX] = x; + mV[VY] = y; + mV[VZ] = z; + mV[VW] = w; +} + +inline void LLVector4::setVec(const LLVector4 &vec) +{ + mV[VX] = vec.mV[VX]; + mV[VY] = vec.mV[VY]; + mV[VZ] = vec.mV[VZ]; + mV[VW] = vec.mV[VW]; +} + +inline void LLVector4::setVec(const LLVector3 &vec, F32 w) +{ + mV[VX] = vec.mV[VX]; + mV[VY] = vec.mV[VY]; + mV[VZ] = vec.mV[VZ]; + mV[VW] = w; +} + +inline void LLVector4::setVec(const F32 *vec) +{ + mV[VX] = vec[VX]; + mV[VY] = vec[VY]; + mV[VZ] = vec[VZ]; + mV[VW] = vec[VW]; +} + +// LLVector4 Magnitude and Normalization Functions + +inline F32 LLVector4::magVec(void) const +{ + return fsqrtf(mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]); +} + +inline F32 LLVector4::magVecSquared(void) const +{ + return mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]; +} + +// LLVector4 Operators + +inline LLVector4 operator+(const LLVector4 &a, const LLVector4 &b) +{ + LLVector4 c(a); + return c += b; +} + +inline LLVector4 operator-(const LLVector4 &a, const LLVector4 &b) +{ + LLVector4 c(a); + return c -= b; +} + +inline F32 operator*(const LLVector4 &a, const LLVector4 &b) +{ + return (a.mV[VX]*b.mV[VX] + a.mV[VY]*b.mV[VY] + a.mV[VZ]*b.mV[VZ]); +} + +inline LLVector4 operator%(const LLVector4 &a, const LLVector4 &b) +{ + return LLVector4(a.mV[VY]*b.mV[VZ] - b.mV[VY]*a.mV[VZ], a.mV[VZ]*b.mV[VX] - b.mV[VZ]*a.mV[VX], a.mV[VX]*b.mV[VY] - b.mV[VX]*a.mV[VY]); +} + +inline LLVector4 operator/(const LLVector4 &a, F32 k) +{ + F32 t = 1.f / k; + return LLVector4( a.mV[VX] * t, a.mV[VY] * t, a.mV[VZ] * t ); +} + + +inline LLVector4 operator*(const LLVector4 &a, F32 k) +{ + return LLVector4( a.mV[VX] * k, a.mV[VY] * k, a.mV[VZ] * k ); +} + +inline LLVector4 operator*(F32 k, const LLVector4 &a) +{ + return LLVector4( a.mV[VX] * k, a.mV[VY] * k, a.mV[VZ] * k ); +} + +inline bool operator==(const LLVector4 &a, const LLVector4 &b) +{ + return ( (a.mV[VX] == b.mV[VX]) + &&(a.mV[VY] == b.mV[VY]) + &&(a.mV[VZ] == b.mV[VZ])); +} + +inline bool operator!=(const LLVector4 &a, const LLVector4 &b) +{ + return ( (a.mV[VX] != b.mV[VX]) + ||(a.mV[VY] != b.mV[VY]) + ||(a.mV[VZ] != b.mV[VZ])); +} + +inline const LLVector4& operator+=(LLVector4 &a, const LLVector4 &b) +{ + a.mV[VX] += b.mV[VX]; + a.mV[VY] += b.mV[VY]; + a.mV[VZ] += b.mV[VZ]; + return a; +} + +inline const LLVector4& operator-=(LLVector4 &a, const LLVector4 &b) +{ + a.mV[VX] -= b.mV[VX]; + a.mV[VY] -= b.mV[VY]; + a.mV[VZ] -= b.mV[VZ]; + return a; +} + +inline const LLVector4& operator%=(LLVector4 &a, const LLVector4 &b) +{ + LLVector4 ret(a.mV[VY]*b.mV[VZ] - b.mV[VY]*a.mV[VZ], a.mV[VZ]*b.mV[VX] - b.mV[VZ]*a.mV[VX], a.mV[VX]*b.mV[VY] - b.mV[VX]*a.mV[VY]); + a = ret; + return a; +} + +inline const LLVector4& operator*=(LLVector4 &a, F32 k) +{ + a.mV[VX] *= k; + a.mV[VY] *= k; + a.mV[VZ] *= k; + return a; +} + +inline const LLVector4& operator/=(LLVector4 &a, F32 k) +{ + F32 t = 1.f / k; + a.mV[VX] *= t; + a.mV[VY] *= t; + a.mV[VZ] *= t; + return a; +} + +inline LLVector4 operator-(const LLVector4 &a) +{ + return LLVector4( -a.mV[VX], -a.mV[VY], -a.mV[VZ] ); +} + +inline F32 dist_vec(const LLVector4 &a, const LLVector4 &b) +{ + LLVector4 vec = a - b; + return (vec.magVec()); +} + +inline F32 dist_vec_squared(const LLVector4 &a, const LLVector4 &b) +{ + LLVector4 vec = a - b; + return (vec.magVecSquared()); +} + +inline LLVector4 lerp(const LLVector4 &a, const LLVector4 &b, F32 u) +{ + return LLVector4( + a.mV[VX] + (b.mV[VX] - a.mV[VX]) * u, + a.mV[VY] + (b.mV[VY] - a.mV[VY]) * u, + a.mV[VZ] + (b.mV[VZ] - a.mV[VZ]) * u, + a.mV[VW] + (b.mV[VW] - a.mV[VW]) * u); +} + +inline F32 LLVector4::normVec(void) +{ + F32 mag = fsqrtf(mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]); + F32 oomag; + + if (mag > FP_MAG_THRESHOLD) + { + oomag = 1.f/mag; + mV[VX] *= oomag; + mV[VY] *= oomag; + mV[VZ] *= oomag; + } + else + { + mV[0] = 0.f; + mV[1] = 0.f; + mV[2] = 0.f; + mag = 0; + } + return (mag); +} + + +#endif + diff --git a/indra/llmath/xform.cpp b/indra/llmath/xform.cpp new file mode 100644 index 0000000000..715b993b2b --- /dev/null +++ b/indra/llmath/xform.cpp @@ -0,0 +1,96 @@ +/** + * @file xform.cpp + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "xform.h" + +LLXform::LLXform() +{ + init(); +} + +LLXform::~LLXform() +{ +} + + +LLXform* LLXform::getRoot() const +{ + const LLXform* root = this; + while(root->mParent) + { + root = root->mParent; + } + return (LLXform*)root; +} + +BOOL LLXform::isRoot() const +{ + return (!mParent); +} + +BOOL LLXform::isRootEdit() const +{ + return (!mParent); +} + +LLXformMatrix::~LLXformMatrix() +{ +} + +void LLXformMatrix::update() +{ + if (mParent) + { + mWorldPosition = mPosition; + if (mParent->getScaleChildOffset()) + { + mWorldPosition.scaleVec(mParent->getScale()); + } + mWorldPosition *= mParent->getWorldRotation(); + mWorldPosition += mParent->getWorldPosition(); + mWorldRotation = mRotation * mParent->getWorldRotation(); + } + else + { + mWorldPosition = mPosition; + mWorldRotation = mRotation; + } +} + +void LLXformMatrix::updateMatrix(BOOL update_bounds) +{ + update(); + + mWorldMatrix.initAll(mScale, mWorldRotation, mWorldPosition); + + if (update_bounds && (mChanged & MOVED)) + { + mMin.mV[0] = mMax.mV[0] = mWorldMatrix.mMatrix[3][0]; + mMin.mV[1] = mMax.mV[1] = mWorldMatrix.mMatrix[3][1]; + mMin.mV[2] = mMax.mV[2] = mWorldMatrix.mMatrix[3][2]; + + F32 f0 = (fabs(mWorldMatrix.mMatrix[0][0])+fabs(mWorldMatrix.mMatrix[1][0])+fabs(mWorldMatrix.mMatrix[2][0])) * 0.5f; + F32 f1 = (fabs(mWorldMatrix.mMatrix[0][1])+fabs(mWorldMatrix.mMatrix[1][1])+fabs(mWorldMatrix.mMatrix[2][1])) * 0.5f; + F32 f2 = (fabs(mWorldMatrix.mMatrix[0][2])+fabs(mWorldMatrix.mMatrix[1][2])+fabs(mWorldMatrix.mMatrix[2][2])) * 0.5f; + + mMin.mV[0] -= f0; + mMin.mV[1] -= f1; + mMin.mV[2] -= f2; + + mMax.mV[0] += f0; + mMax.mV[1] += f1; + mMax.mV[2] += f2; + } +} + +void LLXformMatrix::getMinMax(LLVector3& min, LLVector3& max) const +{ + min = mMin; + max = mMax; +} diff --git a/indra/llmath/xform.h b/indra/llmath/xform.h new file mode 100644 index 0000000000..bd4bc74c79 --- /dev/null +++ b/indra/llmath/xform.h @@ -0,0 +1,269 @@ +/** + * @file xform.h + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_XFORM_H +#define LL_XFORM_H + +#include "v3math.h" +#include "m4math.h" +#include "llquaternion.h" + +#define CHECK_FOR_FINITE + + +const F32 MAX_OBJECT_Z = 768.f; +const F32 MIN_OBJECT_Z = -256.f; +const F32 MIN_OBJECT_SCALE = 0.01f; +const F32 MAX_OBJECT_SCALE = 10.f; + +class LLXform +{ +protected: + LLVector3 mPosition; + LLQuaternion mRotation; + LLVector3 mScale; + + //RN: TODO: move these world transform members to LLXformMatrix + // as they are *never* updated or accessed in the base class + LLVector3 mWorldPosition; + LLQuaternion mWorldRotation; + + LLXform* mParent; + U32 mChanged; + + BOOL mScaleChildOffset; + +public: + typedef enum e_changed_flags + { + UNCHANGED = 0x00, + TRANSLATED = 0x01, + ROTATED = 0x02, + SCALED = 0x04, + SHIFTED = 0x08, + GEOMETRY = 0x10, + TEXTURE = 0x20, + MOVED = TRANSLATED|ROTATED|SCALED, + SILHOUETTE = 0x40, + ALL_CHANGED = 0x7f + }EChangedFlags; + + void init() + { + mParent = NULL; + mChanged = UNCHANGED; + mPosition.setVec(0,0,0); + mRotation.loadIdentity(); + mScale. setVec(1,1,1); + mWorldPosition.clearVec(); + mWorldRotation.loadIdentity(); + mScaleChildOffset = FALSE; + } + + LLXform(); + virtual ~LLXform(); + + void getLocalMat4(LLMatrix4 &mat) const { mat.initAll(mScale, mRotation, mPosition); } + + inline BOOL setParent(LLXform *parent); + + inline void setPosition(const LLVector3& pos); + inline void setPosition(const F32 x, const F32 y, const F32 z); + inline void setPositionX(const F32 x); + inline void setPositionY(const F32 y); + inline void setPositionZ(const F32 z); + inline void addPosition(const LLVector3& pos); + + + inline void setScale(const LLVector3& scale); + inline void setScale(const F32 x, const F32 y, const F32 z); + inline void setRotation(const LLQuaternion& rot); + inline void setRotation(const F32 x, const F32 y, const F32 z); + inline void setRotation(const F32 x, const F32 y, const F32 z, const F32 s); + + void setChanged(const U32 bits) { mChanged |= bits; } + BOOL isChanged() const { return mChanged; } + BOOL isChanged(const U32 bits) const { return mChanged & bits; } + void clearChanged() { mChanged = 0; } + void clearChanged(U32 bits) { mChanged &= ~bits; } + + void setScaleChildOffset(BOOL scale) { mScaleChildOffset = scale; } + BOOL getScaleChildOffset() { return mScaleChildOffset; } + + LLXform* getParent() const { return mParent; } + LLXform* getRoot() const; + virtual BOOL isRoot() const; + virtual BOOL isRootEdit() const; + + const LLVector3& getPosition() const { return mPosition; } + const LLVector3& getScale() const { return mScale; } + const LLQuaternion& getRotation() const { return mRotation; } + const LLVector3& getPositionW() const { return mWorldPosition; } + const LLQuaternion& getWorldRotation() const { return mWorldRotation; } + const LLVector3& getWorldPosition() const { return mWorldPosition; } +}; + +class LLXformMatrix : public LLXform +{ +public: + LLXformMatrix() : LLXform() {}; + virtual ~LLXformMatrix(); + + const LLMatrix4& getWorldMatrix() const { return mWorldMatrix; } + void setWorldMatrix (const LLMatrix4& mat) { mWorldMatrix = mat; } + + void init() + { + mWorldMatrix.identity(); + mMin.clearVec(); + mMax.clearVec(); + + LLXform::init(); + } + + void update(); + void updateMatrix(BOOL update_bounds = TRUE); + void getMinMax(LLVector3& min,LLVector3& max) const; + +protected: + LLMatrix4 mWorldMatrix; + LLVector3 mMin; + LLVector3 mMax; + +}; + +BOOL LLXform::setParent(LLXform* parent) +{ + // Validate and make sure we're not creating a loop + if (parent == mParent) + { + return TRUE; + } + if (parent) + { + LLXform *cur_par = parent->mParent; + while (cur_par) + { + if (cur_par == this) + { + llwarns << "LLXform::setParent Creating loop when setting parent!" << llendl; + return FALSE; + } + cur_par = cur_par->mParent; + } + } + mParent = parent; + return TRUE; +} + +#ifdef CHECK_FOR_FINITE +void LLXform::setPosition(const LLVector3& pos) +{ + setChanged(TRANSLATED); + if (pos.isFinite()) + mPosition = pos; + else + llerror("Non Finite in LLXform::setPosition(LLVector3)", 0); +} + +void LLXform::setPosition(const F32 x, const F32 y, const F32 z) +{ + setChanged(TRANSLATED); + if (llfinite(x) && llfinite(y) && llfinite(z)) + mPosition.setVec(x,y,z); + else + llerror("Non Finite in LLXform::setPosition(F32,F32,F32)", 0); +} + +void LLXform::setPositionX(const F32 x) +{ + setChanged(TRANSLATED); + if (llfinite(x)) + mPosition.mV[VX] = x; + else + llerror("Non Finite in LLXform::setPositionX", 0); +} + +void LLXform::setPositionY(const F32 y) +{ + setChanged(TRANSLATED); + if (llfinite(y)) + mPosition.mV[VY] = y; + else + llerror("Non Finite in LLXform::setPositionY", 0); +} + +void LLXform::setPositionZ(const F32 z) +{ + setChanged(TRANSLATED); + if (llfinite(z)) + mPosition.mV[VZ] = z; + else + llerror("Non Finite in LLXform::setPositionZ", 0); +} + +void LLXform::addPosition(const LLVector3& pos) +{ + setChanged(TRANSLATED); + if (pos.isFinite()) + mPosition += pos; + else + llerror("Non Finite in LLXform::addPosition", 0); +} + +void LLXform::setScale(const LLVector3& scale) +{ + setChanged(SCALED); + if (scale.isFinite()) + mScale = scale; + else + llerror("Non Finite in LLXform::setScale", 0); +} +void LLXform::setScale(const F32 x, const F32 y, const F32 z) +{ + setChanged(SCALED); + if (llfinite(x) && llfinite(y) && llfinite(z)) + mScale.setVec(x,y,z); + else + llerror("Non Finite in LLXform::setScale", 0); +} +void LLXform::setRotation(const LLQuaternion& rot) +{ + setChanged(ROTATED); + if (rot.isFinite()) + mRotation = rot; + else + llerror("Non Finite in LLXform::setRotation", 0); +} +void LLXform::setRotation(const F32 x, const F32 y, const F32 z) +{ + setChanged(ROTATED); + if (llfinite(x) && llfinite(y) && llfinite(z)) + { + mRotation.setQuat(x,y,z); + } + else + { + llerror("Non Finite in LLXform::setRotation", 0); + } +} +void LLXform::setRotation(const F32 x, const F32 y, const F32 z, const F32 s) +{ + setChanged(ROTATED); + if (llfinite(x) && llfinite(y) && llfinite(z) && llfinite(s)) + { + mRotation.mQ[VX] = x; mRotation.mQ[VY] = y; mRotation.mQ[VZ] = z; mRotation.mQ[VS] = s; + } + else + { + llerror("Non Finite in LLXform::setRotation", 0); + } +} + +#endif + +#endif diff --git a/indra/llmessage/llassetstorage.cpp b/indra/llmessage/llassetstorage.cpp new file mode 100644 index 0000000000..b23567288d --- /dev/null +++ b/indra/llmessage/llassetstorage.cpp @@ -0,0 +1,1116 @@ +/** + * @file llassetstorage.cpp + * @brief Implementation of the base asset storage system. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +// system library includes +#include +#include +#include +#include +#include + +#include "llassetstorage.h" + +// linden library includes +#include "llmath.h" +#include "llstring.h" +#include "lldir.h" +#include "llsd.h" + +// this library includes +#include "message.h" +#include "llxfermanager.h" +#include "llvfile.h" +#include "llvfs.h" +#include "lldbstrings.h" + +#include "lltransfersourceasset.h" +#include "lltransfertargetvfile.h" // For debugging + +LLAssetStorage *gAssetStorage = NULL; + +const LLUUID CATEGORIZE_LOST_AND_FOUND_ID("00000000-0000-0000-0000-000000000010"); + +const F32 LL_ASSET_STORAGE_TIMEOUT = 300.0f; // anything that takes longer than this will abort + + + +///---------------------------------------------------------------------------- +/// LLAssetInfo +///---------------------------------------------------------------------------- + +LLAssetInfo::LLAssetInfo( void ) +: mDescription(), + mName(), + mUuid(), + mCreatorID(), + mType( LLAssetType::AT_NONE ) +{ } + +LLAssetInfo::LLAssetInfo( const LLUUID& object_id, const LLUUID& creator_id, + LLAssetType::EType type, const char* name, + const char* desc ) +: mUuid( object_id ), + mCreatorID( creator_id ), + mType( type ) +{ + setName( name ); + setDescription( desc ); +} + +LLAssetInfo::LLAssetInfo( const LLNameValue& nv ) +{ + setFromNameValue( nv ); +} + +// make sure the name is short enough, and strip all pipes since they +// are reserved characters in our inventory tracking system. +void LLAssetInfo::setName( const std::string& name ) +{ + if( !name.empty() ) + { + mName.assign( name, 0, llmin((U32)name.size(), (U32)DB_INV_ITEM_NAME_STR_LEN) ); + mName.erase( std::remove(mName.begin(), mName.end(), '|'), + mName.end() ); + } +} + +// make sure the name is short enough, and strip all pipes since they +// are reserved characters in our inventory tracking system. +void LLAssetInfo::setDescription( const std::string& desc ) +{ + if( !desc.empty() ) + { + mDescription.assign( desc, 0, llmin((U32)desc.size(), + (U32)DB_INV_ITEM_DESC_STR_LEN) ); + mDescription.erase( std::remove(mDescription.begin(), + mDescription.end(), '|'), + mDescription.end() ); + } +} + +// Assets (aka potential inventory items) can be applied to an +// object in the world. We'll store that as a string name value +// pair where the name encodes part of asset info, and the value +// the rest. LLAssetInfo objects will be responsible for parsing +// the meaning out froman LLNameValue object. See the inventory +// design docs for details. Briefly: +// name=| +// value=||| +void LLAssetInfo::setFromNameValue( const LLNameValue& nv ) +{ + std::string str; + std::string buf; + std::string::size_type pos1; + std::string::size_type pos2; + + // convert the name to useful information + str.assign( nv.mName ); + pos1 = str.find('|'); + buf.assign( str, 0, pos1++ ); + mType = LLAssetType::lookup( buf.c_str() ); + buf.assign( str, pos1, std::string::npos ); + mUuid.set( buf.c_str() ); + + // convert the value to useful information + str.assign( nv.getAsset() ); + pos1 = str.find('|'); + buf.assign( str, 0, pos1++ ); + mCreatorID.set( buf.c_str() ); + pos2 = str.find( '|', pos1 ); + buf.assign( str, pos1, (pos2++) - pos1 ); + setName( buf.c_str() ); + buf.assign( str, pos2, std::string::npos ); + setDescription( buf.c_str() ); + llinfos << "uuid: " << mUuid << llendl; + llinfos << "creator: " << mCreatorID << llendl; +} + +///---------------------------------------------------------------------------- +/// LLAssetRequest +///---------------------------------------------------------------------------- + +LLAssetRequest::LLAssetRequest(const LLUUID &uuid, const LLAssetType::EType type) +: mUUID(uuid), + mType(type), + mDownCallback( NULL ), + mUpCallback( NULL ), + mInfoCallback( NULL ), + mUserData( NULL ), + mHost(), + mIsTemp( FALSE ), + mIsLocal(FALSE), + mIsPriority(FALSE), + mDataSentInFirstPacket(FALSE), + mDataIsInVFS( FALSE ) +{ + // Need to guarantee that this time is up to date, we may be creating a circuit even though we haven't been + // running a message system loop. + mTime = LLMessageSystem::getMessageTimeSeconds(TRUE); +} + +LLAssetRequest::~LLAssetRequest() +{ +} + + +///---------------------------------------------------------------------------- +/// LLInvItemRequest +///---------------------------------------------------------------------------- + +LLInvItemRequest::LLInvItemRequest(const LLUUID &uuid, const LLAssetType::EType type) +: mUUID(uuid), + mType(type), + mDownCallback( NULL ), + mUserData( NULL ), + mHost(), + mIsTemp( FALSE ), + mIsPriority(FALSE), + mDataSentInFirstPacket(FALSE), + mDataIsInVFS( FALSE ) +{ + // Need to guarantee that this time is up to date, we may be creating a circuit even though we haven't been + // running a message system loop. + mTime = LLMessageSystem::getMessageTimeSeconds(TRUE); +} + +LLInvItemRequest::~LLInvItemRequest() +{ +} + +///---------------------------------------------------------------------------- +/// LLEstateAssetRequest +///---------------------------------------------------------------------------- + +LLEstateAssetRequest::LLEstateAssetRequest(const LLUUID &uuid, const LLAssetType::EType atype, + EstateAssetType etype) +: mUUID(uuid), + mAType(atype), + mEstateAssetType(etype), + mDownCallback( NULL ), + mUserData( NULL ), + mHost(), + mIsTemp( FALSE ), + mIsPriority(FALSE), + mDataSentInFirstPacket(FALSE), + mDataIsInVFS( FALSE ) +{ + // Need to guarantee that this time is up to date, we may be creating a circuit even though we haven't been + // running a message system loop. + mTime = LLMessageSystem::getMessageTimeSeconds(TRUE); +} + +LLEstateAssetRequest::~LLEstateAssetRequest() +{ +} + + +///---------------------------------------------------------------------------- +/// LLAssetStorage +///---------------------------------------------------------------------------- + +// since many of these functions are called by the messaging and xfer systems, +// they are declared as static and are passed a "this" handle +// it's a C/C++ mish-mash! + +// TODO: permissions on modifications - maybe don't allow at all? +// TODO: verify that failures get propogated down +// TODO: rework tempfile handling? + + +LLAssetStorage::LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, LLVFS *vfs, const LLHost &upstream_host) +{ + _init(msg, xfer, vfs, upstream_host); +} + + +LLAssetStorage::LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, + LLVFS *vfs) +{ + _init(msg, xfer, vfs, LLHost::invalid); +} + + +void LLAssetStorage::_init(LLMessageSystem *msg, + LLXferManager *xfer, + LLVFS *vfs, + const LLHost &upstream_host) +{ + mShutDown = FALSE; + mMessageSys = msg; + mXferManager = xfer; + mVFS = vfs; + + setUpstream(upstream_host); + msg->setHandlerFuncFast(_PREHASH_AssetUploadComplete, processUploadComplete, (void **)this); +} + +LLAssetStorage::~LLAssetStorage() +{ + mShutDown = TRUE; + + _cleanupRequests(TRUE, LL_ERR_CIRCUIT_GONE); + + if (gMessageSystem) + { + // Warning! This won't work if there's more than one asset storage. + // unregister our callbacks with the message system + gMessageSystem->setHandlerFuncFast(_PREHASH_AssetUploadComplete, NULL, NULL); + } +} + +void LLAssetStorage::setUpstream(const LLHost &upstream_host) +{ + llinfos << "AssetStorage: Setting upstream provider to " << upstream_host << llendl; + + mUpstreamHost = upstream_host; +} + +void LLAssetStorage::checkForTimeouts() +{ + _cleanupRequests(FALSE, LL_ERR_TCP_TIMEOUT); +} + +void LLAssetStorage::_cleanupRequests(BOOL all, S32 error) +{ + const S32 NUM_QUEUES = 3; + F64 mt_secs = LLMessageSystem::getMessageTimeSeconds(); + + std::list* requests[NUM_QUEUES]; + requests[0] = &mPendingDownloads; + requests[1] = &mPendingUploads; + requests[2] = &mPendingLocalUploads; + static const char* REQUEST_TYPE[NUM_QUEUES] = { "download", "upload", "localuploads"}; + + std::list timed_out; + + for (S32 ii = 0; ii < NUM_QUEUES; ++ii) + { + for (std::list::iterator iter = requests[ii]->begin(); + iter != requests[ii]->end(); ) + { + std::list::iterator curiter = iter++; + LLAssetRequest* tmp = *curiter; + // if all is true, we want to clean up everything + // otherwise just check for timed out requests + // EXCEPT for upload timeouts + if (all + || ((0 == ii) + && LL_ASSET_STORAGE_TIMEOUT < (mt_secs - tmp->mTime))) + { + llwarns << "Asset " << REQUEST_TYPE[ii] << " request " + << (all ? "aborted" : "timed out") << " for " + << tmp->getUUID() << "." + << LLAssetType::lookup(tmp->getType()) << llendl; + + timed_out.push_front(tmp); + iter = requests[ii]->erase(curiter); + } + } + } + + LLAssetInfo info; + for (std::list::iterator iter = timed_out.begin(); + iter != timed_out.end(); ) + { + std::list::iterator curiter = iter++; + LLAssetRequest* tmp = *curiter; + if (tmp->mUpCallback) + { + tmp->mUpCallback(tmp->getUUID(), tmp->mUserData, error); + } + if (tmp->mDownCallback) + { + tmp->mDownCallback(mVFS, tmp->getUUID(), tmp->getType(), tmp->mUserData, error); + } + if (tmp->mInfoCallback) + { + tmp->mInfoCallback(&info, tmp->mUserData, error); + } + delete tmp; + } + +} + +BOOL LLAssetStorage::hasLocalAsset(const LLUUID &uuid, const LLAssetType::EType type) +{ + return mVFS->getExists(uuid, type); +} + +/////////////////////////////////////////////////////////////////////////// +// GET routines +/////////////////////////////////////////////////////////////////////////// + +// IW - uuid is passed by value to avoid side effects, please don't re-add & +void LLAssetStorage::getAssetData(const LLUUID uuid, LLAssetType::EType type, void (*callback)(LLVFS *vfs, const LLUUID&, LLAssetType::EType, void *,S32), void *user_data, BOOL is_priority) +{ + lldebugs << "LLAssetStorage::getAssetData() - " << uuid << "," << LLAssetType::lookup(type) << llendl; + + if (mShutDown) + { + return; // don't get the asset or do any callbacks, we are shutting down + } + + if (uuid.isNull()) + { + // Special case early out for NULL uuid + if (callback) + { + callback(mVFS, uuid, type, user_data, LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE); + } + return; + } + + BOOL exists = mVFS->getExists(uuid, type); + LLVFile file(mVFS, uuid, type); + U32 size = exists ? file.getSize() : 0; + + if (size < 1) + { + if (exists) + { + llwarns << "Asset vfile " << uuid << ":" << type << " found with bad size " << file.getSize() << ", removing" << llendl; + file.remove(); + } + + BOOL duplicate = FALSE; + + // check to see if there's a pending download of this uuid already + for (std::list::iterator iter = mPendingDownloads.begin(); + iter != mPendingDownloads.end(); ++iter ) + { + LLAssetRequest *tmp = *iter; + if ((type == tmp->getType()) && (uuid == tmp->getUUID())) + { + if (callback == tmp->mDownCallback && user_data == tmp->mUserData) + { + // this is a duplicate from the same subsystem - throw it away + llinfos << "Discarding duplicate request for UUID " << uuid << llendl; + return; + } + else + { + llinfos << "Adding additional non-duplicate request for UUID " << uuid << llendl; + } + + // this is a duplicate request + // queue the request, but don't actually ask for it again + duplicate = TRUE; + break; + } + } + + // This can be overridden by subclasses + _queueDataRequest(uuid, type, callback, user_data, duplicate, is_priority); + } + else + { + // we've already got the file + // theoretically, partial files w/o a pending request shouldn't happen + // unless there's a weird error + if (callback) + { + callback(mVFS, uuid, type, user_data, LL_ERR_NOERR); + } + } +} + +void LLAssetStorage::_queueDataRequest(const LLUUID& uuid, LLAssetType::EType atype, + LLGetAssetCallback callback, + void *user_data, BOOL duplicate, + BOOL is_priority) +{ + if (mUpstreamHost.isOk()) + { + // stash the callback info so we can find it after we get the response message + LLAssetRequest *req = new LLAssetRequest(uuid, atype); + req->mDownCallback = callback; + req->mUserData = user_data; + req->mIsPriority = is_priority; + + mPendingDownloads.push_back(req); + + if (!duplicate) + { + // send request message to our upstream data provider + // Create a new asset transfer. + LLTransferSourceParamsAsset spa; + spa.setAsset(uuid, atype); + + // Set our destination file, and the completion callback. + LLTransferTargetParamsVFile tpvf; + tpvf.setAsset(uuid, atype); + tpvf.setCallback(downloadCompleteCallback, req); + + llinfos << "Starting transfer for " << uuid << llendl; + LLTransferTargetChannel *ttcp = gTransferManager.getTargetChannel(mUpstreamHost, LLTCT_ASSET); + ttcp->requestTransfer(spa, tpvf, 100.f + (is_priority ? 1.f : 0.f)); + } + } + else + { + // uh-oh, we shouldn't have gotten here + llwarns << "Attempt to move asset data request upstream w/o valid upstream provider" << llendl; + if (callback) + { + callback(mVFS, uuid, atype, user_data, LL_ERR_CIRCUIT_GONE); + } + } +} + + +void LLAssetStorage::downloadCompleteCallback(S32 result, void *user_data) +{ + LLAssetRequest* req = (LLAssetRequest *)user_data; + if (!gAssetStorage) + { + llwarns << "LLAssetStorage::downloadCompleteCallback called without any asset system, aborting!" << llendl; + return; + } + + if (LL_ERR_NOERR == result) + { + // we might have gotten a zero-size file + LLVFile vfile(gAssetStorage->mVFS, req->getUUID(), req->getType()); + if (vfile.getSize() <= 0) + { + llwarns << "downloadCompleteCallback has non-existent or zero-size asset " << req->getUUID() << llendl; + + result = LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE; + vfile.remove(); + } + } + + // find and callback ALL pending requests for this UUID + // SJB: We process the callbacks in reverse order, I do not know if this is important, + // but I didn't want to mess with it. + std::list requests; + for (std::list::iterator iter = gAssetStorage->mPendingDownloads.begin(); + iter != gAssetStorage->mPendingDownloads.end(); ) + { + std::list::iterator curiter = iter++; + LLAssetRequest* tmp = *curiter; + if ((tmp->getUUID() == req->getUUID()) && (tmp->getType()== req->getType())) + { + requests.push_front(tmp); + iter = gAssetStorage->mPendingDownloads.erase(curiter); + } + } + for (std::list::iterator iter = requests.begin(); + iter != requests.end(); ) + { + std::list::iterator curiter = iter++; + LLAssetRequest* tmp = *curiter; + if (tmp->mDownCallback) + { + tmp->mDownCallback(gAssetStorage->mVFS, req->getUUID(), req->getType(), tmp->mUserData, result); + } + delete tmp; + } +} + +void LLAssetStorage::getEstateAsset(const LLHost &object_sim, const LLUUID &agent_id, const LLUUID &session_id, + const LLUUID &asset_id, LLAssetType::EType atype, EstateAssetType etype, + LLGetAssetCallback callback, void *user_data, BOOL is_priority) +{ + lldebugs << "LLAssetStorage::getEstateAsset() - " << asset_id << "," << LLAssetType::lookup(atype) << ", estatetype " << etype << llendl; + + // + // Probably will get rid of this early out? + // + if (asset_id.isNull()) + { + // Special case early out for NULL uuid + if (callback) + { + callback(mVFS, asset_id, atype, user_data, LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE); + } + return; + } + + BOOL exists = mVFS->getExists(asset_id, atype); + LLVFile file(mVFS, asset_id, atype); + U32 size = exists ? file.getSize() : 0; + + if (size < 1) + { + if (exists) + { + llwarns << "Asset vfile " << asset_id << ":" << atype << " found with bad size " << file.getSize() << ", removing" << llendl; + file.remove(); + } + + // See whether we should talk to the object's originating sim, or the upstream provider. + LLHost source_host; + if (object_sim.isOk()) + { + source_host = object_sim; + } + else + { + source_host = mUpstreamHost; + } + if (source_host.isOk()) + { + // stash the callback info so we can find it after we get the response message + LLEstateAssetRequest *req = new LLEstateAssetRequest(asset_id, atype, etype); + req->mDownCallback = callback; + req->mUserData = user_data; + req->mIsPriority = is_priority; + + // send request message to our upstream data provider + // Create a new asset transfer. + LLTransferSourceParamsEstate spe; + spe.setAgentSession(agent_id, session_id); + spe.setEstateAssetType(etype); + + // Set our destination file, and the completion callback. + LLTransferTargetParamsVFile tpvf; + tpvf.setAsset(asset_id, atype); + tpvf.setCallback(downloadEstateAssetCompleteCallback, req); + + llinfos << "Starting transfer for " << asset_id << llendl; + LLTransferTargetChannel *ttcp = gTransferManager.getTargetChannel(source_host, LLTCT_ASSET); + ttcp->requestTransfer(spe, tpvf, 100.f + (is_priority ? 1.f : 0.f)); + } + else + { + // uh-oh, we shouldn't have gotten here + llwarns << "Attempt to move asset data request upstream w/o valid upstream provider" << llendl; + if (callback) + { + callback(mVFS, asset_id, atype, user_data, LL_ERR_CIRCUIT_GONE); + } + } + } + else + { + // we've already got the file + // theoretically, partial files w/o a pending request shouldn't happen + // unless there's a weird error + if (callback) + { + callback(mVFS, asset_id, atype, user_data, LL_ERR_NOERR); + } + } +} + +void LLAssetStorage::downloadEstateAssetCompleteCallback(S32 result, void *user_data) +{ + LLEstateAssetRequest *req = (LLEstateAssetRequest *)user_data; + if (!gAssetStorage) + { + llwarns << "LLAssetStorage::downloadCompleteCallback called without any asset system, aborting!" << llendl; + return; + } + + if (LL_ERR_NOERR == result) + { + // we might have gotten a zero-size file + LLVFile vfile(gAssetStorage->mVFS, req->getUUID(), req->getAType()); + if (vfile.getSize() <= 0) + { + llwarns << "downloadCompleteCallback has non-existent or zero-size asset!" << llendl; + + result = LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE; + vfile.remove(); + } + } + + req->mDownCallback(gAssetStorage->mVFS, req->getUUID(), req->getAType(), req->mUserData, result); +} + +void LLAssetStorage::getInvItemAsset(const LLHost &object_sim, const LLUUID &agent_id, const LLUUID &session_id, + const LLUUID &owner_id, const LLUUID &task_id, const LLUUID &item_id, + const LLUUID &asset_id, LLAssetType::EType atype, + LLGetAssetCallback callback, void *user_data, BOOL is_priority) +{ + lldebugs << "LLAssetStorage::getInvItemAsset() - " << asset_id << "," << LLAssetType::lookup(atype) << llendl; + + // + // Probably will get rid of this early out? + // + if (asset_id.isNull()) + { + // Special case early out for NULL uuid + if (callback) + { + callback(mVFS, asset_id, atype, user_data, LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE); + } + return; + } + + BOOL exists = mVFS->getExists(asset_id, atype); + LLVFile file(mVFS, asset_id, atype); + U32 size = exists ? file.getSize() : 0; + + if (size < 1) + { + if (exists) + { + llwarns << "Asset vfile " << asset_id << ":" << atype << " found with bad size " << file.getSize() << ", removing" << llendl; + file.remove(); + } + + // See whether we should talk to the object's originating sim, or the upstream provider. + LLHost source_host; + if (object_sim.isOk()) + { + source_host = object_sim; + } + else + { + source_host = mUpstreamHost; + } + if (source_host.isOk()) + { + // stash the callback info so we can find it after we get the response message + LLInvItemRequest *req = new LLInvItemRequest(asset_id, atype); + req->mDownCallback = callback; + req->mUserData = user_data; + req->mIsPriority = is_priority; + + // send request message to our upstream data provider + // Create a new asset transfer. + LLTransferSourceParamsInvItem spi; + spi.setAgentSession(agent_id, session_id); + spi.setInvItem(owner_id, task_id, item_id); + spi.setAsset(asset_id, atype); + + // Set our destination file, and the completion callback. + LLTransferTargetParamsVFile tpvf; + tpvf.setAsset(asset_id, atype); + tpvf.setCallback(downloadInvItemCompleteCallback, req); + + llinfos << "Starting transfer for " << asset_id << llendl; + LLTransferTargetChannel *ttcp = gTransferManager.getTargetChannel(source_host, LLTCT_ASSET); + ttcp->requestTransfer(spi, tpvf, 100.f + (is_priority ? 1.f : 0.f)); + } + else + { + // uh-oh, we shouldn't have gotten here + llwarns << "Attempt to move asset data request upstream w/o valid upstream provider" << llendl; + if (callback) + { + callback(mVFS, asset_id, atype, user_data, LL_ERR_CIRCUIT_GONE); + } + } + } + else + { + // we've already got the file + // theoretically, partial files w/o a pending request shouldn't happen + // unless there's a weird error + if (callback) + { + callback(mVFS, asset_id, atype, user_data, LL_ERR_NOERR); + } + } +} + + +void LLAssetStorage::downloadInvItemCompleteCallback(S32 result, void *user_data) +{ + LLInvItemRequest *req = (LLInvItemRequest *)user_data; + if (!gAssetStorage) + { + llwarns << "LLAssetStorage::downloadCompleteCallback called without any asset system, aborting!" << llendl; + return; + } + + if (LL_ERR_NOERR == result) + { + // we might have gotten a zero-size file + LLVFile vfile(gAssetStorage->mVFS, req->getUUID(), req->getType()); + if (vfile.getSize() <= 0) + { + llwarns << "downloadCompleteCallback has non-existent or zero-size asset!" << llendl; + + result = LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE; + vfile.remove(); + } + } + + req->mDownCallback(gAssetStorage->mVFS, req->getUUID(), req->getType(), req->mUserData, result); +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Store routines +///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// virtual +void LLAssetStorage::cancelStoreAsset( + const LLUUID& uuid, + LLAssetType::EType atype) +{ + bool do_callback = true; + LLAssetRequest* req = NULL; + + if(mPendingUploads.size() > 0) + { + req = mPendingUploads.front(); + if((req->getUUID() == uuid) && (req->getType() == atype)) + { + // This is probably because the request is in progress - do + // not attempt to cancel. + do_callback = false; + } + } + + if (mPendingLocalUploads.size() > 0) + { + req = mPendingLocalUploads.front(); + if((req->getUUID() == uuid) && (req->getType() == atype)) + { + // This is probably because the request is in progress - do + // not attempt to cancel. + do_callback = false; + } + } + + if (do_callback) + { + // clear it out of the upload queue if it is there. + _callUploadCallbacks(uuid, atype, FALSE); + } +} + +// static +void LLAssetStorage::uploadCompleteCallback(const LLUUID& uuid, void *user_data, S32 result) // StoreAssetData callback (fixed) +{ + if (!gAssetStorage) + { + llwarns << "LLAssetStorage::uploadCompleteCallback has no gAssetStorage!" << llendl; + return; + } + LLAssetRequest *req = (LLAssetRequest *)user_data; + BOOL success = TRUE; + + if (result) + { + llwarns << "LLAssetStorage::uploadCompleteCallback " << result << ":" << getErrorString(result) << " trying to upload file to upstream provider" << llendl; + success = FALSE; + } + + // we're done grabbing the file, tell the client + gAssetStorage->mMessageSys->newMessageFast(_PREHASH_AssetUploadComplete); + gAssetStorage->mMessageSys->nextBlockFast(_PREHASH_AssetBlock); + gAssetStorage->mMessageSys->addUUIDFast(_PREHASH_UUID, uuid); + gAssetStorage->mMessageSys->addS8Fast(_PREHASH_Type, req->getType()); + gAssetStorage->mMessageSys->addBOOLFast(_PREHASH_Success, success); + gAssetStorage->mMessageSys->sendReliable(req->mHost); + + delete req; +} + +void LLAssetStorage::processUploadComplete(LLMessageSystem *msg, void **user_data) +{ + LLAssetStorage *this_ptr = (LLAssetStorage *)user_data; + LLUUID uuid; + S8 asset_type_s8; + LLAssetType::EType asset_type; + BOOL success = FALSE; + + msg->getUUIDFast(_PREHASH_AssetBlock, _PREHASH_UUID, uuid); + msg->getS8Fast(_PREHASH_AssetBlock, _PREHASH_Type, asset_type_s8); + msg->getBOOLFast(_PREHASH_AssetBlock, _PREHASH_Success, success); + + asset_type = (LLAssetType::EType)asset_type_s8; + this_ptr->_callUploadCallbacks(uuid, asset_type, success); +} + +void LLAssetStorage::_callUploadCallbacks(const LLUUID &uuid, LLAssetType::EType asset_type, BOOL success) +{ + // SJB: We process the callbacks in reverse order, I do not know if this is important, + // but I didn't want to mess with it. + std::list requests; + for (std::list::iterator iter = mPendingUploads.begin(); + iter != mPendingUploads.end(); ) + { + std::list::iterator curiter = iter++; + LLAssetRequest* req = *curiter; + if ((req->getUUID() == uuid) && (req->getType() == asset_type)) + { + requests.push_front(req); + iter = mPendingUploads.erase(curiter); + } + } + for (std::list::iterator iter = mPendingLocalUploads.begin(); + iter != mPendingLocalUploads.end(); ) + { + std::list::iterator curiter = iter++; + LLAssetRequest* req = *curiter; + if ((req->getUUID() == uuid) && (req->getType() == asset_type)) + { + requests.push_front(req); + iter = mPendingLocalUploads.erase(curiter); + } + } + for (std::list::iterator iter = requests.begin(); + iter != requests.end(); ) + { + std::list::iterator curiter = iter++; + LLAssetRequest* req = *curiter; + if (req->mUpCallback) + { + req->mUpCallback(uuid, req->mUserData, (success ? LL_ERR_NOERR : LL_ERR_ASSET_REQUEST_FAILED )); + } + delete req; + } +} + + +S32 LLAssetStorage::getNumPendingDownloads() const +{ + return mPendingDownloads.size(); +} + +S32 LLAssetStorage::getNumPendingUploads() const +{ + return mPendingUploads.size(); +} + +S32 LLAssetStorage::getNumPendingLocalUploads() +{ + return mPendingLocalUploads.size(); +} + +LLSD LLAssetStorage::getPendingTypes(const std::list& requests) const +{ + LLSD type_counts; + std::list::const_iterator it = requests.begin(); + std::list::const_iterator end = requests.end(); + for ( ; it != end; ++it) + { + LLAssetRequest* req = *it; + + const char* type_name = LLAssetType::lookupHumanReadable(req->getType()); + type_counts[type_name] = type_counts[type_name].asInteger() + 1; + } + return type_counts; +} + +LLSD LLAssetStorage::getPendingDownloadTypes() const +{ + return getPendingTypes(mPendingDownloads); +} + +LLSD LLAssetStorage::getPendingUploadTypes() const +{ + return getPendingTypes(mPendingUploads); +} + +// static +const char* LLAssetStorage::getErrorString(S32 status) +{ + switch( status ) + { + case LL_ERR_NOERR: + return "No error"; + + case LL_ERR_ASSET_REQUEST_FAILED: + return "Asset request: failed"; + + case LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE: + return "Asset request: non-existent file"; + + case LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE: + return "Asset request: asset not found in database"; + + case LL_ERR_EOF: + return "End of file"; + + case LL_ERR_CANNOT_OPEN_FILE: + return "Cannot open file"; + + case LL_ERR_FILE_NOT_FOUND: + return "File not found"; + + case LL_ERR_TCP_TIMEOUT: + return "File transfer timeout"; + + case LL_ERR_CIRCUIT_GONE: + return "Circuit gone"; + + default: + return "Unknown status"; + } +} + + + +void LLAssetStorage::getAssetData(const LLUUID uuid, LLAssetType::EType type, void (*callback)(const char*, const LLUUID&, void *, S32), void *user_data, BOOL is_priority) +{ + // check for duplicates here, since we're about to fool the normal duplicate checker + for (std::list::iterator iter = mPendingDownloads.begin(); + iter != mPendingDownloads.end(); ) + { + LLAssetRequest* tmp = *iter++; + if (type == tmp->getType() && + uuid == tmp->getUUID() && + legacyGetDataCallback == tmp->mDownCallback && + callback == ((LLLegacyAssetRequest *)tmp->mUserData)->mDownCallback && + user_data == ((LLLegacyAssetRequest *)tmp->mUserData)->mUserData) + { + // this is a duplicate from the same subsystem - throw it away + llinfos << "Discarding duplicate request for UUID " << uuid << llendl; + return; + } + } + + + LLLegacyAssetRequest *legacy = new LLLegacyAssetRequest; + + legacy->mDownCallback = callback; + legacy->mUserData = user_data; + + getAssetData(uuid, type, legacyGetDataCallback, (void **)legacy, + is_priority); +} + +// static +void LLAssetStorage::legacyGetDataCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type, void *user_data, S32 status) +{ + LLLegacyAssetRequest *legacy = (LLLegacyAssetRequest *)user_data; + char filename[LL_MAX_PATH] = ""; /* Flawfinder: ignore */ + + if (! status) + { + LLVFile file(vfs, uuid, type); + + char uuid_str[UUID_STR_LENGTH]; /* Flawfinder: ignore */ + + uuid.toString(uuid_str); + snprintf(filename,sizeof(filename),"%s.%s",gDirUtilp->getExpandedFilename(LL_PATH_CACHE,uuid_str).c_str(),LLAssetType::lookup(type)); /* Flawfinder: ignore */ + + FILE *fp = LLFile::fopen(filename, "wb"); /* Flawfinder: ignore */ + if (fp) + { + const S32 buf_size = 65536; + U8 copy_buf[buf_size]; + while (file.read(copy_buf, buf_size)) + { + if (fwrite(copy_buf, file.getLastBytesRead(), 1, fp) < 1) + { + // return a bad file error if we can't write the whole thing + status = LL_ERR_CANNOT_OPEN_FILE; + } + } + + fclose(fp); + } + else + { + status = LL_ERR_CANNOT_OPEN_FILE; + } + } + + legacy->mDownCallback(filename, uuid, legacy->mUserData, status); + delete legacy; +} + +// this is overridden on the viewer and the sim, so it doesn't really do anything +// virtual +void LLAssetStorage::storeAssetData( + const LLTransactionID& tid, + LLAssetType::EType asset_type, + LLStoreAssetCallback callback, + void* user_data, + bool temp_file, + bool is_priority, + bool store_local) +{ + llwarns << "storeAssetData: wrong version called" << llendl; +} + +// virtual +// this does nothing, viewer and sim both override this. +void LLAssetStorage::storeAssetData( + const LLUUID& asset_id, + LLAssetType::EType asset_type, + LLStoreAssetCallback callback, + void* user_data, + bool temp_file , + bool is_priority, + bool store_local, + const LLUUID& requesting_agent_id) +{ + llwarns << "storeAssetData: wrong version called" << llendl; +} + +// virtual +// this does nothing, viewer and sim both override this. +void LLAssetStorage::storeAssetData( + const char* filename, + const LLUUID& asset_id, + LLAssetType::EType asset_type, + LLStoreAssetCallback callback, + void* user_data, + bool temp_file, + bool is_priority) +{ + llwarns << "storeAssetData: wrong version called" << llendl; +} + +// virtual +// this does nothing, viewer and sim both override this. +void LLAssetStorage::storeAssetData( + const char* filename, + const LLTransactionID &transactoin_id, + LLAssetType::EType asset_type, + LLStoreAssetCallback callback, + void* user_data, + bool temp_file, + bool is_priority) +{ + llwarns << "storeAssetData: wrong version called" << llendl; +} + +// static +void LLAssetStorage::legacyStoreDataCallback(const LLUUID &uuid, void *user_data, S32 status) +{ + LLLegacyAssetRequest *legacy = (LLLegacyAssetRequest *)user_data; + if (legacy && legacy->mUpCallback) + { + legacy->mUpCallback(uuid, legacy->mUserData, status); + } + delete legacy; +} + +// virtual +void LLAssetStorage::addTempAssetData(const LLUUID& asset_id, const LLUUID& agent_id, const std::string& host_name) +{ } + +// virtual +BOOL LLAssetStorage::hasTempAssetData(const LLUUID& texture_id) const +{ return FALSE; } + +// virtual +std::string LLAssetStorage::getTempAssetHostName(const LLUUID& texture_id) const +{ return std::string(); } + +// virtual +LLUUID LLAssetStorage::getTempAssetAgentID(const LLUUID& texture_id) const +{ return LLUUID::null; } + +// virtual +void LLAssetStorage::removeTempAssetData(const LLUUID& asset_id) +{ } + +// virtual +void LLAssetStorage::removeTempAssetDataByAgentID(const LLUUID& agent_id) +{ } + +// virtual +void LLAssetStorage::dumpTempAssetData(const LLUUID& avatar_id) const +{ } + +// virtual +void LLAssetStorage::clearTempAssetData() +{ } diff --git a/indra/llmessage/llassetstorage.h b/indra/llmessage/llassetstorage.h new file mode 100644 index 0000000000..4ccbb08abd --- /dev/null +++ b/indra/llmessage/llassetstorage.h @@ -0,0 +1,328 @@ +/** + * @file llassetstorage.h + * @brief definition of LLAssetStorage class which allows simple + * up/downloads of uuid,type asets + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLASSETSTORAGE_H +#define LL_LLASSETSTORAGE_H + +#include + +#include "lluuid.h" +#include "lltimer.h" +#include "llnamevalue.h" +#include "llhost.h" +#include "stdenums.h" // for EDragAndDropType +#include "lltransfermanager.h" // For LLTSCode enum +#include "llassettype.h" +#include "llstring.h" + +// Forward declarations +class LLMessageSystem; +class LLXferManager; +class LLAssetStorage; +class LLVFS; +class LLSD; + +class LLAssetInfo +{ +protected: + std::string mDescription; + std::string mName; + +public: + LLUUID mUuid; + LLTransactionID mTransactionID; + LLUUID mCreatorID; + LLAssetType::EType mType; + + LLAssetInfo( void ); + LLAssetInfo( const LLUUID& object_id, const LLUUID& creator_id, + LLAssetType::EType type, const char* name, const char* desc ); + LLAssetInfo( const LLNameValue& nv ); + + const std::string& getName( void ) const { return mName; } + const std::string& getDescription( void ) const { return mDescription; } + void setName( const std::string& name ); + void setDescription( const std::string& desc ); + + // Assets (aka potential inventory items) can be applied to an + // object in the world. We'll store that as a string name value + // pair where the name encodes part of asset info, and the value + // the rest. LLAssetInfo objects will be responsible for parsing + // the meaning out froman LLNameValue object. See the inventory + // design docs for details. + void setFromNameValue( const LLNameValue& nv ); +}; + + +class LLAssetRequest +{ +public: + LLAssetRequest(const LLUUID &uuid, const LLAssetType::EType at); + virtual ~LLAssetRequest(); + + LLUUID getUUID() const { return mUUID; } + LLAssetType::EType getType() const { return mType; } +protected: + LLUUID mUUID; + LLAssetType::EType mType; + +public: + void (*mDownCallback)(LLVFS*, const LLUUID&, LLAssetType::EType, void *, S32); + void (*mUpCallback)(const LLUUID&, void *, S32); + void (*mInfoCallback)(LLAssetInfo *, void *, S32); + + void *mUserData; + LLHost mHost; + BOOL mIsTemp; + BOOL mIsLocal; + F64 mTime; // Message system time + BOOL mIsPriority; + BOOL mDataSentInFirstPacket; + BOOL mDataIsInVFS; + LLUUID mRequestingAgentID; // Only valid for uploads from an agent +}; + + +class LLInvItemRequest +{ +public: + LLInvItemRequest(const LLUUID &uuid, const LLAssetType::EType at); + virtual ~LLInvItemRequest(); + + LLUUID getUUID() const { return mUUID; } + LLAssetType::EType getType() const { return mType; } +protected: + LLUUID mUUID; + LLAssetType::EType mType; + +public: + void (*mDownCallback)(LLVFS*, const LLUUID&, LLAssetType::EType, void *, S32); + + void *mUserData; + LLHost mHost; + BOOL mIsTemp; + F64 mTime; // Message system time + BOOL mIsPriority; + BOOL mDataSentInFirstPacket; + BOOL mDataIsInVFS; + +}; + +class LLEstateAssetRequest +{ +public: + LLEstateAssetRequest(const LLUUID &uuid, const LLAssetType::EType at, EstateAssetType et); + virtual ~LLEstateAssetRequest(); + + LLUUID getUUID() const { return mUUID; } + LLAssetType::EType getAType() const { return mAType; } +protected: + LLUUID mUUID; + LLAssetType::EType mAType; + EstateAssetType mEstateAssetType; + +public: + void (*mDownCallback)(LLVFS*, const LLUUID&, LLAssetType::EType, void *, S32); + + void *mUserData; + LLHost mHost; + BOOL mIsTemp; + F64 mTime; // Message system time + BOOL mIsPriority; + BOOL mDataSentInFirstPacket; + BOOL mDataIsInVFS; + +}; + + + + +typedef void (*LLGetAssetCallback)(LLVFS *vfs, const LLUUID &asset_id, + LLAssetType::EType asset_type, void *user_data, S32 status); + +class LLAssetStorage +{ +public: + // VFS member is public because static child methods need it :( + LLVFS *mVFS; + typedef void (*LLStoreAssetCallback)(const LLUUID &asset_id, void *user_data, S32 status); + +protected: + BOOL mShutDown; + LLHost mUpstreamHost; + + LLMessageSystem *mMessageSys; + LLXferManager *mXferManager; + + std::list mPendingDownloads; + std::list mPendingUploads; + std::list mPendingLocalUploads; + +public: + LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, + LLVFS *vfs, const LLHost &upstream_host); + + LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, + LLVFS *vfs); + virtual ~LLAssetStorage(); + + void setUpstream(const LLHost &upstream_host); + + virtual BOOL hasLocalAsset(const LLUUID &uuid, LLAssetType::EType type); + + // public interface methods + // note that your callback may get called BEFORE the function returns + + virtual void getAssetData(const LLUUID uuid, LLAssetType::EType atype, LLGetAssetCallback cb, void *user_data, BOOL is_priority = FALSE); + + /* + * TransactionID version + * Viewer needs the store_local + */ + virtual void storeAssetData( + const LLTransactionID& tid, + LLAssetType::EType atype, + LLStoreAssetCallback callback, + void* user_data, + bool temp_file = false, + bool is_priority = false, + bool store_local = false); + + /* + * AssetID version + * Sim needs both store_local and requesting_agent_id. + */ + virtual void storeAssetData( + const LLUUID& asset_id, + LLAssetType::EType asset_type, + LLStoreAssetCallback callback, + void* user_data, + bool temp_file = false, + bool is_priority = false, + bool store_local = false, + const LLUUID& requesting_agent_id = LLUUID::null); + + // This call will attempt to clear a store asset. This will only + // attempt to cancel an upload that has not yet begun. The + // callback will be called with an error code. + virtual void cancelStoreAsset( + const LLUUID& uuid, + LLAssetType::EType oatype); + + virtual void checkForTimeouts(); + + void getEstateAsset(const LLHost &object_sim, const LLUUID &agent_id, const LLUUID &session_id, + const LLUUID &asset_id, LLAssetType::EType atype, EstateAssetType etype, + LLGetAssetCallback callback, void *user_data, BOOL is_priority); + + void getInvItemAsset(const LLHost &object_sim, + const LLUUID &agent_id, const LLUUID &session_id, + const LLUUID &owner_id, const LLUUID &task_id, const LLUUID &item_id, + const LLUUID &asset_id, LLAssetType::EType atype, + LLGetAssetCallback cb, void *user_data, BOOL is_priority = FALSE); // Get a particular inventory item. + + + S32 getNumPendingDownloads() const; + S32 getNumPendingUploads() const; + S32 getNumPendingLocalUploads(); + + // Returns a map from type to num pending, eg 'texture' => 5, 'object' => 10 + LLSD getPendingDownloadTypes() const; + LLSD getPendingUploadTypes() const; + + // download process callbacks + static void downloadCompleteCallback(const S32 result, void *user_data); + static void downloadEstateAssetCompleteCallback(const S32 result, void *user_data); + static void downloadInvItemCompleteCallback(const S32 result, void *user_data); + + // upload process callbacks + static void uploadCompleteCallback(const LLUUID&, void *user_data, S32 result); + static void processUploadComplete(LLMessageSystem *msg, void **this_handle); + + // debugging + static const char* getErrorString( S32 status ); + + // deprecated file-based methods + void getAssetData(const LLUUID uuid, LLAssetType::EType type, void (*callback)(const char*, const LLUUID&, void *, S32), void *user_data, BOOL is_priority = FALSE); + + /* + * AssetID version. + */ + virtual void storeAssetData( + const char* filename, + const LLUUID& asset_id, + LLAssetType::EType type, + LLStoreAssetCallback callback, + void* user_data, + bool temp_file = false, + bool is_priority = false); + + /* + * TransactionID version + */ + virtual void storeAssetData( + const char * filename, + const LLTransactionID &transaction_id, + LLAssetType::EType type, + LLStoreAssetCallback callback, + void *user_data, + bool temp_file = false, + bool is_priority = false); + + static void legacyGetDataCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType, void *user_data, S32 status); + static void legacyStoreDataCallback(const LLUUID &uuid, void *user_data, S32 status); + + // Temp assets are stored on sim nodes, they have agent ID and location data associated with them. + // This is a no-op for non-http asset systems + virtual void addTempAssetData(const LLUUID& asset_id, const LLUUID& agent_id, const std::string& host_name); + virtual BOOL hasTempAssetData(const LLUUID& texture_id) const; + virtual std::string getTempAssetHostName(const LLUUID& texture_id) const; + virtual LLUUID getTempAssetAgentID(const LLUUID& texture_id) const; + virtual void removeTempAssetData(const LLUUID& asset_id); + virtual void removeTempAssetDataByAgentID(const LLUUID& agent_id); + // Pass LLUUID::null for all + virtual void dumpTempAssetData(const LLUUID& avatar_id) const; + virtual void clearTempAssetData(); + + // add extra methods to handle metadata + +protected: + void _cleanupRequests(BOOL all, S32 error); + void _callUploadCallbacks(const LLUUID &uuid, const LLAssetType::EType asset_type, BOOL success); + + virtual void _queueDataRequest(const LLUUID& uuid, LLAssetType::EType type, + void (*callback)(LLVFS *vfs, const LLUUID&, LLAssetType::EType, void *, S32), + void *user_data, BOOL duplicate, + BOOL is_priority); + +private: + void _init(LLMessageSystem *msg, + LLXferManager *xfer, + LLVFS *vfs, + const LLHost &upstream_host); + LLSD getPendingTypes(const std::list& requests) const; + +}; + +//////////////////////////////////////////////////////////////////////// +// Wrappers to replicate deprecated API +//////////////////////////////////////////////////////////////////////// + +class LLLegacyAssetRequest +{ +public: + void (*mDownCallback)(const char *, const LLUUID&, void *, S32); + LLAssetStorage::LLStoreAssetCallback mUpCallback; + + void *mUserData; +}; + +extern LLAssetStorage *gAssetStorage; +extern const LLUUID CATEGORIZE_LOST_AND_FOUND_ID; +#endif diff --git a/indra/llmessage/llbuffer.cpp b/indra/llmessage/llbuffer.cpp new file mode 100644 index 0000000000..009387598b --- /dev/null +++ b/indra/llmessage/llbuffer.cpp @@ -0,0 +1,746 @@ +/** + * @file llbuffer.cpp + * @author Phoenix + * @date 2005-09-20 + * @brief Implementation of the segments, buffers, and buffer arrays. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llbuffer.h" + +#include "llmath.h" +#include "llmemtype.h" +#include "llstl.h" + +/** + * LLSegment + */ +LLSegment::LLSegment() : + mChannel(0), + mData(NULL), + mSize(0) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); +} + +LLSegment::LLSegment(S32 channel, U8* data, S32 data_len) : + mChannel(channel), + mData(data), + mSize(data_len) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); +} + +LLSegment::~LLSegment() +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); +} + +bool LLSegment::isOnChannel(S32 channel) const +{ + return (mChannel == channel); +} + +S32 LLSegment::getChannel() const +{ + return mChannel; +} + +void LLSegment::setChannel(S32 channel) +{ + mChannel = channel; +} + + +U8* LLSegment::data() const +{ + return mData; +} + +S32 LLSegment::size() const +{ + return mSize; +} + + +/** + * LLHeapBuffer + */ +LLHeapBuffer::LLHeapBuffer() +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + const S32 DEFAULT_HEAP_BUFFER_SIZE = 16384; + allocate(DEFAULT_HEAP_BUFFER_SIZE); +} + +LLHeapBuffer::LLHeapBuffer(S32 size) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + allocate(size); +} + +LLHeapBuffer::LLHeapBuffer(const U8* src, S32 len) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + if((len > 0) && src) + { + allocate(len); + if(mBuffer) + { + memcpy(mBuffer, src, len); + } + } + else + { + mBuffer = NULL; + mSize = 0; + mNextFree = NULL; + } +} + +// virtual +LLHeapBuffer::~LLHeapBuffer() +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + delete[] mBuffer; + mBuffer = NULL; + mSize = 0; + mNextFree = NULL; +} + +// virtual +//S32 LLHeapBuffer::bytesLeft() const +//{ +// return (mSize - (mNextFree - mBuffer)); +//} + +// virtual +bool LLHeapBuffer::createSegment( + S32 channel, + S32 size, + LLSegment& segment) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + // get actual size of the segment. + S32 actual_size = llmin(size, (mSize - S32(mNextFree - mBuffer))); + + // bail if we cannot build a valid segment + if(actual_size <= 0) + { + return false; + } + + // Yay, we're done. + segment = LLSegment(channel, mNextFree, actual_size); + mNextFree += actual_size; + return true; +} + +void LLHeapBuffer::allocate(S32 size) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + mBuffer = new U8[size]; + if(mBuffer) + { + mSize = size; + mNextFree = mBuffer; + } +} + + +/** + * LLBufferArray + */ +LLBufferArray::LLBufferArray() : + mNextBaseChannel(0) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); +} + +LLBufferArray::~LLBufferArray() +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + std::for_each(mBuffers.begin(), mBuffers.end(), DeletePointer()); +} + +// static +LLChannelDescriptors LLBufferArray::makeChannelConsumer( + const LLChannelDescriptors& channels) +{ + LLChannelDescriptors rv(channels.out()); + return rv; +} + +LLChannelDescriptors LLBufferArray::nextChannel() +{ + LLChannelDescriptors rv(mNextBaseChannel++); + return rv; +} + +bool LLBufferArray::append(S32 channel, const U8* src, S32 len) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + std::vector segments; + if(copyIntoBuffers(channel, src, len, segments)) + { + mSegments.insert(mSegments.end(), segments.begin(), segments.end()); + return true; + } + return false; +} + +bool LLBufferArray::prepend(S32 channel, const U8* src, S32 len) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + std::vector segments; + if(copyIntoBuffers(channel, src, len, segments)) + { + mSegments.insert(mSegments.begin(), segments.begin(), segments.end()); + return true; + } + return false; +} + +bool LLBufferArray::insertAfter( + segment_iterator_t segment, + S32 channel, + const U8* src, + S32 len) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + std::vector segments; + if(mSegments.end() != segment) + { + ++segment; + } + if(copyIntoBuffers(channel, src, len, segments)) + { + mSegments.insert(segment, segments.begin(), segments.end()); + return true; + } + return false; +} + +LLBufferArray::segment_iterator_t LLBufferArray::splitAfter(U8* address) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + segment_iterator_t end = mSegments.end(); + segment_iterator_t it = getSegment(address); + if(it == end) + { + return end; + } + + // We have the location and the segment. + U8* base = (*it).data(); + S32 size = (*it).size(); + if(address == (base + size)) + { + // No need to split, since this is the last byte of the + // segment. We do not want to have zero length segments, since + // that will only incur processing overhead with no advantage. + return it; + } + S32 channel = (*it).getChannel(); + LLSegment segment1(channel, base, (address - base) + 1); + *it = segment1; + segment_iterator_t rv = it; + ++it; + LLSegment segment2(channel, address + 1, size - (address - base) - 1); + mSegments.insert(it, segment2); + return rv; +} + +LLBufferArray::segment_iterator_t LLBufferArray::beginSegment() +{ + return mSegments.begin(); +} + +LLBufferArray::segment_iterator_t LLBufferArray::endSegment() +{ + return mSegments.end(); +} + +LLBufferArray::segment_iterator_t LLBufferArray::constructSegmentAfter( + U8* address, + LLSegment& segment) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + segment_iterator_t rv = mSegments.begin(); + segment_iterator_t end = mSegments.end(); + if(!address) + { + if(rv != end) + { + segment = (*rv); + } + } + else + { + // we have an address - find the segment it is in. + for( ; rv != end; ++rv) + { + if((address >= (*rv).data()) + && (address < ((*rv).data() + (*rv).size()))) + { + if((++address) < ((*rv).data() + (*rv).size())) + { + // it's in this segment - construct an appropriate + // sub-segment. + segment = LLSegment( + (*rv).getChannel(), + address, + (*rv).size() - (address - (*rv).data())); + } + else + { + ++rv; + if(rv != end) + { + segment = (*rv); + } + } + break; + } + } + } + if(rv == end) + { + segment = LLSegment(); + } + return rv; +} + +LLBufferArray::segment_iterator_t LLBufferArray::getSegment(U8* address) +{ + segment_iterator_t end = mSegments.end(); + if(!address) + { + return end; + } + segment_iterator_t it = mSegments.begin(); + for( ; it != end; ++it) + { + if((address >= (*it).data())&&(address < (*it).data() + (*it).size())) + { + // found it. + return it; + } + } + return end; +} + +LLBufferArray::const_segment_iterator_t LLBufferArray::getSegment( + U8* address) const +{ + const_segment_iterator_t end = mSegments.end(); + if(!address) + { + return end; + } + const_segment_iterator_t it = mSegments.begin(); + for( ; it != end; ++it) + { + if((address >= (*it).data()) + && (address < (*it).data() + (*it).size())) + { + // found it. + return it; + } + } + return end; +} + +/* +U8* LLBufferArray::getAddressAfter(U8* address) +{ + U8* rv = NULL; + segment_iterator_t it = getSegment(address); + segment_iterator_t end = mSegments.end(); + if(it != end) + { + if(++address < ((*it).data() + (*it).size())) + { + // it's in the same segment + rv = address; + } + else + { + // it's in the next segment + if(++it != end) + { + rv = (*it).data(); + } + } + } + return rv; +} +*/ + +S32 LLBufferArray::countAfter(S32 channel, U8* start) const +{ + S32 count = 0; + S32 offset = 0; + const_segment_iterator_t it; + const_segment_iterator_t end = mSegments.end(); + if(start) + { + it = getSegment(start); + if(it == end) + { + return count; + } + if(++start < ((*it).data() + (*it).size())) + { + // it's in the same segment + offset = start - (*it).data(); + } + else if(++it == end) + { + // it's in the next segment + return count; + } + } + else + { + it = mSegments.begin(); + } + while(it != end) + { + if((*it).isOnChannel(channel)) + { + count += (*it).size() - offset; + } + offset = 0; + ++it; + } + return count; +} + +U8* LLBufferArray::readAfter( + S32 channel, + U8* start, + U8* dest, + S32& len) const +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + U8* rv = start; + if(!dest || len <= 0) + { + return rv; + } + S32 bytes_left = len; + len = 0; + S32 bytes_to_copy = 0; + const_segment_iterator_t it; + const_segment_iterator_t end = mSegments.end(); + if(start) + { + it = getSegment(start); + if(it == end) + { + return rv; + } + if((++start < ((*it).data() + (*it).size())) + && (*it).isOnChannel(channel)) + { + // copy the data out of this segment + S32 bytes_in_segment = (*it).size() - (start - (*it).data()); + bytes_to_copy = llmin(bytes_left, bytes_in_segment); + memcpy(dest, start, bytes_to_copy); /*Flawfinder: ignore*/ + len += bytes_to_copy; + bytes_left -= bytes_to_copy; + rv = start + bytes_to_copy - 1; + ++it; + } + else + { + ++it; + } + } + else + { + it = mSegments.begin(); + } + while(bytes_left && (it != end)) + { + if(!((*it).isOnChannel(channel))) + { + ++it; + continue; + } + bytes_to_copy = llmin(bytes_left, (*it).size()); + memcpy(dest + len, (*it).data(), bytes_to_copy); /*Flawfinder: ignore*/ + len += bytes_to_copy; + bytes_left -= bytes_to_copy; + rv = (*it).data() + bytes_to_copy - 1; + ++it; + } + return rv; +} + +U8* LLBufferArray::seek( + S32 channel, + U8* start, + S32 delta) const +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + const_segment_iterator_t it; + const_segment_iterator_t end = mSegments.end(); + U8* rv = start; + if(0 == delta) + { + if((U8*)npos == start) + { + // someone is looking for end of data. + segment_list_t::const_reverse_iterator rit = mSegments.rbegin(); + segment_list_t::const_reverse_iterator rend = mSegments.rend(); + while(rit != rend) + { + if(!((*rit).isOnChannel(channel))) + { + ++rit; + continue; + } + rv = (*rit).data() + (*rit).size(); + break; + } + } + else if(start) + { + // This is sort of a weird case - check if zero bytes away + // from current position is on channel and return start if + // that is true. Otherwise, return NULL. + it = getSegment(start); + if((it == end) || !(*it).isOnChannel(channel)) + { + rv = NULL; + } + } + else + { + // Start is NULL, so return the very first byte on the + // channel, or NULL. + it = mSegments.begin(); + while((it != end) && !(*it).isOnChannel(channel)) + { + ++it; + } + if(it != end) + { + rv = (*it).data(); + } + } + return rv; + } + if(start) + { + it = getSegment(start); + if((it != end) && (*it).isOnChannel(channel)) + { + if(delta > 0) + { + S32 bytes_in_segment = (*it).size() - (start - (*it).data()); + S32 local_delta = llmin(delta, bytes_in_segment); + rv += local_delta; + delta -= local_delta; + ++it; + } + else + { + S32 bytes_in_segment = start - (*it).data(); + S32 local_delta = llmin(llabs(delta), bytes_in_segment); + rv -= local_delta; + delta += local_delta; + } + } + } + else if(delta < 0) + { + // start is NULL, and delta indicates seeking backwards - + // return NULL. + return NULL; + } + else + { + // start is NULL and delta > 0 + it = mSegments.begin(); + } + if(delta > 0) + { + // At this point, we have an iterator into the segments, and + // are seeking forward until delta is zero or we run out + while(delta && (it != end)) + { + if(!((*it).isOnChannel(channel))) + { + ++it; + continue; + } + if(delta <= (*it).size()) + { + // it's in this segment + rv = (*it).data() + delta; + } + delta -= (*it).size(); + ++it; + } + if(delta && (it == end)) + { + // Whoops - sought past end. + rv = NULL; + } + } + else //if(delta < 0) + { + // We are at the beginning of a segment, and need to search + // backwards. + segment_list_t::const_reverse_iterator rit(it); + segment_list_t::const_reverse_iterator rend = mSegments.rend(); + while(delta && (rit != rend)) + { + if(!((*rit).isOnChannel(channel))) + { + ++rit; + continue; + } + if(llabs(delta) <= (*rit).size()) + { + // it's in this segment. + rv = (*rit).data() + (*rit).size() + delta; + delta = 0; + } + else + { + delta += (*rit).size(); + } + ++rit; + } + if(delta && (rit == rend)) + { + // sought past the beginning. + rv = NULL; + } + } + return rv; +} + +bool LLBufferArray::takeContents(LLBufferArray& source) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + std::copy( + source.mBuffers.begin(), + source.mBuffers.end(), + std::back_insert_iterator(mBuffers)); + source.mBuffers.clear(); + std::copy( + source.mSegments.begin(), + source.mSegments.end(), + std::back_insert_iterator(mSegments)); + source.mSegments.clear(); + source.mNextBaseChannel = 0; + return true; +} + +LLBufferArray::segment_iterator_t LLBufferArray::makeSegment( + S32 channel, + S32 len) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + // start at the end of the buffers, because it is the most likely + // to have free space. + LLSegment segment; + buffer_list_t::reverse_iterator it = mBuffers.rbegin(); + buffer_list_t::reverse_iterator end = mBuffers.rend(); + bool made_segment = false; + for(; it != end; ++it) + { + if((*it)->createSegment(channel, len, segment)) + { + made_segment = true; + break; + } + } + segment_iterator_t send = mSegments.end(); + if(!made_segment) + { + LLBuffer* buf = new LLHeapBuffer; + mBuffers.push_back(buf); + if(!buf->createSegment(channel, len, segment)) + { + // failed. this should never happen. + return send; + } + } + + // store and return the newly made segment + mSegments.insert(send, segment); + std::list::reverse_iterator rv = mSegments.rbegin(); + ++rv; + send = rv.base(); + return send; +} + +bool LLBufferArray::eraseSegment(const segment_iterator_t& iter) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + // *FIX: in theory, we could reclaim the memory. We are leaking a + // bit of buffered memory into an unusable but still referenced + // location. + (void)mSegments.erase(iter); + return true; +} + + +bool LLBufferArray::copyIntoBuffers( + S32 channel, + const U8* src, + S32 len, + std::vector& segments) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + if(!src || !len) return false; + S32 copied = 0; + LLSegment segment; + buffer_iterator_t it = mBuffers.begin(); + buffer_iterator_t end = mBuffers.end(); + for(; it != end;) + { + if(!(*it)->createSegment(channel, len, segment)) + { + ++it; + continue; + } + segments.push_back(segment); + S32 bytes = llmin(segment.size(), len); + memcpy(segment.data(), src + copied, bytes); /* Flawfinder Ignore */ + copied += bytes; + len -= bytes; + if(0 == len) + { + break; + } + } + while(len) + { + LLBuffer* buf = new LLHeapBuffer; + mBuffers.push_back(buf); + if(!buf->createSegment(channel, len, segment)) + { + // this totally failed - bail. This is the weird corner + // case were we 'leak' memory. No worries about an actual + // leak - we will still reclaim the memory later, but this + // particular buffer array is hosed for some reason. + // This should never happen. + return false; + } + segments.push_back(segment); + memcpy(segment.data(), src + copied, segment.size()); + copied += segment.size(); + len -= segment.size(); + } + return true; +} diff --git a/indra/llmessage/llbuffer.h b/indra/llmessage/llbuffer.h new file mode 100644 index 0000000000..3d7f209123 --- /dev/null +++ b/indra/llmessage/llbuffer.h @@ -0,0 +1,493 @@ +/** + * @file llbuffer.h + * @author Phoenix + * @date 2005-09-20 + * @brief Declaration of buffer and buffer arrays primarily used in I/O. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLBUFFER_H +#define LL_LLBUFFER_H + +/** + * Declaration of classes used for minimizing calls to new[], + * memcpy(), and delete[]. Typically, you would create an LLHeapArray, + * feed it data, modify and add segments as you process it, and feed + * it to a sink. + */ + +#include + +/** + * @class LLChannelDescriptors + * @brief A way simple interface to accesss channels inside a buffer + */ +class LLChannelDescriptors +{ +public: + // enumeration for segmenting the channel information + enum { E_CHANNEL_COUNT = 3 }; + LLChannelDescriptors() : mBaseChannel(0) {} + explicit LLChannelDescriptors(S32 base) : mBaseChannel(base) {} + S32 in() const { return mBaseChannel; } + S32 out() const { return mBaseChannel + 1; } + //S32 err() const { return mBaseChannel + 2; } +protected: + S32 mBaseChannel; +}; + + +/** + * @class LLSegment + * @brief A segment is a single, contiguous chunk of memory in a buffer + * + * Each segment represents a contiguous addressable piece of memory + * which is located inside a buffer. The segment is not responsible + * for allocation or deallcoation of the data. Each segment is a light + * weight object, and simple enough to copy around, use, and generate + * as necessary. + * This is the preferred interface for working with memory blocks, + * since it is the only way to safely, inexpensively, and directly + * access linear blocks of memory. + */ +class LLSegment +{ +public: + LLSegment(); + LLSegment(S32 channel, U8* data, S32 data_len); + ~LLSegment(); + + /** + * @brief Check if this segment is on the given channel. + * + */ + bool isOnChannel(S32 channel) const; + + /** + * @brief Get the channel + */ + S32 getChannel() const; + + /** + * @brief Set the channel + */ + void setChannel(S32 channel); + + /** + * @brief Return a raw pointer to the current data set. + * + * The pointer returned can be used for reading or even adjustment + * if you are a bit crazy up to size() bytes into memory. + * @return A potentially NULL pointer to the raw buffer data + */ + U8* data() const; + + /** + * @brief Return the size of the segment + */ + S32 size() const; + +protected: + S32 mChannel; + U8* mData; + S32 mSize; +}; + +/** + * @class LLBuffer + * @brief Abstract base class for buffers + * + * This class declares the interface necessary for buffer arrays. A + * buffer is not necessarily a single contiguous memory chunk, so + * please do not circumvent the segment API. + */ +class LLBuffer +{ +public: + /** + * @brief The buffer base class should have no responsibilities + * other than an interface. + */ + virtual ~LLBuffer() {} + + /** + * @brief Generate a segment for this buffer. + * + * The segment returned is always contiguous memory. This call can + * fail if no contiguous memory is available, eg, offset is past + * the end. The segment returned may be smaller than the requested + * size. The segment will never be larger than the requested size. + * @param channel The channel for the segment. + * @param offset The offset from zero in the buffer. + * @param size The requested size of the segment. + * @param segment[out] The out-value from the operation + * @return Returns true if a segment was found. + */ + virtual bool createSegment(S32 channel, S32 size, LLSegment& segment) = 0; +}; + +/** + * @class LLHeapBuffer + * @brief A large contiguous buffer allocated on the heap with new[]. + * + * This class is a simple buffer implementation which allocates chunks + * off the heap. Once a buffer is constructed, it's buffer has a fixed + * length. + */ +class LLHeapBuffer : public LLBuffer +{ +public: + /** + * @brief Construct a heap buffer with a reasonable default size. + */ + LLHeapBuffer(); + + /** + * @brief Construct a heap buffer with a specified size. + * + * @param size The minimum size of the buffer. + */ + explicit LLHeapBuffer(S32 size); + + /** + * @brief Construct a heap buffer of minimum size len, and copy from src. + * + * @param src The source of the data to be copied. + * @param len The minimum size of the buffer. + */ + LLHeapBuffer(const U8* src, S32 len); + + /** + * @brief Simple destruction. + */ + virtual ~LLHeapBuffer(); + + /** + * @brief Get the number of bytes left in the buffer. + * + * @return Returns the number of bytes left. + */ + //virtual S32 bytesLeft() const; + + /** + * @brief Generate a segment for this buffer. + * + * The segment returned is always contiguous memory. This call can + * fail if no contiguous memory is available, eg, offset is past + * the end. The segment returned may be smaller than the requested + * size. It is up to the caller to delete the segment returned. + * @param channel The channel for the segment. + * @param offset The offset from zero in the buffer + * @param size The requested size of the segment + * @param segment[out] The out-value from the operation + * @return Returns true if a segment was found. + */ + virtual bool createSegment(S32 channel, S32 size, LLSegment& segment); + +protected: + U8* mBuffer; + S32 mSize; + U8* mNextFree; + +private: + /** + * @brief Helper method to allocate a buffer and correctly set + * intertnal state of this buffer. + */ + void allocate(S32 size); +}; + +/** + * @class LLBufferArray + * @brief Class to represent scattered memory buffers and in-order segments + * of that buffered data. + * + * NOTE: This class needs to have an iovec interface + */ +class LLBufferArray +{ +public: + typedef std::vector buffer_list_t; + typedef buffer_list_t::iterator buffer_iterator_t; + typedef std::list segment_list_t; + typedef segment_list_t::const_iterator const_segment_iterator_t; + typedef segment_list_t::iterator segment_iterator_t; + enum { npos = 0xffffffff }; + + LLBufferArray(); + ~LLBufferArray(); + + /* @name Channel methods + */ + //@{ + /** + * @brief Generate the a channel descriptor which consumes the + * output for the channel passed in. + */ + static LLChannelDescriptors makeChannelConsumer( + const LLChannelDescriptors& channels); + + /** + * @brief Generate the next channel descriptor for this buffer array. + * + * The channel descriptor interface is how the buffer array + * clients can know where to read and write data. Use this + * interface to get the 'next' channel set for usage. This is a + * bit of a simple hack until it's utility indicates it should be + * extended. + * @return Returns a valid channel descriptor set for input and output. + */ + LLChannelDescriptors nextChannel(); + //@} + + /* @name Data methods + */ + //@{ + + // These methods will be useful once there is any kind of buffer + // besides a heap buffer. + //bool append(EBufferChannel channel, LLBuffer* data); + //bool prepend(EBufferChannel channel, LLBuffer* data); + //bool insertAfter( + // segment_iterator_t segment, + // EBufferChannel channel, + // LLBuffer* data); + + /** + * @brief Put data on a channel at the end of this buffer array. + * + * The data is copied from src into the buffer array. At least one + * new segment is created and put on the end of the array. This + * object will internally allocate new buffers if necessary. + * @param channel The channel for this data + * @param src The start of memory for the data to be copied + * @param len The number of bytes of data to copy + * @return Returns true if the method worked. + */ + bool append(S32 channel, const U8* src, S32 len); + + /** + * @brief Put data on a channel at the front of this buffer array. + * + * The data is copied from src into the buffer array. At least one + * new segment is created and put in the front of the array. This + * object will internally allocate new buffers if necessary. + * @param channel The channel for this data + + * @param src The start of memory for the data to be copied + * @param len The number of bytes of data to copy + * @return Returns true if the method worked. + */ + bool prepend(S32 channel, const U8* src, S32 len); + + /** + * @brief Insert data into a buffer array after a particular segment. + * + * The data is copied from src into the buffer array. At least one + * new segment is created and put in the array. This object will + * internally allocate new buffers if necessary. + * @param segment The segment in front of the new segments location + * @param channel The channel for this data + * @param src The start of memory for the data to be copied + * @param len The number of bytes of data to copy + * @return Returns true if the method worked. + */ + bool insertAfter( + segment_iterator_t segment, + S32 channel, + const U8* src, + S32 len); + + /** + * @brief Count bytes in the buffer array on the specified channel + * + * @param channel The channel to count. + * @param start The start address in the array for counting. You + * can specify NULL to start at the beginning. + * @return Returns the number of bytes in the channel after start + */ + S32 countAfter(S32 channel, U8* start) const; + + /** + * @brief Read bytes in the buffer array on the specified channel + * + * You should prefer iterating over segments is possible since + * this method requires you to allocate large buffers - precisely + * what this class is trying to prevent. This method will skip + * any segments which are not on the given channel, so this method + * would usually be used to read a channel and copy that to a log + * or a socket buffer or something. + * @param channel The channel to read. + * @param start The start address in the array for reading. You + * can specify NULL to start at the beginning. + * @param dest The destination of the data read. This must be at + * least len bytes long. + * @param len[in,out] in How many bytes to read. out How + * many bytes were read. + * @return Returns the address of the last read byte. + */ + U8* readAfter(S32 channel, U8* start, U8* dest, S32& len) const; + + /** + * @brief Find an address in a buffer array + * + * @param channel The channel to seek in. + * @param start The start address in the array for the seek + * operation. You can specify NULL to start the seek at the + * beginning, or pass in npos to start at the end. + * @param delta How many bytes to seek through the array. + * @return Returns the address of the last read byte. + */ + U8* seek(S32 channel, U8* start, S32 delta) const; + //@} + + /* @name Buffer interaction + */ + //@{ + /** + * @brief Take the contents of another buffer array + * + * This method simply strips the contents out of the source + * buffery array - segments, buffers, etc, and appends them to + * this instance. After this operation, the source is empty and + * ready for reuse. + * @param source The source buffer + * @return Returns true if the operation succeeded. + */ + bool takeContents(LLBufferArray& source); + //@} + + /* @name Segment methods + */ + //@{ + /** + * @brief Split a segments so that address is the last address of + * one segment, and the rest of the original segment becomes + * another segment on the same channel. + * + * After this method call, + * getLastSegmentAddress(*getSegment(address)) == + * address should be true. This call will only create a new + * segment if the statement above is false before the call. Since + * you usually call splitAfter() to change a segment property, use + * getSegment() to perform those operations. + * @param address The address which will become the last address + * of the segment it is in. + * @return Returns an iterator to the segment which contains + * address which is endSegment() on + * failure. + */ + segment_iterator_t splitAfter(U8* address); + + /** + * @brief Get the first segment in the buffer array. + * + * @return Returns the segment if there is one. + */ + segment_iterator_t beginSegment(); + + /** + * @brief Get the one-past-the-end segment in the buffer array + * + * @return Returns the iterator for an invalid segment location. + */ + segment_iterator_t endSegment(); + + /** + * @brief Get the segment which holds the given address. + * + * As opposed to some methods, passing a NULL will result in + * returning the end segment. + * @param address An address in the middle of the sought segment. + * @return Returns the iterator for the segment or endSegment() on + * failure. + */ + const_segment_iterator_t getSegment(U8* address) const; + + /** + * @brief Get the segment which holds the given address. + * + * As opposed to some methods, passing a NULL will result in + * returning the end segment. + * @param address An address in the middle of the sought segment. + * @return Returns the iterator for the segment or endSegment() on + * failure. + */ + segment_iterator_t getSegment(U8* address); + + /** + * @brief Get a segment iterator after address, and a constructed + * segment to represent the next linear block of memory. + * + * This method is a helper by giving you the largest segment + * possible in the out-value param after the address provided. The + * iterator will be useful for iteration, while the segment can be + * used for direct access to memory after address if the return + * values isnot end. Passing in NULL will return beginSegment() + * which may be endSegment(). The segment returned will only be + * zero length if the return value equals end. + * This is really just a helper method, since all the information + * returned could be constructed through other methods. + * @param address An address in the middle of the sought segment. + * @param segment[out] segment to be used for reading or writing + * @return Returns an iterator which contains at least segment or + * endSegment() on failure. + */ + segment_iterator_t constructSegmentAfter(U8* address, LLSegment& segment); + + /** + * @brief Make a new segment at the end of buffer array + * + * This method will attempt to create a new and empty segment of + * the specified length. The segment created may be shorter than + * requested. + * @param channel[in] The channel for the newly created segment. + * @param length[in] The requested length of the segment. + * @return Returns an iterator which contains at least segment or + * endSegment() on failure. + */ + segment_iterator_t makeSegment(S32 channel, S32 length); + + /** + * @brief Erase the segment if it is in the buffer array. + * + * @param iter An iterator referring to the segment to erase. + * @return Returns true on success. + */ + bool eraseSegment(const segment_iterator_t& iter); + //@} + +protected: + /** + * @brief Optimally put data in buffers, and reutrn segments. + * + * This is an internal function used to create buffers as + * necessary, and sequence the segments appropriately for the + * various ways to copy data from src into this. + * If this method fails, it may actually leak some space inside + * buffers, but I am not too worried about the slim possibility + * that we may have some 'dead' space which will be recovered when + * the buffer (which we will not lose) is deleted. Addressing this + * weakness will make the buffers almost as complex as a general + * memory management system. + * @param channel The channel for this data + * @param src The start of memory for the data to be copied + * @param len The number of bytes of data to copy + * @param segments Out-value for the segments created. + * @return Returns true if the method worked. + */ + bool copyIntoBuffers( + S32 channel, + const U8* src, + S32 len, + std::vector& segments); + +protected: + S32 mNextBaseChannel; + buffer_list_t mBuffers; + segment_list_t mSegments; +}; + +#endif // LL_LLBUFFER_H diff --git a/indra/llmessage/llbufferstream.cpp b/indra/llmessage/llbufferstream.cpp new file mode 100644 index 0000000000..684548b408 --- /dev/null +++ b/indra/llmessage/llbufferstream.cpp @@ -0,0 +1,265 @@ +/** + * @file llbufferstream.cpp + * @author Phoenix + * @date 2005-10-10 + * @brief Implementation of the buffer iostream classes + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llbufferstream.h" + +#include "llbuffer.h" +#include "llmemtype.h" + +static const S32 DEFAULT_OUTPUT_SEGMENT_SIZE = 1024 * 4; + +/* + * LLBufferStreamBuf + */ +LLBufferStreamBuf::LLBufferStreamBuf( + const LLChannelDescriptors& channels, + LLBufferArray* buffer) : + mChannels(channels), + mBuffer(buffer) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); +} + +LLBufferStreamBuf::~LLBufferStreamBuf() +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + sync(); +} + +// virtual +int LLBufferStreamBuf::underflow() +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + //lldebugs << "LLBufferStreamBuf::underflow()" << llendl; + if(!mBuffer) + { + return EOF; + } + LLSegment segment; + LLBufferArray::segment_iterator_t it; + U8* last_pos = (U8*)gptr(); + if(last_pos) --last_pos; + + LLBufferArray::segment_iterator_t end = mBuffer->endSegment(); + + // Get iterator to full segment containing last_pos + // and construct sub-segment starting at last_pos. + // Note: segment may != *it at this point + it = mBuffer->constructSegmentAfter(last_pos, segment); + if(it == end) + { + return EOF; + } + + // Iterate through segments to find a non-empty segment on input channel. + while((!segment.isOnChannel(mChannels.in()) || (segment.size() == 0))) + { + ++it; + if(it == end) + { + return EOF; + } + + segment = *it; + } + + char* start = (char*)segment.data(); + setg(start, start, start + segment.size()); + return *gptr(); +} + +// virtual +int LLBufferStreamBuf::overflow(int c) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + if(!mBuffer) + { + return EOF; + } + if(EOF == c) + { + // if someone puts an EOF, I suppose we should sync and return + // success. + if(0 == sync()) + { + return 1; + } + else + { + return EOF; + } + } + + // since we got here, we have a buffer, and we have a character to + // put on it. + LLBufferArray::segment_iterator_t it; + it = mBuffer->makeSegment(mChannels.out(), DEFAULT_OUTPUT_SEGMENT_SIZE); + if(it != mBuffer->endSegment()) + { + char* start = (char*)(*it).data(); + (*start) = (char)(c); + setp(start + 1, start + (*it).size()); + return c; + } + else + { + return EOF; + } +} + +// virtual +int LLBufferStreamBuf::sync() +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + int return_value = -1; + if(!mBuffer) + { + return return_value; + } + + // set the put pointer so that we force an overflow on the next + // write. + U8* address = (U8*)pptr(); + setp(NULL, NULL); + + // *NOTE: I bet we could just --address. Need to think about that. + address = mBuffer->seek(mChannels.out(), address, -1); + if(address) + { + LLBufferArray::segment_iterator_t it; + it = mBuffer->splitAfter(address); + LLBufferArray::segment_iterator_t end = mBuffer->endSegment(); + if(it != end) + { + ++it; + if(it != end) + { + mBuffer->eraseSegment(it); + } + return_value = 0; + } + } + else + { + // nothing was put on the buffer, so the sync() is a no-op. + return_value = 0; + } + return return_value; +} + +// virtual +#if( LL_WINDOWS || __GNUC__ > 2) +LLBufferStreamBuf::pos_type LLBufferStreamBuf::seekoff( + LLBufferStreamBuf::off_type off, + std::ios::seekdir way, + std::ios::openmode which) +#else +streampos LLBufferStreamBuf::seekoff( + streamoff off, + std::ios::seekdir way, + std::ios::openmode which) +#endif +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); + if(!mBuffer + || ((way == std::ios::beg) && (off < 0)) + || ((way == std::ios::end) && (off > 0))) + { + return -1; + } + U8* address = NULL; + if(which & std::ios::in) + { + U8* base_addr = NULL; + switch(way) + { + case std::ios::end: + base_addr = (U8*)LLBufferArray::npos; + break; + case std::ios::cur: + // get the current get pointer and adjust it for buffer + // array semantics. + base_addr = (U8*)gptr(); + break; + case std::ios::beg: + default: + // NULL is fine + break; + } + address = mBuffer->seek(mChannels.in(), base_addr, off); + if(address) + { + LLBufferArray::segment_iterator_t iter; + iter = mBuffer->getSegment(address); + char* start = (char*)(*iter).data(); + setg(start, (char*)address, start + (*iter).size()); + } + else + { + address = (U8*)(-1); + } + } + if(which & std::ios::out) + { + U8* base_addr = NULL; + switch(way) + { + case std::ios::end: + base_addr = (U8*)LLBufferArray::npos; + break; + case std::ios::cur: + // get the current put pointer and adjust it for buffer + // array semantics. + base_addr = (U8*)pptr(); + break; + case std::ios::beg: + default: + // NULL is fine + break; + } + address = mBuffer->seek(mChannels.out(), base_addr, off); + if(address) + { + LLBufferArray::segment_iterator_t iter; + iter = mBuffer->getSegment(address); + setp((char*)address, (char*)(*iter).data() + (*iter).size()); + } + else + { + address = (U8*)(-1); + } + } + +#if( LL_WINDOWS || __GNUC__ > 2 ) + S32 rv = (S32)(intptr_t)address; + return (pos_type)rv; +#else + return (streampos)address; +#endif +} + + +/* + * LLBufferStream + */ +LLBufferStream::LLBufferStream( + const LLChannelDescriptors& channels, + LLBufferArray* buffer) : + std::iostream(&mStreamBuf), + mStreamBuf(channels, buffer) +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); +} + +LLBufferStream::~LLBufferStream() +{ + LLMemType m1(LLMemType::MTYPE_IO_BUFFER); +} diff --git a/indra/llmessage/llbufferstream.h b/indra/llmessage/llbufferstream.h new file mode 100644 index 0000000000..8b972322ce --- /dev/null +++ b/indra/llmessage/llbufferstream.h @@ -0,0 +1,134 @@ +/** + * @file llbufferstream.h + * @author Phoenix + * @date 2005-10-10 + * @brief Classes to treat an LLBufferArray as a c++ iostream. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLBUFFERSTREAM_H +#define LL_LLBUFFERSTREAM_H + +#include +#include +#include "llbuffer.h" + +/** + * @class LLBufferStreamBuf + * @brief This implements the buffer wrapper for an istream + * + * The buffer array passed in is not owned by the stream buf object. + */ +class LLBufferStreamBuf : public std::streambuf +{ +public: + LLBufferStreamBuf( + const LLChannelDescriptors& channels, + LLBufferArray* buffer); + virtual ~LLBufferStreamBuf(); + +protected: +#if( LL_WINDOWS || __GNUC__ > 2 ) + typedef std::streambuf::pos_type pos_type; + typedef std::streambuf::off_type off_type; +#endif + + /* @name streambuf vrtual implementations + */ + //@{ + /* + * @brief called when we hit the end of input + * + * @return Returns the character at the current position or EOF. + */ + virtual int underflow(); + + /* + * @brief called when we hit the end of output + * + * @param c The character to store at the current put position + * @return Returns EOF if the function failed. Any other value on success. + */ + virtual int overflow(int c); + + /* + * @brief synchronize the buffer + * + * @return Returns 0 on success or -1 on failure. + */ + virtual int sync(); + + /* + * @brief Seek to an offset position in a stream. + * + * @param off Offset value relative to way paramter + * @param way The seek direction. One of ios::beg, ios::cur, and ios::end. + * @param which Which pointer to modify. One of ios::in, ios::out, + * or both masked together. + * @return Returns the new position or an invalid position on failure. + */ +#if( LL_WINDOWS || __GNUC__ > 2) + virtual pos_type seekoff( + off_type off, + std::ios::seekdir way, + std::ios::openmode which); +#else + virtual streampos seekoff( + streamoff off, + std::ios::seekdir way, + std::ios::openmode which); +#endif + + /* + * @brief Get s sequence of characters from the input + * + * @param dst Pointer to a block of memory to accept the characters + * @param length Number of characters to be read + * @return Returns the number of characters read + */ + //virtual streamsize xsgetn(char* dst, streamsize length); + + /* + * @brief Write some characters to output + * + * @param src Pointer to a sequence of characters to be output + * @param length Number of characters to be put + * @return Returns the number of characters written + */ + //virtual streamsize xsputn(char* src, streamsize length); + //@} + +protected: + // This channels we are working on. + LLChannelDescriptors mChannels; + + // The buffer we work on + LLBufferArray* mBuffer; +}; + + +/** + * @class LLBufferStream + * @brief This implements an istream based wrapper around an LLBufferArray. + * + * This class does not own the buffer array, and does not hold a + * shared pointer to it. Since the class itself is fairly ligthweight, + * just make one on the stack when needed and let it fall out of + * scope. + */ +class LLBufferStream : public std::iostream +{ +public: + LLBufferStream( + const LLChannelDescriptors& channels, + LLBufferArray* buffer); + ~LLBufferStream(); + +protected: + LLBufferStreamBuf mStreamBuf; +}; + + +#endif // LL_LLBUFFERSTREAM_H diff --git a/indra/llmessage/llcachename.cpp b/indra/llmessage/llcachename.cpp new file mode 100644 index 0000000000..075f4f01cf --- /dev/null +++ b/indra/llmessage/llcachename.cpp @@ -0,0 +1,763 @@ +/** + * @file llcachename.cpp + * @brief A hierarchical cache of first and last names queried based on UUID. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llcachename.h" + +// system includes +#include // strcpy +#include +#include +#include +#include + +// linden library includes +#include "message.h" +#include "llrand.h" +#include "lldbstrings.h" +#include "llframetimer.h" +#include "llhost.h" +#include "lluuid.h" + +// Constants +const char* CN_WAITING = "(waiting)"; +const char* CN_NOBODY = "(nobody)"; +const char* CN_NONE = "(none)"; +const char* CN_HIPPOS = "(hippos)"; +const F32 HIPPO_PROBABILITY = 0.01f; + +// File version number +const S32 CN_FILE_VERSION = 2; + +// Globals +LLCacheName* gCacheName = NULL; + +/// --------------------------------------------------------------------------- +/// class LLCacheNameEntry +/// --------------------------------------------------------------------------- + +namespace { + class LLCacheNameEntry + { + public: + LLCacheNameEntry(); + + public: + bool mIsGroup; + U32 mCreateTime; // unix time_t + char mFirstName[DB_FIRST_NAME_BUF_SIZE]; /*Flawfinder: ignore*/ + char mLastName[DB_LAST_NAME_BUF_SIZE]; /*Flawfinder: ignore*/ + char mGroupName[DB_GROUP_NAME_BUF_SIZE]; /*Flawfinder: ignore*/ + }; + + LLCacheNameEntry::LLCacheNameEntry() + { + mFirstName[0] = '\0'; + mLastName[0] = '\0'; + mGroupName[0] = '\0'; + } + + + class PendingReply + { + public: + LLUUID mID; + LLCacheNameCallback mCallback; + LLHost mHost; + void* mData; + PendingReply(const LLUUID& id, LLCacheNameCallback callback, void* data = NULL) + : mID(id), mCallback(callback), mData(data) + { } + + PendingReply(const LLUUID& id, const LLHost& host) + : mID(id), mCallback(0), mHost(host) + { } + + void done() { mID.setNull(); } + bool isDone() const { return mID.isNull() != FALSE; } + }; + + class ReplySender + { + public: + ReplySender(LLMessageSystem* msg); + ~ReplySender(); + + void send(const LLUUID& id, + const LLCacheNameEntry& entry, const LLHost& host); + + private: + void flush(); + + LLMessageSystem* mMsg; + bool mPending; + bool mCurrIsGroup; + LLHost mCurrHost; + }; + + ReplySender::ReplySender(LLMessageSystem* msg) + : mMsg(msg), mPending(false) + { } + + ReplySender::~ReplySender() + { + flush(); + } + + void ReplySender::send(const LLUUID& id, + const LLCacheNameEntry& entry, const LLHost& host) + { + if (mPending) + { + if (mCurrIsGroup != entry.mIsGroup + || mCurrHost != host) + { + flush(); + } + } + + if (!mPending) + { + mPending = true; + mCurrIsGroup = entry.mIsGroup; + mCurrHost = host; + + if(mCurrIsGroup) + mMsg->newMessageFast(_PREHASH_UUIDGroupNameReply); + else + mMsg->newMessageFast(_PREHASH_UUIDNameReply); + } + + mMsg->nextBlockFast(_PREHASH_UUIDNameBlock); + mMsg->addUUIDFast(_PREHASH_ID, id); + if(mCurrIsGroup) + { + mMsg->addStringFast(_PREHASH_GroupName, entry.mGroupName); + } + else + { + mMsg->addStringFast(_PREHASH_FirstName, entry.mFirstName); + mMsg->addStringFast(_PREHASH_LastName, entry.mLastName); + } + + if(mMsg->isSendFullFast(_PREHASH_UUIDNameBlock)) + { + flush(); + } + } + + void ReplySender::flush() + { + if (mPending) + { + mMsg->sendReliable(mCurrHost); + mPending = false; + } + } + + + typedef std::vector AskQueue; + typedef std::vector ReplyQueue; + typedef std::map Cache; + typedef std::vector Observers; +}; + +class LLCacheName::Impl +{ +public: + LLMessageSystem* mMsg; + LLHost mUpstreamHost; + + Cache mCache; + // the map of UUIDs to names + + AskQueue mAskNameQueue; + AskQueue mAskGroupQueue; + // UUIDs to ask our upstream host about + + ReplyQueue mReplyQueue; + // requests awaiting replies from us + + Observers mObservers; + + LLFrameTimer mProcessTimer; + + Impl(LLMessageSystem* msg); + ~Impl(); + + void processPendingAsks(); + void processPendingReplies(); + void sendRequest(const char* msg_name, const AskQueue& queue); + + // Message system callbacks. + void processUUIDRequest(LLMessageSystem* msg, bool isGroup); + void processUUIDReply(LLMessageSystem* msg, bool isGroup); + + static void handleUUIDNameRequest(LLMessageSystem* msg, void** userdata); + static void handleUUIDNameReply(LLMessageSystem* msg, void** userdata); + static void handleUUIDGroupNameRequest(LLMessageSystem* msg, void** userdata); + static void handleUUIDGroupNameReply(LLMessageSystem* msg, void** userdata); + + void notifyObservers(const LLUUID& id, const char* first, const char* last, BOOL group); +}; + + +/// -------------------------------------------------------------------------- +/// class LLCacheName +/// --------------------------------------------------------------------------- + +LLCacheName::LLCacheName(LLMessageSystem* msg) + : impl(* new Impl(msg)) + { } + +LLCacheName::LLCacheName(LLMessageSystem* msg, const LLHost& upstream_host) + : impl(* new Impl(msg)) +{ + setUpstream(upstream_host); +} + +LLCacheName::~LLCacheName() +{ + delete &impl; +} + +LLCacheName::Impl::Impl(LLMessageSystem* msg) + : mMsg(msg), mUpstreamHost(LLHost::invalid) +{ + mMsg->setHandlerFuncFast( + _PREHASH_UUIDNameRequest, handleUUIDNameRequest, (void**)this); + mMsg->setHandlerFuncFast( + _PREHASH_UUIDNameReply, handleUUIDNameReply, (void**)this); + mMsg->setHandlerFuncFast( + _PREHASH_UUIDGroupNameRequest, handleUUIDGroupNameRequest, (void**)this); + mMsg->setHandlerFuncFast( + _PREHASH_UUIDGroupNameReply, handleUUIDGroupNameReply, (void**)this); +} + + +LLCacheName::Impl::~Impl() +{ + for_each(mCache.begin(), mCache.end(), DeletePairedPointer()); +} + + +void LLCacheName::setUpstream(const LLHost& upstream_host) +{ + impl.mUpstreamHost = upstream_host; +} + +void LLCacheName::addObserver(LLCacheNameCallback callback) +{ + impl.mObservers.push_back(callback); +} + + +void LLCacheName::importFile(FILE* fp) +{ + S32 count = 0; + + const S32 BUFFER_SIZE = 1024; + char buffer[BUFFER_SIZE]; /*Flawfinder: ignore*/ + + char id_string[MAX_STRING]; /*Flawfinder: ignore*/ + char firstname[MAX_STRING]; /*Flawfinder: ignore*/ + char lastname[MAX_STRING]; /*Flawfinder: ignore*/ + U32 create_time; + + // This is OK if the first line is actually a name. We just don't load it. + char* valid = fgets(buffer, BUFFER_SIZE, fp); + if (!valid) return; + + char version_string[BUFFER_SIZE]; /*Flawfinder: ignore*/ + S32 version = 0; + S32 match = sscanf(buffer, "%s %d", version_string, &version); // XXXTBD + if ( match != 2 + || strcmp(version_string, "version") + || version != CN_FILE_VERSION) + { + llwarns << "Ignoring old cache name file format" << llendl; + return; + } + + // We'll expire entries more than a week old + U32 now = (U32)time(NULL); + const U32 SECS_PER_DAY = 60 * 60 * 24; + U32 delete_before_time = now - (7 * SECS_PER_DAY); + + while(!feof(fp)) + { + valid = fgets(buffer, BUFFER_SIZE, fp); + if (!valid) break; + + match = sscanf(buffer, "%s %u %s %s", // XXXTBD + id_string, + &create_time, + firstname, + lastname); + if (4 != match) continue; + + LLUUID id(id_string); + if (id.isNull()) continue; + + // undo trivial XOR + S32 i; + for (i = 0; i < UUID_BYTES; i++) + { + id.mData[i] ^= 0x33; + } + + // Don't load entries that are more than a week old + if (create_time < delete_before_time) continue; + + LLCacheNameEntry* entry = new LLCacheNameEntry(); + entry->mIsGroup = false; + entry->mCreateTime = create_time; + LLString::copy(entry->mFirstName, firstname, DB_FIRST_NAME_BUF_SIZE); + LLString::copy(entry->mLastName, lastname, DB_LAST_NAME_BUF_SIZE); + impl.mCache[id] = entry; + + count++; + } + + llinfos << "LLCacheName loaded " << count << " names" << llendl; +} + + +void LLCacheName::exportFile(FILE* fp) +{ + fprintf(fp, "version\t%d\n", CN_FILE_VERSION); + + for (Cache::iterator iter = impl.mCache.begin(), + end = impl.mCache.end(); + iter != end; iter++) + { + LLCacheNameEntry* entry = iter->second; + // Only write entries for which we have valid data. + // HACK: Only write agent names. This makes the reader easier. + if ( entry->mFirstName[0] + && entry->mLastName[0]) + { + LLUUID id = iter->first; + + // Trivial XOR encoding + S32 i; + for (i = 0; i < UUID_BYTES; i++) + { + id.mData[i] ^= 0x33; + } + + char id_string[UUID_STR_SIZE]; /*Flawfinder:ignore*/ + id.toString(id_string); + + // ...not a group name + fprintf(fp, "%s\t%u\t%s\t%s\n", + id_string, + entry->mCreateTime, + entry->mFirstName, + entry->mLastName); + } + } +} + + +BOOL LLCacheName::getName(const LLUUID& id, char* first, char* last) +{ + if(id.isNull()) + { + // The function signature needs to change to pass in the + // length of first and last. + strcpy(first, CN_NOBODY); + last[0] = '\0'; + return FALSE; + } + + LLCacheNameEntry* entry = get_ptr_in_map(impl.mCache, id ); + if (entry) + { + // The function signature needs to change to pass in the + // length of first and last. + strcpy(first, entry->mFirstName); + strcpy(last, entry->mLastName); + return TRUE; + } + else + { + //The function signature needs to change to pass in the + //length of first and last. + strcpy(first,(frand(1.0f) < HIPPO_PROBABILITY) + ? CN_HIPPOS + : CN_WAITING); + strcpy(last, ""); + + impl.mAskNameQueue.push_back(id); + return FALSE; + } + +} + + + +BOOL LLCacheName::getGroupName(const LLUUID& id, char* group) +{ + if(id.isNull()) + { + // The function signature needs to change to pass in the + // length of first and last. + strcpy(group, CN_NONE); + return FALSE; + } + + LLCacheNameEntry* entry = get_ptr_in_map(impl.mCache,id); + if (entry && !entry->mGroupName[0]) + { + // COUNTER-HACK to combat James' HACK in exportFile()... + // this group name was loaded from a name cache that did not + // bother to save the group name ==> we must ask for it + lldebugs << "LLCacheName queuing HACK group request: " << id << llendl; + entry = NULL; + } + + if (entry) + { + // The function signature needs to change to pass in the length + // of group. + strcpy(group, entry->mGroupName); + return TRUE; + } + else + { + // The function signature needs to change to pass in the length + // of first and last. + strcpy(group, CN_WAITING); + + impl.mAskGroupQueue.push_back(id); + return FALSE; + } +} + +// TODO: Make the cache name callback take a SINGLE std::string, +// not a separate first and last name. +void LLCacheName::get(const LLUUID& id, BOOL is_group, LLCacheNameCallback callback, void* user_data) +{ + if(id.isNull()) + { + callback(id, CN_NOBODY, "", is_group, user_data); + } + + LLCacheNameEntry* entry = get_ptr_in_map(impl.mCache, id ); + if (entry) + { + if (!entry->mIsGroup) + { + callback(id, entry->mFirstName, entry->mLastName, entry->mIsGroup, user_data); + } + else + { + callback(id, entry->mGroupName, "", entry->mIsGroup, user_data); + } + } + else + { + if (!is_group) + { + impl.mAskNameQueue.push_back(id); + } + else + { + impl.mAskGroupQueue.push_back(id); + } + impl.mReplyQueue.push_back(PendingReply(id, callback, user_data)); + } +} + +void LLCacheName::processPending() +{ + const F32 SECS_BETWEEN_PROCESS = 0.1f; + if(!impl.mProcessTimer.checkExpirationAndReset(SECS_BETWEEN_PROCESS)) + { + return; + } + + if(!impl.mUpstreamHost.isOk()) + { + lldebugs << "LLCacheName::processPending() - bad upstream host." + << llendl; + return; + } + + impl.processPendingAsks(); + impl.processPendingReplies(); +} + +void LLCacheName::deleteEntriesOlderThan(S32 secs) +{ + U32 now = (U32)time(NULL); + U32 expire_time = now - secs; + for(Cache::iterator iter = impl.mCache.begin(); iter != impl.mCache.end(); ) + { + Cache::iterator curiter = iter++; + LLCacheNameEntry* entry = curiter->second; + if (entry->mCreateTime < expire_time) + { + delete entry; + impl.mCache.erase(curiter); + } + } +} + + +void LLCacheName::dump() +{ + for (Cache::iterator iter = impl.mCache.begin(), + end = impl.mCache.end(); + iter != end; iter++) + { + LLCacheNameEntry* entry = iter->second; + if (entry->mIsGroup) + { + llinfos + << iter->first << " = (group) " + << entry->mGroupName + << " @ " << entry->mCreateTime + << llendl; + } + else + { + llinfos + << iter->first << " = " + << entry->mFirstName << " " << entry->mLastName + << " @ " << entry->mCreateTime + << llendl; + } + } +} + +void LLCacheName::Impl::processPendingAsks() +{ + sendRequest(_PREHASH_UUIDNameRequest, mAskNameQueue); + sendRequest(_PREHASH_UUIDGroupNameRequest, mAskGroupQueue); + mAskNameQueue.clear(); + mAskGroupQueue.clear(); +} + +void LLCacheName::Impl::processPendingReplies() +{ + ReplyQueue::iterator it = mReplyQueue.begin(); + ReplyQueue::iterator end = mReplyQueue.end(); + + // First call all the callbacks, because they might send messages. + for(; it != end; ++it) + { + LLCacheNameEntry* entry = get_ptr_in_map(mCache, it->mID); + if(!entry) continue; + + if (it->mCallback) + { + if (!entry->mIsGroup) + { + (it->mCallback)(it->mID, + entry->mFirstName, entry->mLastName, + FALSE, it->mData); + } + else { + (it->mCallback)(it->mID, + entry->mGroupName, "", + TRUE, it->mData); + } + } + } + + // Forward on all replies, if needed. + ReplySender sender(mMsg); + for (it = mReplyQueue.begin(); it != end; ++it) + { + LLCacheNameEntry* entry = get_ptr_in_map(mCache, it->mID); + if(!entry) continue; + + if (it->mHost.isOk()) + { + sender.send(it->mID, *entry, it->mHost); + } + + it->done(); + } + + mReplyQueue.erase( + remove_if(mReplyQueue.begin(), mReplyQueue.end(), + std::mem_fun_ref(&PendingReply::isDone)), + mReplyQueue.end()); +} + + +void LLCacheName::Impl::sendRequest( + const char* msg_name, + const AskQueue& queue) +{ + if(queue.empty()) + { + return; + } + + bool start_new_message = true; + AskQueue::const_iterator it = queue.begin(); + AskQueue::const_iterator end = queue.end(); + for(; it != end; ++it) + { + if(start_new_message) + { + start_new_message = false; + mMsg->newMessageFast(msg_name); + } + mMsg->nextBlockFast(_PREHASH_UUIDNameBlock); + mMsg->addUUIDFast(_PREHASH_ID, (*it)); + + if(mMsg->isSendFullFast(_PREHASH_UUIDNameBlock)) + { + start_new_message = true; + mMsg->sendReliable(mUpstreamHost); + } + } + if(!start_new_message) + { + mMsg->sendReliable(mUpstreamHost); + } +} + +void LLCacheName::Impl::notifyObservers(const LLUUID& id, + const char* first, const char* last, BOOL is_group) +{ + for (Observers::const_iterator i = mObservers.begin(), + end = mObservers.end(); + i != end; + ++i) + { + (**i)(id, first, last, is_group, NULL); + } +} + + +void LLCacheName::Impl::processUUIDRequest(LLMessageSystem* msg, bool isGroup) +{ + // You should only get this message if the cache is at the simulator + // level, hence having an upstream provider. + if (!mUpstreamHost.isOk()) + { + llwarns << "LLCacheName - got UUID name/group request, but no upstream provider!" << llendl; + return; + } + + LLHost fromHost = msg->getSender(); + ReplySender sender(msg); + + S32 count = msg->getNumberOfBlocksFast(_PREHASH_UUIDNameBlock); + for(S32 i = 0; i < count; ++i) + { + LLUUID id; + msg->getUUIDFast(_PREHASH_UUIDNameBlock, _PREHASH_ID, id, i); + LLCacheNameEntry* entry = get_ptr_in_map(mCache, id); + if(entry) + { + if (isGroup != entry->mIsGroup) + { + llwarns << "LLCacheName - Asked for " + << (isGroup ? "group" : "user") << " name, " + << "but found " + << (entry->mIsGroup ? "group" : "user") + << ": " << id << llendl; + } + else + { + // ...it's in the cache, so send it as the reply + sender.send(id, *entry, fromHost); + } + } + else + { + if (isGroup) + { + mAskGroupQueue.push_back(id); + } + else + { + mAskNameQueue.push_back(id); + } + + mReplyQueue.push_back(PendingReply(id, fromHost)); + } + } +} + + + +void LLCacheName::Impl::processUUIDReply(LLMessageSystem* msg, bool isGroup) +{ + S32 count = msg->getNumberOfBlocksFast(_PREHASH_UUIDNameBlock); + for(S32 i = 0; i < count; ++i) + { + LLUUID id; + msg->getUUIDFast(_PREHASH_UUIDNameBlock, _PREHASH_ID, id, i); + LLCacheNameEntry* entry = get_ptr_in_map(mCache, id); + if (!entry) + { + entry = new LLCacheNameEntry; + mCache[id] = entry; + } + + entry->mIsGroup = isGroup; + entry->mCreateTime = (U32)time(NULL); + if (!isGroup) + { + msg->getStringFast(_PREHASH_UUIDNameBlock, _PREHASH_FirstName, DB_FIRST_NAME_BUF_SIZE, entry->mFirstName, i); + msg->getStringFast(_PREHASH_UUIDNameBlock, _PREHASH_LastName, DB_LAST_NAME_BUF_SIZE, entry->mLastName, i); + } + else + { + msg->getStringFast(_PREHASH_UUIDNameBlock, _PREHASH_GroupName, DB_GROUP_NAME_BUF_SIZE, entry->mGroupName, i); + } + + if (!isGroup) + { + notifyObservers(id, + entry->mFirstName, entry->mLastName, + FALSE); + } + else + { + notifyObservers(id, entry->mGroupName, "", TRUE); + } + } +} + + + +// static call back functions + +void LLCacheName::Impl::handleUUIDNameReply(LLMessageSystem* msg, void** userData) +{ + ((LLCacheName::Impl*)userData)->processUUIDReply(msg, false); +} + +void LLCacheName::Impl::handleUUIDNameRequest(LLMessageSystem* msg, void** userData) +{ + ((LLCacheName::Impl*)userData)->processUUIDRequest(msg, false); +} + +void LLCacheName::Impl::handleUUIDGroupNameRequest(LLMessageSystem* msg, void** userData) +{ + ((LLCacheName::Impl*)userData)->processUUIDRequest(msg, true); +} + +void LLCacheName::Impl::handleUUIDGroupNameReply(LLMessageSystem* msg, void** userData) +{ + ((LLCacheName::Impl*)userData)->processUUIDReply(msg, true); +} + + + + diff --git a/indra/llmessage/llcachename.h b/indra/llmessage/llcachename.h new file mode 100644 index 0000000000..ec9c467d8b --- /dev/null +++ b/indra/llmessage/llcachename.h @@ -0,0 +1,90 @@ +/** + * @file llcachename.h + * @brief A cache of names from UUIDs. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLCACHENAME_H +#define LL_LLCACHENAME_H + +// Forward declarations +#include + +class LLMessageSystem; +class LLHost; +class LLUUID; + +// agent_id/group_id, first_name, last_name, is_group, user_data +typedef void (*LLCacheNameCallback)(const LLUUID&, const char*, const char*, BOOL, void*); + +// Here's the theory: +// If you request a name that isn't in the cache, it returns "waiting" +// and requests the data. After the data arrives, you get that on +// subsequent calls. +// If the data hasn't been updated in an hour, it requests it again, +// but keeps giving you the old value until new data arrives. +// If you haven't requested the data in an hour, it releases it. +class LLCacheName +{ +public: + LLCacheName(LLMessageSystem* msg); + LLCacheName(LLMessageSystem* msg, const LLHost& upstream_host); + ~LLCacheName(); + + // registers the upstream host + // for viewers, this is the currently connected simulator + // for simulators, this is the data server + void setUpstream(const LLHost& upstream_host); + + void addObserver(LLCacheNameCallback callback); + void removeObserver(LLCacheNameCallback callback); + + // storing cache on disk; for viewer, in name.cache + void importFile(FILE* fp); + void exportFile(FILE* fp); + + // If available, copies the first and last name into the strings provided. + // first must be at least DB_FIRST_NAME_BUF_SIZE characters. + // last must be at least DB_LAST_NAME_BUF_SIZE characters. + // If not available, copies the string "waiting". + // Returns TRUE iff available. + BOOL getName(const LLUUID& id, char* first, char* last); + + // If available, this method copies the group name into the string + // provided. The caller must allocate at least + // DB_GROUP_NAME_BUF_SIZE characters. If not available, this + // method copies the string "waiting". Returns TRUE iff available. + BOOL getGroupName(const LLUUID& id, char* group); + + // Call the callback with the group or avatar name. + // If the data is currently available, may call the callback immediatly + // otherwise, will request the data, and will call the callback when + // available. There is no garuntee the callback will ever be called. + void get(const LLUUID& id, BOOL is_group, LLCacheNameCallback callback, void* user_data = NULL); + + // LEGACY + void getName(const LLUUID& id, LLCacheNameCallback callback, void* user_data = NULL) + { get(id, FALSE, callback, user_data); } + + // This method needs to be called from time to time to send out + // requests. + void processPending(); + + // Expire entries created more than "secs" seconds ago. + void deleteEntriesOlderThan(S32 secs); + + // Debugging + void dump(); + +private: + class Impl; + Impl& impl; +}; + + + +extern LLCacheName* gCacheName; + +#endif diff --git a/indra/llmessage/llchainio.cpp b/indra/llmessage/llchainio.cpp new file mode 100644 index 0000000000..c7795f1792 --- /dev/null +++ b/indra/llmessage/llchainio.cpp @@ -0,0 +1,70 @@ +/** + * @file llchainio.cpp + * @author Phoenix + * @date 2005-08-04 + * @brief Implementaiton of the chain factory. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llchainio.h" + +#include "lliopipe.h" +#include "llioutil.h" + + +/** + * LLDeferredChain + */ +// static +bool LLDeferredChain::addToPump( + LLPumpIO* pump, + F32 in_seconds, + const LLPumpIO::chain_t& deferred_chain, + F32 chain_timeout) +{ + if(!pump) return false; + LLPumpIO::chain_t sleep_chain; + sleep_chain.push_back(LLIOPipe::ptr_t(new LLIOSleep(in_seconds))); + sleep_chain.push_back( + LLIOPipe::ptr_t(new LLIOAddChain(deferred_chain, chain_timeout))); + + // give it a litle bit of padding. + pump->addChain(sleep_chain, in_seconds + 10.0f); + return true; +} + +/** + * LLChainIOFactory + */ +LLChainIOFactory::LLChainIOFactory() +{ +} + +// virtual +LLChainIOFactory::~LLChainIOFactory() +{ +} + +#if 0 +bool LLChainIOFactory::build(LLIOPipe* in, LLIOPipe* out) const +{ + if(!in || !out) + { + return false; + } + LLIOPipe* first = NULL; + LLIOPipe* last = NULL; + if(build_impl(first, last) && first && last) + { + in->connect(first); + last->connect(out); + return true; + } + LLIOPipe::ptr_t foo(first); + LLIOPipe::ptr_t bar(last); + return false; +} +#endif diff --git a/indra/llmessage/llchainio.h b/indra/llmessage/llchainio.h new file mode 100644 index 0000000000..f07432da05 --- /dev/null +++ b/indra/llmessage/llchainio.h @@ -0,0 +1,117 @@ +/** + * @file llchainio.h + * @author Phoenix + * @date 2005-08-04 + * @brief This class declares the interface for constructing io chains. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLCHAINIO_H +#define LL_LLCHAINIO_H + +#include "llpumpio.h" + +/** + * @class LLDeferredChain + * @brief This class allows easy addition of a chain which will sleep + * and then process another chain. + */ +class LLDeferredChain +{ +public: + /** + * @brief Add a chain to a pump in a finite # of seconds + * + * @prarm pump The pump to work on. + * @prarm in_seconds The number of seconds from now when chain should start. + * @prarm chain The chain to add in in_seconds seconds. + * @prarm chain_timeout timeout for chain on the pump. + * @return Returns true if the operation was queued. + */ + static bool addToPump( + LLPumpIO* pump, + F32 in_seconds, + const LLPumpIO::chain_t& chain, + F32 chain_timeout); +}; + +/** + * @class LLChainIOFactory + * @brief This class is an abstract base class for building io chains. + * + * This declares an abstract base class for a chain factory. The + * factory is used to connect an input pipe to the first pipe in the + * chain, and an output pipe to the last pipe in the chain. This will + * allow easy construction for buffer based io like services to for + * API centered IO while abstracting the input and output to simple + * data passing. + * To use this class, you should derive a class which implements the + * build method. + */ +class LLChainIOFactory +{ +public: + // Constructor + LLChainIOFactory(); + + // Destructor + virtual ~LLChainIOFactory(); + + /** + * @brief Build the chian with in as the first and end as the last + * + * The caller of the LLChainIOFactory is responsible for managing + * the memory of the in pipe. All of the chains generated by the + * factory will be ref counted as usual, so the caller will also + * need to break the links in the chain. + * @param chain The chain which will have new pipes appended + * @param context A context for use by this factory if you choose + * @retrun Returns true if the call was successful. + */ + virtual bool build(LLPumpIO::chain_t& chain, LLSD context) const = 0; + +protected: +}; + +/** + * @class LLSimpleIOFactory + * @brief Basic implementation for making a factory that returns a + * 'chain' of one object + */ +template +class LLSimpleIOFactory : public LLChainIOFactory +{ +public: + virtual bool build(LLPumpIO::chain_t& chain, LLSD context) const + { + chain.push_back(LLIOPipe::ptr_t(new Pipe)); + return true; + } +}; + +/** + * @class LLCloneIOFactory + * @brief Implementation for a facory which copies a particular pipe. + */ +template +class LLCloneIOFactory : public LLChainIOFactory +{ +public: + LLCloneIOFactory(Pipe* original) : + mHandle(original), + mOriginal(original) {} + + virtual bool build(LLPumpIO::chain_t& chain, LLSD context) const + { + chain.push_back(LLIOPipe::ptr_t(new Pipe(*mOriginal))); + return true; + } + +protected: + LLIOPipe::ptr_t mHandle; + Pipe* mOriginal; +}; + +#endif // LL_LLCHAINIO_H diff --git a/indra/llmessage/llcircuit.cpp b/indra/llmessage/llcircuit.cpp new file mode 100644 index 0000000000..d3ef92e4a7 --- /dev/null +++ b/indra/llmessage/llcircuit.cpp @@ -0,0 +1,1382 @@ +/** + * @file llcircuit.cpp + * @brief Class to track UDP endpoints for the message system. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#if LL_WINDOWS + +#include + +#else + +#if LL_LINUX +#include // RTLD_LAZY +#endif +#include +#include +#include + +#endif + + +#if !defined(USE_CIRCUIT_LIST) +#include +#endif +#include +#include +#include + +#include "llcircuit.h" + +#include "message.h" +#include "llrand.h" +#include "llstl.h" +#include "lltransfermanager.h" + +const F32 PING_INTERVAL = 5.f; // seconds +const S32 PING_START_BLOCK = 3; // How many pings behind we have to be to consider ourself blocked. +const S32 PING_RELEASE_BLOCK = 2; // How many pings behind we have to be to consider ourself unblocked. + +const F32 TARGET_PERIOD_LENGTH = 5.f; // seconds +const F32 LL_DUPLICATE_SUPPRESSION_TIMEOUT = 60.f; //seconds - this can be long, as time-based cleanup is + // only done when wrapping packetids, now... + +LLCircuitData::LLCircuitData(const LLHost &host, TPACKETID in_id) +: mHost (host), + mWrapID(0), + mPacketsOutID(0), + mPacketsInID(in_id), + mHighestPacketID(in_id), + mTrusted(FALSE), + mbAllowTimeout(TRUE), + mbAlive(TRUE), + mBlocked(FALSE), + mPingTime(0.0), + mLastPingSendTime(0.0), + mLastPingReceivedTime(0.0), + mNextPingSendTime(0.0), + mPingsInTransit(0), + mLastPingID(0), + mPingDelay(INITIAL_PING_VALUE_MSEC), + mPingDelayAveraged((F32)INITIAL_PING_VALUE_MSEC), + mUnackedPacketCount(0), + mUnackedPacketBytes(0), + mLocalEndPointID(), + mPacketsOut(0), + mPacketsIn(0), + mPacketsLost(0), + mBytesIn(0), + mBytesOut(0), + mLastPeriodLength(-1.f), + mBytesInLastPeriod(0), + mBytesOutLastPeriod(0), + mBytesInThisPeriod(0), + mBytesOutThisPeriod(0), + mPeakBPSIn(0), + mPeakBPSOut(0), + mPeriodTime(0.0), + mExistenceTimer(), + mCurrentResendCount(0) +{ + // Need to guarantee that this time is up to date, we may be creating a circuit even though we haven't been + // running a message system loop. + F64 mt_sec = LLMessageSystem::getMessageTimeSeconds(TRUE); + F32 distribution_offset = frand(1.0f); + + mPingTime = mt_sec; + mLastPingSendTime = mt_sec + PING_INTERVAL * distribution_offset; + mLastPingReceivedTime = mt_sec; + mNextPingSendTime = mLastPingSendTime + 0.95*PING_INTERVAL + frand(0.1f*PING_INTERVAL); + mPeriodTime = mt_sec; + + mTimeoutCallback = NULL; + mTimeoutUserData = NULL; + + mLocalEndPointID.generate(); +} + + +LLCircuitData::~LLCircuitData() +{ + LLReliablePacket *packetp = NULL; + + // Clean up all pending transfers. + gTransferManager.cleanupConnection(mHost); + + // remove all pending reliable messages on this circuit + std::vector doomed; + reliable_iter iter; + reliable_iter end = mUnackedPackets.end(); + for(iter = mUnackedPackets.begin(); iter != end; ++iter) + { + packetp = iter->second; + gMessageSystem->mFailedResendPackets++; + if(gMessageSystem->mVerboseLog) + { + doomed.push_back(packetp->mPacketID); + } + if (packetp->mCallback) + { + packetp->mCallback(packetp->mCallbackData,LL_ERR_CIRCUIT_GONE); + } + + // Update stats + mUnackedPacketCount--; + mUnackedPacketBytes -= packetp->mBufferLength; + + delete packetp; + } + + // remove all pending final retry reliable messages on this circuit + end = mFinalRetryPackets.end(); + for(iter = mFinalRetryPackets.begin(); iter != end; ++iter) + { + packetp = iter->second; + gMessageSystem->mFailedResendPackets++; + if(gMessageSystem->mVerboseLog) + { + doomed.push_back(packetp->mPacketID); + } + if (packetp->mCallback) + { + packetp->mCallback(packetp->mCallbackData,LL_ERR_CIRCUIT_GONE); + } + + // Update stats + mUnackedPacketCount--; + mUnackedPacketBytes -= packetp->mBufferLength; + + delete packetp; + } + + // log aborted reliable packets for this circuit. + if(gMessageSystem->mVerboseLog && !doomed.empty()) + { + std::ostringstream str; + std::ostream_iterator append(str, " "); + str << "MSG: -> " << mHost << "\tABORTING RELIABLE:\t"; + std::copy(doomed.begin(), doomed.end(), append); + llinfos << str.str().c_str() << llendl; + } +} + + +void LLCircuitData::ackReliablePacket(TPACKETID packet_num) +{ + reliable_iter iter; + LLReliablePacket *packetp; + + iter = mUnackedPackets.find(packet_num); + if (iter != mUnackedPackets.end()) + { + packetp = iter->second; + + if(gMessageSystem->mVerboseLog) + { + std::ostringstream str; + str << "MSG: <- " << packetp->mHost << "\tRELIABLE ACKED:\t" + << packetp->mPacketID; + llinfos << str.str().c_str() << llendl; + } + if (packetp->mCallback) + { + if (packetp->mTimeout < 0.f) // negative timeout will always return timeout even for successful ack, for debugging + { + packetp->mCallback(packetp->mCallbackData,LL_ERR_TCP_TIMEOUT); + } + else + { + packetp->mCallback(packetp->mCallbackData,LL_ERR_NOERR); + } + } + + // Update stats + mUnackedPacketCount--; + mUnackedPacketBytes -= packetp->mBufferLength; + + // Cleanup + delete packetp; + mUnackedPackets.erase(iter); + return; + } + + iter = mFinalRetryPackets.find(packet_num); + if (iter != mFinalRetryPackets.end()) + { + packetp = iter->second; + // llinfos << "Packet " << packet_num << " removed from the pending list" << llendl; + if(gMessageSystem->mVerboseLog) + { + std::ostringstream str; + str << "MSG: <- " << packetp->mHost << "\tRELIABLE ACKED:\t" + << packetp->mPacketID; + llinfos << str.str().c_str() << llendl; + } + if (packetp->mCallback) + { + if (packetp->mTimeout < 0.f) // negative timeout will always return timeout even for successful ack, for debugging + { + packetp->mCallback(packetp->mCallbackData,LL_ERR_TCP_TIMEOUT); + } + else + { + packetp->mCallback(packetp->mCallbackData,LL_ERR_NOERR); + } + } + + // Update stats + mUnackedPacketCount--; + mUnackedPacketBytes -= packetp->mBufferLength; + + // Cleanup + delete packetp; + mFinalRetryPackets.erase(iter); + } + else + { + // Couldn't find this packet on either of the unacked lists. + // maybe it's a duplicate ack? + } +} + + + +S32 LLCircuitData::resendUnackedPackets(const F64 now) +{ + S32 resent_packets = 0; + LLReliablePacket *packetp; + + + // + // Theoretically we should search through the list for the packet with the oldest + // packet ID, as otherwise when we WRAP we will resend reliable packets out of order. + // Since resends are ALREADY out of order, and wrapping is highly rare (16+million packets), + // I'm not going to worry about this for now - djs + // + + reliable_iter iter; + BOOL have_resend_overflow = FALSE; + for (iter = mUnackedPackets.begin(); iter != mUnackedPackets.end();) + { + packetp = iter->second; + + // Only check overflow if we haven't had one yet. + if (!have_resend_overflow) + { + have_resend_overflow = mThrottles.checkOverflow(TC_RESEND, 0); + } + + if (have_resend_overflow) + { + // We've exceeded our bandwidth for resends. + // Time to stop trying to send them. + + // If we have too many unacked packets, we need to start dropping expired ones. + if (mUnackedPacketBytes > 512000) + { + if (now > packetp->mExpirationTime) + { + // This circuit has overflowed. Do not retry. Do not pass go. + packetp->mRetries = 0; + // Remove it from this list and add it to the final list. + mUnackedPackets.erase(iter++); + mFinalRetryPackets[packetp->mPacketID] = packetp; + } + else + { + ++iter; + } + // Move on to the next unacked packet. + continue; + } + + if (mUnackedPacketBytes > 256000 && !(getPacketsOut() % 1024)) + { + // Warn if we've got a lot of resends waiting. + llwarns << mHost << " has " << mUnackedPacketBytes + << " bytes of reliable messages waiting" << llendl; + } + // Stop resending. There are less than 512000 unacked packets. + break; + } + + if (now > packetp->mExpirationTime) + { + packetp->mRetries--; + + // retry + mCurrentResendCount++; + + gMessageSystem->mResentPackets++; + + if(gMessageSystem->mVerboseLog) + { + std::ostringstream str; + str << "MSG: -> " << packetp->mHost + << "\tRESENDING RELIABLE:\t" << packetp->mPacketID; + llinfos << str.str().c_str() << llendl; + } + + packetp->mBuffer[0] |= LL_RESENT_FLAG; // tag packet id as being a resend + + gMessageSystem->mPacketRing.sendPacket(packetp->mSocket, + (char *)packetp->mBuffer, packetp->mBufferLength, + packetp->mHost); + + mThrottles.throttleOverflow(TC_RESEND, packetp->mBufferLength * 8.f); + + // The new method, retry time based on ping + if (packetp->mPingBasedRetry) + { + packetp->mExpirationTime = now + llmax(LL_MINIMUM_RELIABLE_TIMEOUT_SECONDS, (LL_RELIABLE_TIMEOUT_FACTOR * getPingDelayAveraged())); + } + else + { + // custom, constant retry time + packetp->mExpirationTime = now + packetp->mTimeout; + } + + if (!packetp->mRetries) + { + // Last resend, remove it from this list and add it to the final list. + mUnackedPackets.erase(iter++); + mFinalRetryPackets[packetp->mPacketID] = packetp; + } + else + { + // Don't remove it yet, it still gets to try to resend at least once. + ++iter; + } + resent_packets++; + } + else + { + // Don't need to do anything with this packet, keep iterating. + ++iter; + } + } + + + for (iter = mFinalRetryPackets.begin(); iter != mFinalRetryPackets.end();) + { + packetp = iter->second; + if (now > packetp->mExpirationTime) + { + // fail (too many retries) + //llinfos << "Packet " << packetp->mPacketID << " removed from the pending list: exceeded retry limit" << llendl; + //if (packetp->mMessageName) + //{ + // llinfos << "Packet name " << packetp->mMessageName << llendl; + //} + gMessageSystem->mFailedResendPackets++; + + if(gMessageSystem->mVerboseLog) + { + std::ostringstream str; + str << "MSG: -> " << packetp->mHost << "\tABORTING RELIABLE:\t" + << packetp->mPacketID; + llinfos << str.str().c_str() << llendl; + } + + if (packetp->mCallback) + { + packetp->mCallback(packetp->mCallbackData,LL_ERR_TCP_TIMEOUT); + } + + // Update stats + mUnackedPacketCount--; + mUnackedPacketBytes -= packetp->mBufferLength; + + mFinalRetryPackets.erase(iter++); + delete packetp; + } + else + { + ++iter; + } + } + + return mUnackedPacketCount; +} + + +LLCircuit::LLCircuit() : mLastCircuit(NULL) +{ +} + +LLCircuit::~LLCircuit() +{ + // delete pointers in the map. + std::for_each(mCircuitData.begin(), + mCircuitData.end(), + llcompose1( + DeletePointerFunctor(), + llselect2nd())); +} + +LLCircuitData *LLCircuit::addCircuitData(const LLHost &host, TPACKETID in_id) +{ + // This should really validate if one already exists + llinfos << "LLCircuit::addCircuitData for " << host << llendl; + LLCircuitData *tempp = new LLCircuitData(host, in_id); + mCircuitData.insert(circuit_data_map::value_type(host, tempp)); + mPingSet.insert(tempp); + + mLastCircuit = tempp; + return tempp; +} + +void LLCircuit::removeCircuitData(const LLHost &host) +{ + llinfos << "LLCircuit::removeCircuitData for " << host << llendl; + mLastCircuit = NULL; + circuit_data_map::iterator it = mCircuitData.find(host); + if(it != mCircuitData.end()) + { + LLCircuitData *cdp = it->second; + mCircuitData.erase(it); + + LLCircuit::ping_set_t::iterator psit = mPingSet.find(cdp); + if (psit != mPingSet.end()) + { + mPingSet.erase(psit); + } + else + { + llwarns << "Couldn't find entry for next ping in ping set!" << llendl; + } + + // Clean up from optimization maps + mUnackedCircuitMap.erase(host); + mSendAckMap.erase(host); + delete cdp; + } + + // This also has to happen AFTER we nuke the circuit, because various + // callbacks for the circuit may result in messages being sent to + // this circuit, and the setting of mLastCircuit. We don't check + // if the host matches, but we don't really care because mLastCircuit + // is an optimization, and this happens VERY rarely. + mLastCircuit = NULL; +} + +void LLCircuitData::setAlive(BOOL b_alive) +{ + if (mbAlive != b_alive) + { + mPacketsOutID = 0; + mPacketsInID = 0; + mbAlive = b_alive; + } + if (b_alive) + { + mLastPingReceivedTime = LLMessageSystem::getMessageTimeSeconds(); + mPingsInTransit = 0; + mBlocked = FALSE; + } +} + + +void LLCircuitData::setAllowTimeout(BOOL allow) +{ + mbAllowTimeout = allow; + + if (allow) + { + // resuming circuit + // make sure it's alive + setAlive(TRUE); + } +} + + +// Reset per-period counters if necessary. +void LLCircuitData::checkPeriodTime() +{ + F64 mt_sec = LLMessageSystem::getMessageTimeSeconds(); + F64 period_length = mt_sec - mPeriodTime; + if ( period_length > TARGET_PERIOD_LENGTH) + { + F32 bps_in = (F32)(mBytesInThisPeriod * 8.f / period_length); + if (bps_in > mPeakBPSIn) + { + mPeakBPSIn = bps_in; + } + + F32 bps_out = (F32)(mBytesOutThisPeriod * 8.f / period_length); + if (bps_out > mPeakBPSOut) + { + mPeakBPSOut = bps_out; + } + + mBytesInLastPeriod = mBytesInThisPeriod; + mBytesOutLastPeriod = mBytesOutThisPeriod; + mBytesInThisPeriod = 0; + mBytesOutThisPeriod = 0; + mLastPeriodLength = (F32)period_length; + + mPeriodTime = mt_sec; + } +} + + +void LLCircuitData::addBytesIn(S32 bytes) +{ + mBytesIn += bytes; + mBytesInThisPeriod += bytes; +} + + +void LLCircuitData::addBytesOut(S32 bytes) +{ + mBytesOut += bytes; + mBytesOutThisPeriod += bytes; +} + + +void LLCircuitData::addReliablePacket(S32 mSocket, U8 *buf_ptr, S32 buf_len, LLReliablePacketParams *params) +{ + LLReliablePacket *packet_info; + + packet_info = new LLReliablePacket(mSocket, buf_ptr, buf_len, params); + + mUnackedPacketCount++; + mUnackedPacketBytes += packet_info->mBufferLength; + + if (params && params->mRetries) + { + mUnackedPackets[packet_info->mPacketID] = packet_info; + } + else + { + mFinalRetryPackets[packet_info->mPacketID] = packet_info; + } +} + + +void LLCircuit::resendUnackedPackets(S32& unacked_list_length, S32& unacked_list_size) +{ + F64 now = LLMessageSystem::getMessageTimeSeconds(); + unacked_list_length = 0; + unacked_list_size = 0; + + LLCircuitData* circ; + circuit_data_map::iterator end = mUnackedCircuitMap.end(); + for(circuit_data_map::iterator it = mUnackedCircuitMap.begin(); it != end; ++it) + { + circ = (*it).second; + unacked_list_length += circ->resendUnackedPackets(now); + unacked_list_size += circ->getUnackedPacketBytes(); + } +} + + +BOOL LLCircuitData::isDuplicateResend(TPACKETID packetnum) +{ + return (mRecentlyReceivedReliablePackets.find(packetnum) != mRecentlyReceivedReliablePackets.end()); +} + + +void LLCircuit::dumpResends() +{ + circuit_data_map::iterator end = mCircuitData.end(); + for(circuit_data_map::iterator it = mCircuitData.begin(); it != end; ++it) + { + (*it).second->dumpResendCountAndReset(); + } +} + +LLCircuitData* LLCircuit::findCircuit(const LLHost& host) const +{ + // An optimization on finding the previously found circuit. + if (mLastCircuit && (mLastCircuit->mHost == host)) + { + return mLastCircuit; + } + + circuit_data_map::const_iterator it = mCircuitData.find(host); + if(it == mCircuitData.end()) + { + return NULL; + } + mLastCircuit = it->second; + return mLastCircuit; +} + + +BOOL LLCircuit::isCircuitAlive(const LLHost& host) const +{ + LLCircuitData *cdp = findCircuit(host); + if(cdp) + { + return cdp->mbAlive; + } + + return FALSE; +} + +void LLCircuitData::setTimeoutCallback(void (*callback_func)(const LLHost &host, void *user_data), void *user_data) +{ + mTimeoutCallback = callback_func; + mTimeoutUserData = user_data; +} + +void LLCircuitData::checkPacketInID(TPACKETID id, BOOL receive_resent) +{ + // Done as floats so we don't have to worry about running out of room + // with U32 getting poked into an S32. + F32 delta = (F32)mHighestPacketID - (F32)id; + if (delta > (0.5f*LL_MAX_OUT_PACKET_ID)) + { + // We've almost definitely wrapped, reset the mLastPacketID to be low again. + mHighestPacketID = id; + } + else if (delta < (-0.5f*LL_MAX_OUT_PACKET_ID)) + { + // This is almost definitely an old packet coming in after a wrap, ignore it. + } + else + { + mHighestPacketID = llmax(mHighestPacketID, id); + } + + + // Have we received anything on this circuit yet? + if (0 == mPacketsIn) + { + // Must be first packet from unclosed circuit. + mPacketsIn++; + setPacketInID((id + 1) % LL_MAX_OUT_PACKET_ID); + + return; + } + + mPacketsIn++; + + + // now, check to see if we've got a gap + if ((mPacketsInID == id)) + { + // nope! bump and wrap the counter, then return + mPacketsInID++; + mPacketsInID = (mPacketsInID) % LL_MAX_OUT_PACKET_ID; + } + else if (id < mWrapID) + { + // id < mWrapID will happen if the first few packets are out of order. . . + // at that point we haven't marked anything "potentially lost" and + // the out-of-order packet will cause a full wrap marking all the IDs "potentially lost" + + // do nothing + } + else + { + // we have a gap! if that id is in the map, remove it from the map, leave mCurrentCircuit->mPacketsInID + // alone + // otherwise, walk from mCurrentCircuit->mPacketsInID to id with wrapping, adding the values to the map + // and setting mPacketsInID to id + 1 % LL_MAX_OUT_PACKET_ID + + if (mPotentialLostPackets.find(id) != mPotentialLostPackets.end()) + { + if(gMessageSystem->mVerboseLog) + { + std::ostringstream str; + str << "MSG: <- " << mHost << "\tRECOVERING LOST:\t" << id; + llinfos << str.str().c_str() << llendl; + } + // llinfos << "removing potential lost: " << id << llendl; + mPotentialLostPackets.erase(id); + } + else if (!receive_resent) // don't freak out over out-of-order reliable resends + { + U64 time = LLMessageSystem::getMessageTimeUsecs(); + TPACKETID index = mPacketsInID; + S32 gap_count = 0; + if ((index < id) && ((id - index) < 16)) + { + while (index != id) + { + if(gMessageSystem->mVerboseLog) + { + std::ostringstream str; + str << "MSG: <- " << mHost << "\tPACKET GAP:\t" + << index; + llinfos << str.str().c_str() << llendl; + } + +// llinfos << "adding potential lost: " << index << llendl; + mPotentialLostPackets[index] = time; + index++; + index = index % LL_MAX_OUT_PACKET_ID; + gap_count++; + } + } + else + { + llinfos << "packet_out_of_order - got packet " << id << " expecting " << index << " from " << mHost << llendl; + if(gMessageSystem->mVerboseLog) + { + std::ostringstream str; + str << "MSG: <- " << mHost << "\tPACKET GAP:\t" + << id << " expected " << index; + llinfos << str.str().c_str() << llendl; + } + } + + mPacketsInID = id + 1; + mPacketsInID = (mPacketsInID) % LL_MAX_OUT_PACKET_ID; + + if (gap_count > 128) + { + llwarns << "Packet loss gap filler running amok!" << llendl; + } + else if (gap_count > 16) + { + llwarns << "Sustaining large amounts of packet loss!" << llendl; + } + + } + } +} + + +void LLCircuit::updateWatchDogTimers(LLMessageSystem *msgsys) +{ + F64 cur_time = LLMessageSystem::getMessageTimeSeconds(); + S32 count = mPingSet.size(); + S32 cur = 0; + + // Only process each circuit once at most, stop processing if no circuits + while((cur < count) && !mPingSet.empty()) + { + cur++; + + LLCircuit::ping_set_t::iterator psit = mPingSet.begin(); + LLCircuitData *cdp = *psit; + + if (!cdp->mbAlive) + { + // We suspect that this case should never happen, given how + // the alive status is set. + // Skip over dead circuits, just add the ping interval and push it to the back + // Always remember to remove it from the set before changing the sorting + // key (mNextPingSendTime) + mPingSet.erase(psit); + cdp->mNextPingSendTime = cur_time + PING_INTERVAL; + mPingSet.insert(cdp); + continue; + } + else + { + // Check to see if this needs a ping + if (cur_time < cdp->mNextPingSendTime) + { + // This circuit doesn't need a ping, break out because + // we have a sorted list, thus no more circuits need pings + break; + } + + // Update watchdog timers + if (cdp->updateWatchDogTimers(msgsys)) + { + // Randomize our pings a bit by doing some up to 5% early or late + F64 dt = 0.95f*PING_INTERVAL + frand(0.1f*PING_INTERVAL); + + // Remove it, and reinsert it with the new next ping time. + // Always remove before changing the sorting key. + mPingSet.erase(psit); + cdp->mNextPingSendTime = cur_time + dt; + mPingSet.insert(cdp); + + // Update our throttles + cdp->mThrottles.dynamicAdjust(); + + // Update some stats, this is not terribly important + cdp->checkPeriodTime(); + } + else + { + // This mPingSet.erase isn't necessary, because removing the circuit will + // remove the ping set. + //mPingSet.erase(psit); + removeCircuitData(cdp->mHost); + } + } + } +} + + +BOOL LLCircuitData::updateWatchDogTimers(LLMessageSystem *msgsys) +{ + F64 cur_time = LLMessageSystem::getMessageTimeSeconds(); + mLastPingSendTime = cur_time; + + if (!checkCircuitTimeout()) + { + // Pass this back to the calling LLCircuit, this circuit needs to be cleaned up. + return FALSE; + } + + // WARNING! + // Duplicate suppression can FAIL if packets are delivered out of + // order, although it's EXTREMELY unlikely. It would require + // that the ping get delivered out of order enough that the ACK + // for the packet that it was out of order with was received BEFORE + // the ping was sent. + + // Find the current oldest reliable packetID + // This is to handle the case if we actually manage to wrap our + // packet IDs - the oldest will actually have a higher packet ID + // than the current. + BOOL wrapped = FALSE; + reliable_iter iter; + iter = mUnackedPackets.upper_bound(getPacketOutID()); + if (iter == mUnackedPackets.end()) + { + // Nothing AFTER this one, so we want the lowest packet ID + // then. + iter = mUnackedPackets.begin(); + wrapped = TRUE; + } + + TPACKETID packet_id = 0; + + // Check against the "final" packets + BOOL wrapped_final = FALSE; + reliable_iter iter_final; + iter_final = mFinalRetryPackets.upper_bound(getPacketOutID()); + if (iter_final == mFinalRetryPackets.end()) + { + iter_final = mFinalRetryPackets.begin(); + wrapped_final = TRUE; + } + + //llinfos << mHost << " - unacked count " << mUnackedPackets.size() << llendl; + //llinfos << mHost << " - final count " << mFinalRetryPackets.size() << llendl; + if (wrapped != wrapped_final) + { + // One of the "unacked" or "final" lists hasn't wrapped. Whichever one + // hasn't has the oldest packet. + if (!wrapped) + { + // Hasn't wrapped, so the one on the + // unacked packet list is older + packet_id = iter->first; + //llinfos << mHost << ": nowrapped unacked" << llendl; + } + else + { + packet_id = iter_final->first; + //llinfos << mHost << ": nowrapped final" << llendl; + } + } + else + { + // They both wrapped, we can just use the minimum of the two. + if ((iter == mUnackedPackets.end()) && (iter_final == mFinalRetryPackets.end())) + { + // Wow! No unacked packets at all! + // Send the ID of the last packet we sent out. + // This will flush all of the destination's + // unacked packets, theoretically. + //llinfos << mHost << ": No unacked!" << llendl; + packet_id = getPacketOutID(); + } + else + { + BOOL had_unacked = FALSE; + if (iter != mUnackedPackets.end()) + { + // Unacked list has the lowest so far + packet_id = iter->first; + had_unacked = TRUE; + //llinfos << mHost << ": Unacked" << llendl; + } + + if (iter_final != mFinalRetryPackets.end()) + { + // Use the lowest of the unacked list and the final list + if (had_unacked) + { + // Both had a packet, use the lowest. + packet_id = llmin(packet_id, iter_final->first); + //llinfos << mHost << ": Min of unacked/final" << llendl; + } + else + { + // Only the final had a packet, use it. + packet_id = iter_final->first; + //llinfos << mHost << ": Final!" << llendl; + } + } + } + } + + // Send off the another ping. + pingTimerStart(); + msgsys->newMessageFast(_PREHASH_StartPingCheck); + msgsys->nextBlock(_PREHASH_PingID); + msgsys->addU8Fast(_PREHASH_PingID, nextPingID()); + msgsys->addU32Fast(_PREHASH_OldestUnacked, packet_id); + msgsys->sendMessage(mHost); + + // Also do lost packet accounting. + // Check to see if anything on our lost list is old enough to + // be considered lost + + LLCircuitData::packet_time_map::iterator it; + U64 timeout = (U64)(1000000.0*llmin(LL_MAX_LOST_TIMEOUT, getPingDelayAveraged() * LL_LOST_TIMEOUT_FACTOR)); + + U64 mt_usec = LLMessageSystem::getMessageTimeUsecs(); + for (it = mPotentialLostPackets.begin(); it != mPotentialLostPackets.end(); ) + { + U64 delta_t_usec = mt_usec - (*it).second; + if (delta_t_usec > timeout) + { + // let's call this one a loss! + mPacketsLost++; + gMessageSystem->mDroppedPackets++; + if(gMessageSystem->mVerboseLog) + { + std::ostringstream str; + str << "MSG: <- " << mHost << "\tLOST PACKET:\t" + << (*it).first; + llinfos << str.str().c_str() << llendl; + } + mPotentialLostPackets.erase((*(it++)).first); + } + else + { + ++it; + } + } + + return TRUE; +} + + +void LLCircuitData::clearDuplicateList(TPACKETID oldest_id) +{ + // purge old data from the duplicate suppression queue + + // we want to KEEP all x where oldest_id <= x <= last incoming packet, and delete everything else. + + //llinfos << mHost << ": clearing before oldest " << oldest_id << llendl; + //llinfos << "Recent list before: " << mRecentlyReceivedReliablePackets.size() << llendl; + if (oldest_id < mHighestPacketID) + { + // Clean up everything with a packet ID less than oldest_id. + packet_time_map::iterator pit_start; + packet_time_map::iterator pit_end; + pit_start = mRecentlyReceivedReliablePackets.begin(); + pit_end = mRecentlyReceivedReliablePackets.lower_bound(oldest_id); + mRecentlyReceivedReliablePackets.erase(pit_start, pit_end); + } + + // Do timeout checks on everything with an ID > mHighestPacketID. + // This should be empty except for wrapping IDs. Thus, this should be + // highly rare. + U64 mt_usec = LLMessageSystem::getMessageTimeUsecs(); + + packet_time_map::iterator pit; + for(pit = mRecentlyReceivedReliablePackets.upper_bound(mHighestPacketID); + pit != mRecentlyReceivedReliablePackets.end(); ) + { + // Validate that the packet ID seems far enough away + if ((pit->first - mHighestPacketID) < 100) + { + llwarns << "Probably incorrectly timing out non-wrapped packets!" << llendl; + } + U64 delta_t_usec = mt_usec - (*pit).second; + F64 delta_t_sec = delta_t_usec * SEC_PER_USEC; + if (delta_t_sec > LL_DUPLICATE_SUPPRESSION_TIMEOUT) + { + // enough time has elapsed we're not likely to get a duplicate on this one + llinfos << "Clearing " << pit->first << " from recent list" << llendl; + mRecentlyReceivedReliablePackets.erase(pit++); + } + else + { + ++pit; + } + } + //llinfos << "Recent list after: " << mRecentlyReceivedReliablePackets.size() << llendl; +} + +BOOL LLCircuitData::checkCircuitTimeout() +{ + F64 time_since_last_ping = LLMessageSystem::getMessageTimeSeconds() - mLastPingReceivedTime; + + // Nota Bene: This needs to be turned off if you are debugging multiple simulators + if (time_since_last_ping > PING_INTERVAL_MAX) + { + llwarns << "LLCircuitData::checkCircuitTimeout for " << mHost << " last ping " << time_since_last_ping << " seconds ago." < PING_INTERVAL_ALARM) + { + //llwarns << "Unresponsive circuit: " << mHost << ": " << time_since_last_ping << " seconds since last ping."<< llendl; + } + return TRUE; +} + +// Call this method when a reliable message comes in - this will +// correctly place the packet in the correct list to be acked later. +BOOL LLCircuitData::collectRAck(TPACKETID packet_num) +{ + if (mAcks.empty()) + { + // First extra ack, we need to add ourselves to the list of circuits that need to send acks + gMessageSystem->mCircuitInfo.mSendAckMap[mHost] = this; + } + + mAcks.push_back(packet_num); + return TRUE; +} + +// this method is called during the message system processAcks() to +// send out any acks that did not get sent already. +void LLCircuit::sendAcks() +{ + LLCircuitData* cd; + circuit_data_map::iterator end = mSendAckMap.end(); + for(circuit_data_map::iterator it = mSendAckMap.begin(); it != end; ++it) + { + cd = (*it).second; + + S32 count = (S32)cd->mAcks.size(); + if(count > 0) + { + // send the packet acks + S32 acks_this_packet = 0; + for(S32 i = 0; i < count; ++i) + { + if(acks_this_packet == 0) + { + gMessageSystem->newMessageFast(_PREHASH_PacketAck); + } + gMessageSystem->nextBlockFast(_PREHASH_Packets); + gMessageSystem->addU32Fast(_PREHASH_ID, cd->mAcks[i]); + ++acks_this_packet; + if(acks_this_packet > 250) + { + gMessageSystem->sendMessage(cd->mHost); + acks_this_packet = 0; + } + } + if(acks_this_packet > 0) + { + gMessageSystem->sendMessage(cd->mHost); + } + + if(gMessageSystem->mVerboseLog) + { + std::ostringstream str; + str << "MSG: -> " << cd->mHost << "\tPACKET ACKS:\t"; + std::ostream_iterator append(str, " "); + std::copy(cd->mAcks.begin(), cd->mAcks.end(), append); + llinfos << str.str().c_str() << llendl; + } + + // empty out the acks list + cd->mAcks.clear(); + } + } + + // All acks have been sent, clear the map + mSendAckMap.clear(); +} + + +std::ostream& operator<<(std::ostream& s, LLCircuitData& circuit) +{ + F32 age = circuit.mExistenceTimer.getElapsedTimeF32(); + + using namespace std; + s << "Circuit " << circuit.mHost << " "; + s << circuit.mRemoteID << " "; + s << (circuit.mbAlive ? "Alive" : "Not Alive") << " "; + s << (circuit.mbAllowTimeout ? "Timeout Allowed" : "Timeout Not Allowed"); + s << endl; + + s << " Packets Lost: " << circuit.mPacketsLost; + s << " Measured Ping: " << circuit.mPingDelay; + s << " Averaged Ping: " << circuit.mPingDelayAveraged; + s << endl; + + s << "Global In/Out " << S32(age) << " sec"; + s << " KBytes: " << circuit.mBytesIn / 1024 << "/" << circuit.mBytesOut / 1024; + s << " Kbps: "; + s << S32(circuit.mBytesIn * 8.f / circuit.mExistenceTimer.getElapsedTimeF32() / 1024.f); + s << "/"; + s << S32(circuit.mBytesOut * 8.f / circuit.mExistenceTimer.getElapsedTimeF32() / 1024.f); + s << " Packets: " << circuit.mPacketsIn << "/" << circuit.mPacketsOut; + s << endl; + + s << "Recent In/Out " << S32(circuit.mLastPeriodLength) << " sec"; + s << " KBytes: "; + s << circuit.mBytesInLastPeriod / 1024; + s << "/"; + s << circuit.mBytesOutLastPeriod / 1024; + s << " Kbps: "; + s << S32(circuit.mBytesInLastPeriod * 8.f / circuit.mLastPeriodLength / 1024.f); + s << "/"; + s << S32(circuit.mBytesOutLastPeriod * 8.f / circuit.mLastPeriodLength / 1024.f); + s << " Peak Kbps: "; + s << S32(circuit.mPeakBPSIn / 1024.f); + s << "/"; + s << S32(circuit.mPeakBPSOut / 1024.f); + s << endl; + + return s; +} + +const char* LLCircuitData::getInfoString() const +{ + std::ostringstream info; + info << "Circuit: " << mHost << std::endl + << (mbAlive ? "Alive" : "Not Alive") << std::endl + << "Age: " << mExistenceTimer.getElapsedTimeF32() << std::endl; + return info.str().c_str(); +} + +void LLCircuitData::dumpResendCountAndReset() +{ + if (mCurrentResendCount) + { + llinfos << "Circuit: " << mHost << " resent " << mCurrentResendCount << " packets" << llendl; + mCurrentResendCount = 0; + } +} + +std::ostream& operator<<(std::ostream& s, LLCircuit &circuit) +{ + s << "Circuit Info:" << std::endl; + LLCircuit::circuit_data_map::iterator end = circuit.mCircuitData.end(); + LLCircuit::circuit_data_map::iterator it; + for(it = circuit.mCircuitData.begin(); it != end; ++it) + { + s << *((*it).second) << std::endl; + } + return s; +} + +const char* LLCircuit::getInfoString() const +{ + std::ostringstream info; + info << "Circuit Info:" << std::endl; + LLCircuit::circuit_data_map::const_iterator end = mCircuitData.end(); + LLCircuit::circuit_data_map::const_iterator it; + for(it = mCircuitData.begin(); it != end; ++it) + { + info << (*it).second->getInfoString() << std::endl; + } + return info.str().c_str(); +} + +void LLCircuit::getCircuitRange( + const LLHost& key, + LLCircuit::circuit_data_map::iterator& first, + LLCircuit::circuit_data_map::iterator& end) +{ + end = mCircuitData.end(); + first = mCircuitData.upper_bound(key); +} + +TPACKETID LLCircuitData::nextPacketOutID() +{ + mPacketsOut++; + + TPACKETID id; + + id = (mPacketsOutID + 1) % LL_MAX_OUT_PACKET_ID; + + if (id < mPacketsOutID) + { + // we just wrapped on a circuit, reset the wrap ID to zero + mWrapID = 0; + } + mPacketsOutID = id; + return id; +} + + +void LLCircuitData::setPacketInID(TPACKETID id) +{ + id = id % LL_MAX_OUT_PACKET_ID; + mPacketsInID = id; + mRecentlyReceivedReliablePackets.clear(); + + mWrapID = id; +} + + +void LLCircuitData::pingTimerStop(const U8 ping_id) +{ + F64 mt_secs = LLMessageSystem::getMessageTimeSeconds(); + + // Nota Bene: no averaging of ping times until we get a feel for how this works + F64 time = mt_secs - mPingTime; + if (time == 0.0) + { + // Ack, we got our ping response on the same frame! Sigh, let's get a real time otherwise + // all of our ping calculations will be skewed. + mt_secs = LLMessageSystem::getMessageTimeSeconds(TRUE); + } + mLastPingReceivedTime = mt_secs; + + // If ping is longer than 1 second, we'll get sequence deltas in the ping. + // Approximate by assuming each ping counts for 1 second (slightly low, probably) + S32 delta_ping = (S32)mLastPingID - (S32) ping_id; + if (delta_ping < 0) + { + delta_ping += 256; + } + + U32 msec = (U32) ((delta_ping*PING_INTERVAL + time) * 1000.f); + setPingDelay(msec); + + mPingsInTransit = delta_ping; + if (mBlocked && (mPingsInTransit <= PING_RELEASE_BLOCK)) + { + mBlocked = FALSE; + } +} + + +void LLCircuitData::pingTimerStart() +{ + mPingTime = LLMessageSystem::getMessageTimeSeconds(); + mPingsInTransit++; + + if (!mBlocked && (mPingsInTransit > PING_START_BLOCK)) + { + mBlocked = TRUE; + } +} + + +U32 LLCircuitData::getPacketsIn() const +{ + return mPacketsIn; +} + + +S32 LLCircuitData::getBytesIn() const +{ + return mBytesIn; +} + + +S32 LLCircuitData::getBytesOut() const +{ + return mBytesOut; +} + + +U32 LLCircuitData::getPacketsOut() const +{ + return mPacketsOut; +} + + +TPACKETID LLCircuitData::getPacketOutID() const +{ + return mPacketsOutID; +} + + +U32 LLCircuitData::getPacketsLost() const +{ + return mPacketsLost; +} + + +BOOL LLCircuitData::isAlive() const +{ + return mbAlive; +} + + +BOOL LLCircuitData::isBlocked() const +{ + return mBlocked; +} + + +BOOL LLCircuitData::getAllowTimeout() const +{ + return mbAllowTimeout; +} + + +U32 LLCircuitData::getPingDelay() const +{ + return mPingDelay; +} + + +F32 LLCircuitData::getPingInTransitTime() +{ + // This may be inaccurate in the case of a circuit that was "dead" and then revived, + // but only until the first round trip ping is sent - djs + F32 time_since_ping_was_sent = 0; + + if (mPingsInTransit) + { + time_since_ping_was_sent = (F32)((mPingsInTransit*PING_INTERVAL - 1) + (LLMessageSystem::getMessageTimeSeconds() - mPingTime))*1000.f; + } + + return time_since_ping_was_sent; +} + + +void LLCircuitData::setPingDelay(U32 ping) +{ + mPingDelay = ping; + mPingDelayAveraged = llmax((F32)ping, getPingDelayAveraged()); + mPingDelayAveraged = ((1.f - LL_AVERAGED_PING_ALPHA) * mPingDelayAveraged) + + (LL_AVERAGED_PING_ALPHA * (F32) ping); + mPingDelayAveraged = llclamp(mPingDelayAveraged, + LL_AVERAGED_PING_MIN, + LL_AVERAGED_PING_MAX); +} + + +F32 LLCircuitData::getPingDelayAveraged() +{ + return llmin(llmax(getPingInTransitTime(), mPingDelayAveraged), LL_AVERAGED_PING_MAX); +} + + +BOOL LLCircuitData::getTrusted() const +{ + return mTrusted; +} + + +void LLCircuitData::setTrusted(BOOL t) +{ + mTrusted = t; +} + +F32 LLCircuitData::getAgeInSeconds() const +{ + return mExistenceTimer.getElapsedTimeF32(); +} diff --git a/indra/llmessage/llcircuit.h b/indra/llmessage/llcircuit.h new file mode 100644 index 0000000000..003734df29 --- /dev/null +++ b/indra/llmessage/llcircuit.h @@ -0,0 +1,315 @@ +/** + * @file llcircuit.h + * @brief Provides a method for tracking network circuit information + * for the UDP message system + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLCIRCUIT_H +#define LL_LLCIRCUIT_H + +#include +#include + +#include "llerror.h" +#include "linked_lists.h" + +#include "lltimer.h" +#include "timing.h" +#include "net.h" +#include "llhost.h" +#include "llpacketack.h" +#include "lluuid.h" +#include "llthrottle.h" + +// +// Constants +// +const F32 PING_INTERVAL_MAX = 100.f; +const F32 PING_INTERVAL_ALARM = 50.f; + + +const F32 LL_AVERAGED_PING_ALPHA = 0.2f; // relaxation constant on ping running average +const F32 LL_AVERAGED_PING_MAX = 2000; // msec +const F32 LL_AVERAGED_PING_MIN = 100; // msec // IW: increased to avoid retransmits when a process is slow + +const U32 INITIAL_PING_VALUE_MSEC = 1000; // initial value for the ping delay, or for ping delay for an unknown circuit + +const TPACKETID LL_MAX_OUT_PACKET_ID = 0x01000000; +const U8 LL_PACKET_ID_SIZE = 4; + +const S32 LL_MAX_RESENT_PACKETS_PER_FRAME = 100; +const S32 LL_MAX_ACKED_PACKETS_PER_FRAME = 200; + +// +// Prototypes and Predefines +// +class LLMessageSystem; +class LLEncodedDatagramService; + +// +// Classes +// + + +class LLCircuitData +{ +public: + LLCircuitData(const LLHost &host, TPACKETID in_id); + ~LLCircuitData(); + + S32 resendUnackedPackets(const F64 now); + void clearDuplicateList(TPACKETID oldest_id); + + + void dumpResendCountAndReset(); // Used for tracking how many resends are being done on a circuit. + + + + // Public because stupid message system callbacks uses it. + void pingTimerStart(); + void pingTimerStop(const U8 ping_id); + void ackReliablePacket(TPACKETID packet_num); + + // remote computer information + const LLUUID& getRemoteID() const { return mRemoteID; } + const LLUUID& getRemoteSessionID() const { return mRemoteSessionID; } + void setRemoteID(const LLUUID& id) { mRemoteID = id; } + void setRemoteSessionID(const LLUUID& id) { mRemoteSessionID = id; } + + void setTrusted(BOOL t); + + // The local end point ID is used when establishing a trusted circuit. + // no matching set function for getLocalEndPointID() + // mLocalEndPointID should only ever be setup in the LLCircuitData constructor + const LLUUID& getLocalEndPointID() const { return mLocalEndPointID; } + + U32 getPingDelay() const; + S32 getPingsInTransit() const { return mPingsInTransit; } + + // ACCESSORS + BOOL isAlive() const; + BOOL isBlocked() const; + BOOL getAllowTimeout() const; + F32 getPingDelayAveraged(); + F32 getPingInTransitTime(); + U32 getPacketsIn() const; + S32 getBytesIn() const; + S32 getBytesOut() const; + U32 getPacketsOut() const; + U32 getPacketsLost() const; + TPACKETID getPacketOutID() const; + BOOL getTrusted() const; + F32 getAgeInSeconds() const; + S32 getUnackedPacketCount() const { return mUnackedPacketCount; } + S32 getUnackedPacketBytes() const { return mUnackedPacketBytes; } + F64 getNextPingSendTime() const { return mNextPingSendTime; } + + LLThrottleGroup &getThrottleGroup() { return mThrottles; } + + class less + { + public: + bool operator()(const LLCircuitData* lhs, const LLCircuitData* rhs) const + { + if (lhs->getNextPingSendTime() < rhs->getNextPingSendTime()) + { + return true; + } + else if (lhs->getNextPingSendTime() > rhs->getNextPingSendTime()) + { + return false; + } + else return lhs > rhs; + } + }; + + // + // Debugging stuff (not necessary for operation) + // + void checkPeriodTime(); // Reset per-period counters if necessary. + friend std::ostream& operator<<(std::ostream& s, LLCircuitData &circuit); + const char* getInfoString() const; + + friend class LLCircuit; + friend class LLMessageSystem; + friend class LLEncodedDatagramService; + friend void crash_on_spaceserver_timeout (const LLHost &host, void *); // HACK, so it has access to setAlive() so it can send a final shutdown message. +protected: + TPACKETID nextPacketOutID(); + void setPacketInID(TPACKETID id); + void checkPacketInID(TPACKETID id, BOOL receive_resent); + void setPingDelay(U32 ping); + BOOL checkCircuitTimeout(); // Return FALSE if the circuit is dead and should be cleaned up + + void addBytesIn(S32 bytes); + void addBytesOut(S32 bytes); + + U8 nextPingID() { mLastPingID++; return mLastPingID; } + + BOOL updateWatchDogTimers(LLMessageSystem *msgsys); // Return FALSE if the circuit is dead and should be cleaned up + + void addReliablePacket(S32 mSocket, U8 *buf_ptr, S32 buf_len, LLReliablePacketParams *params); + BOOL isDuplicateResend(TPACKETID packetnum); + // Call this method when a reliable message comes in - this will + // correctly place the packet in the correct list to be acked + // later. RAack = requested ack + BOOL collectRAck(TPACKETID packet_num); + + + void setTimeoutCallback(void (*callback_func)(const LLHost &host, void *user_data), void *user_data); + + + + void setAlive(BOOL b_alive); + void setAllowTimeout(BOOL allow); + +protected: + // Identification for this circuit. + LLHost mHost; + LLUUID mRemoteID; + LLUUID mRemoteSessionID; + + LLThrottleGroup mThrottles; + + TPACKETID mWrapID; + + // Current packet IDs of incoming/outgoing packets + // Used for packet sequencing/packet loss detection. + TPACKETID mPacketsOutID; + TPACKETID mPacketsInID; + TPACKETID mHighestPacketID; + + + // Callback and data to run in the case of a circuit timeout. + // Used primarily to try and reconnect to servers if they crash/die. + void (*mTimeoutCallback)(const LLHost &host, void *user_data); + void *mTimeoutUserData; + + BOOL mTrusted; // Is this circuit trusted? + BOOL mbAllowTimeout; // Machines can "pause" circuits, forcing them not to be dropped + + BOOL mbAlive; // Indicates whether a circuit is "alive", i.e. responded to pings + + BOOL mBlocked; // Blocked is true if the circuit is hosed, i.e. far behind on pings + + // Not sure what the difference between this and mLastPingSendTime is + F64 mPingTime; // Time at which a ping was sent. + + F64 mLastPingSendTime; // Time we last sent a ping + F64 mLastPingReceivedTime; // Time we last received a ping + F64 mNextPingSendTime; // Time to try and send the next ping + S32 mPingsInTransit; // Number of pings in transit + U8 mLastPingID; // ID of the last ping that we sent out + + + // Used for determining the resend time for reliable resends. + U32 mPingDelay; // raw ping delay + F32 mPingDelayAveraged; // averaged ping delay (fast attack/slow decay) + + typedef std::map packet_time_map; + + packet_time_map mPotentialLostPackets; + packet_time_map mRecentlyReceivedReliablePackets; + std::vector mAcks; + + typedef std::map reliable_map; + typedef reliable_map::iterator reliable_iter; + + reliable_map mUnackedPackets; + reliable_map mFinalRetryPackets; + + S32 mUnackedPacketCount; + S32 mUnackedPacketBytes; + + + LLUUID mLocalEndPointID; + + // + // These variables are being used for statistical and debugging purpose ONLY, + // as far as I can tell. + // + + U32 mPacketsOut; + U32 mPacketsIn; + S32 mPacketsLost; + S32 mBytesIn; + S32 mBytesOut; + + F32 mLastPeriodLength; // seconds + S32 mBytesInLastPeriod; + S32 mBytesOutLastPeriod; + S32 mBytesInThisPeriod; + S32 mBytesOutThisPeriod; + F32 mPeakBPSIn; // bits per second, max of all period bps + F32 mPeakBPSOut; // bits per second, max of all period bps + F64 mPeriodTime; + LLTimer mExistenceTimer; // initialized when circuit created, used to track bandwidth numbers + + S32 mCurrentResendCount; // Number of resent packets since last spam +}; + + +// Actually a singleton class -- the global messagesystem +// has a single LLCircuit member. +class LLCircuit +{ +public: + // CREATORS + LLCircuit(); + ~LLCircuit(); + + // ACCESSORS + LLCircuitData* findCircuit(const LLHost& host) const; + BOOL isCircuitAlive(const LLHost& host) const; + + // MANIPULATORS + LLCircuitData *addCircuitData(const LLHost &host, TPACKETID in_id); + void removeCircuitData(const LLHost &host); + + void updateWatchDogTimers(LLMessageSystem *msgsys); + void resendUnackedPackets(S32& unacked_list_length, S32& unacked_list_size); + + // this method is called during the message system processAcks() + // to send out any acks that did not get sent already. + void sendAcks(); + + friend std::ostream& operator<<(std::ostream& s, LLCircuit &circuit); + const char* getInfoString() const; + + void dumpResends(); + + typedef std::map circuit_data_map; + + /** + * @brief This method gets an iterator range starting after key in + * the circuit data map. + * + * @param key The the host before first. + * @param first[out] The first matching value after key. This + * value will equal end if there are no entries. + * @param end[out] The end of the iteration sequence. + */ + void getCircuitRange( + const LLHost& key, + circuit_data_map::iterator& first, + circuit_data_map::iterator& end); + + // Lists that optimize how many circuits we need to traverse a frame + // HACK - this should become protected eventually, but stupid !@$@# message system/circuit classes are jumbling things up. + circuit_data_map mUnackedCircuitMap; // Map of circuits with unacked data + circuit_data_map mSendAckMap; // Map of circuits which need to send acks +protected: + circuit_data_map mCircuitData; + + typedef std::set ping_set_t; // Circuits sorted by next ping time + ping_set_t mPingSet; + + // This variable points to the last circuit data we found to + // optimize the many, many times we call findCircuit. This may be + // set in otherwise const methods, so it is declared mutable. + mutable LLCircuitData* mLastCircuit; +}; +#endif diff --git a/indra/llmessage/llclassifiedflags.cpp b/indra/llmessage/llclassifiedflags.cpp new file mode 100644 index 0000000000..d84071e2eb --- /dev/null +++ b/indra/llmessage/llclassifiedflags.cpp @@ -0,0 +1,49 @@ +/** + * @file llclassifiedflags.cpp + * @brief + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//***************************************************************************** +// llclassifiedflags.cpp +// +// Some exported symbols and functions for dealing with classified flags. +// +// Copyright 2005, Linden Research, Inc +//***************************************************************************** + +#include "linden_common.h" + +#include "llclassifiedflags.h" + +ClassifiedFlags pack_classified_flags(BOOL is_mature, BOOL auto_renew) +{ + U8 rv = 0; + if(is_mature) rv |= CLASSIFIED_FLAG_MATURE; + if(auto_renew) rv |= CLASSIFIED_FLAG_AUTO_RENEW; + return rv; +} + +bool is_cf_mature(ClassifiedFlags flags) +{ + return ((flags & CLASSIFIED_FLAG_MATURE) != 0); +} + +// Deprecated, but leaving commented out because someday we might +// want to let users enable/disable classifieds. JC +//bool is_cf_enabled(ClassifiedFlags flags) +//{ +// return ((flags & CLASSIFIED_FLAG_ENABLED) == CLASSIFIED_FLAG_ENABLED); +//} + +bool is_cf_update_time(ClassifiedFlags flags) +{ + return ((flags & CLASSIFIED_FLAG_UPDATE_TIME) != 0); +} + +bool is_cf_auto_renew(ClassifiedFlags flags) +{ + return ((flags & CLASSIFIED_FLAG_AUTO_RENEW) != 0); +} diff --git a/indra/llmessage/llclassifiedflags.h b/indra/llmessage/llclassifiedflags.h new file mode 100644 index 0000000000..1949eb54d0 --- /dev/null +++ b/indra/llmessage/llclassifiedflags.h @@ -0,0 +1,31 @@ +/** + * @file llclassifiedflags.h + * @brief Flags used in the classifieds. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLCLASSIFIEDFLAGS_H +#define LL_LLCLASSIFIEDFLAGS_H + +typedef U8 ClassifiedFlags; + +const U8 CLASSIFIED_FLAG_NONE = 1 << 0; +const U8 CLASSIFIED_FLAG_MATURE = 1 << 1; +//const U8 CLASSIFIED_FLAG_ENABLED = 1 << 2; // see llclassifiedflags.cpp +//const U8 CLASSIFIED_FLAG_HAS_PRICE= 1 << 3; // deprecated +const U8 CLASSIFIED_FLAG_UPDATE_TIME= 1 << 4; +const U8 CLASSIFIED_FLAG_AUTO_RENEW = 1 << 5; + +const U8 CLASSIFIED_QUERY_FILTER_MATURE = 1 << 1; +const U8 CLASSIFIED_QUERY_FILTER_ENABLED = 1 << 2; +const U8 CLASSIFIED_QUERY_FILTER_PRICE = 1 << 3; + +ClassifiedFlags pack_classified_flags(BOOL is_mature, BOOL auto_renew); +bool is_cf_mature(ClassifiedFlags flags); +//bool is_cf_enabled(ClassifiedFlags flags); +bool is_cf_update_time(ClassifiedFlags flags); +bool is_cf_auto_renew(ClassifiedFlags flags); + +#endif diff --git a/indra/llmessage/lldatapacker.cpp b/indra/llmessage/lldatapacker.cpp new file mode 100644 index 0000000000..bc9f4f3486 --- /dev/null +++ b/indra/llmessage/lldatapacker.cpp @@ -0,0 +1,1866 @@ +/** + * @file lldatapacker.cpp + * @brief Data packer implementation. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include + +#include "linden_common.h" + +#include "lldatapacker.h" +#include "llerror.h" + +#include "message.h" + +#include "v4color.h" +#include "v4coloru.h" +#include "v2math.h" +#include "v3math.h" +#include "v4math.h" +#include "lluuid.h" + +// *NOTE: there are functions below which use sscanf and rely on this +// particular value of DP_BUFSIZE. Search for '511' (DP_BUFSIZE - 1) +// to find them if you change this number. +const S32 DP_BUFSIZE = 512; + +static char DUMMY_BUFFER[128]; /*Flawfinder: ignore*/ + +LLDataPacker::LLDataPacker() : mPassFlags(0), mWriteEnabled(FALSE) +{ +} + +BOOL LLDataPacker::packFixed(const F32 value, const char *name, + const BOOL is_signed, const U32 int_bits, const U32 frac_bits) +{ + BOOL success = TRUE; + S32 unsigned_bits = int_bits + frac_bits; + S32 total_bits = unsigned_bits; + + if (is_signed) + { + total_bits++; + } + + S32 min_val; + U32 max_val; + if (is_signed) + { + min_val = 1 << int_bits; + min_val *= -1; + } + else + { + min_val = 0; + } + max_val = 1 << int_bits; + + // Clamp to be within range + F32 fixed_val = llclamp(value, (F32)min_val, (F32)max_val); + if (is_signed) + { + fixed_val += max_val; + } + fixed_val *= 1 << frac_bits; + + if (total_bits <= 8) + { + packU8((U8)fixed_val, name); + } + else if (total_bits <= 16) + { + packU16((U16)fixed_val, name); + } + else if (total_bits <= 31) + { + packU32((U32)fixed_val, name); + } + else + { + llerrs << "Using fixed-point packing of " << total_bits << " bits, why?!" << llendl; + } + return success; +} + +BOOL LLDataPacker::unpackFixed(F32 &value, const char *name, + const BOOL is_signed, const U32 int_bits, const U32 frac_bits) +{ + //BOOL success = TRUE; + //llinfos << "unpackFixed:" << name << " int:" << int_bits << " frac:" << frac_bits << llendl; + BOOL ok = FALSE; + S32 unsigned_bits = int_bits + frac_bits; + S32 total_bits = unsigned_bits; + + if (is_signed) + { + total_bits++; + } + + S32 min_val; + U32 max_val; + if (is_signed) + { + min_val = 1 << int_bits; + min_val *= -1; + } + max_val = 1 << int_bits; + + F32 fixed_val; + if (total_bits <= 8) + { + U8 fixed_8; + ok = unpackU8(fixed_8, name); + fixed_val = (F32)fixed_8; + } + else if (total_bits <= 16) + { + U16 fixed_16; + ok = unpackU16(fixed_16, name); + fixed_val = (F32)fixed_16; + } + else if (total_bits <= 31) + { + U32 fixed_32; + ok = unpackU32(fixed_32, name); + fixed_val = (F32)fixed_32; + } + else + { + fixed_val = 0; + llerrs << "Bad bit count: " << total_bits << llendl; + } + + //llinfos << "Fixed_val:" << fixed_val << llendl; + + fixed_val /= (F32)(1 << frac_bits); + if (is_signed) + { + fixed_val -= max_val; + } + value = fixed_val; + //llinfos << "Value: " << value << llendl; + return ok; +} + +//--------------------------------------------------------------------------- +// LLDataPackerBinaryBuffer implementation +//--------------------------------------------------------------------------- + +BOOL LLDataPackerBinaryBuffer::packString(const char *value, const char *name) +{ + BOOL success = TRUE; + S32 length = (S32)strlen(value) + 1; /*Flawfinder: ignore*/ + + success &= verifyLength(length, name); + + if (mWriteEnabled) + { + htonmemcpy(mCurBufferp, value, MVT_VARIABLE, length); + } + mCurBufferp += length; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::unpackString(char *value, const char *name) +{ + BOOL success = TRUE; + S32 length = (S32)strlen((char *)mCurBufferp) + 1; /*Flawfinder: ignore*/ + + success &= verifyLength(length, name); + + htonmemcpy(value, mCurBufferp, MVT_VARIABLE, length); + mCurBufferp += length; + return success; +} + +BOOL LLDataPackerBinaryBuffer::packBinaryData(const U8 *value, S32 size, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(size + 4, name); + + if (mWriteEnabled) + { + htonmemcpy(mCurBufferp, &size, MVT_S32, 4); + } + mCurBufferp += 4; + if (mWriteEnabled) + { + htonmemcpy(mCurBufferp, value, MVT_VARIABLE, size); + } + mCurBufferp += size; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::unpackBinaryData(U8 *value, S32 &size, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(4, name); + htonmemcpy(&size, mCurBufferp, MVT_S32, 4); + mCurBufferp += 4; + success &= verifyLength(size, name); + if (success) + { + htonmemcpy(value, mCurBufferp, MVT_VARIABLE, size); + mCurBufferp += size; + } + else + { + llwarns << "LLDataPackerBinaryBuffer::unpackBinaryData would unpack invalid data, aborting!" << llendl; + success = FALSE; + } + return success; +} + + +BOOL LLDataPackerBinaryBuffer::packBinaryDataFixed(const U8 *value, S32 size, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(size, name); + + if (mWriteEnabled) + { + htonmemcpy(mCurBufferp, value, MVT_VARIABLE, size); + } + mCurBufferp += size; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::unpackBinaryDataFixed(U8 *value, S32 size, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(size, name); + htonmemcpy(value, mCurBufferp, MVT_VARIABLE, size); + mCurBufferp += size; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::packU8(const U8 value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(sizeof(U8), name); + + if (mWriteEnabled) + { + *mCurBufferp = value; + } + mCurBufferp++; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::unpackU8(U8 &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(sizeof(U8), name); + + value = *mCurBufferp; + mCurBufferp++; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::packU16(const U16 value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(sizeof(U16), name); + + if (mWriteEnabled) + { + htonmemcpy(mCurBufferp, &value, MVT_U16, 2); + } + mCurBufferp += 2; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::unpackU16(U16 &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(sizeof(U16), name); + + htonmemcpy(&value, mCurBufferp, MVT_U16, 2); + mCurBufferp += 2; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::packU32(const U32 value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(sizeof(U32), name); + + if (mWriteEnabled) + { + htonmemcpy(mCurBufferp, &value, MVT_U32, 4); + } + mCurBufferp += 4; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::unpackU32(U32 &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(sizeof(U32), name); + + htonmemcpy(&value, mCurBufferp, MVT_U32, 4); + mCurBufferp += 4; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::packS32(const S32 value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(sizeof(S32), name); + + if (mWriteEnabled) + { + htonmemcpy(mCurBufferp, &value, MVT_S32, 4); + } + mCurBufferp += 4; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::unpackS32(S32 &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(sizeof(S32), name); + + htonmemcpy(&value, mCurBufferp, MVT_S32, 4); + mCurBufferp += 4; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::packF32(const F32 value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(sizeof(F32), name); + + if (mWriteEnabled) + { + htonmemcpy(mCurBufferp, &value, MVT_F32, 4); + } + mCurBufferp += 4; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::unpackF32(F32 &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(sizeof(F32), name); + + htonmemcpy(&value, mCurBufferp, MVT_F32, 4); + mCurBufferp += 4; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::packColor4(const LLColor4 &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(16, name); + + if (mWriteEnabled) + { + htonmemcpy(mCurBufferp, value.mV, MVT_LLVector4, 16); + } + mCurBufferp += 16; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::unpackColor4(LLColor4 &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(16, name); + + htonmemcpy(value.mV, mCurBufferp, MVT_LLVector4, 16); + mCurBufferp += 16; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::packColor4U(const LLColor4U &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(4, name); + + if (mWriteEnabled) + { + htonmemcpy(mCurBufferp, value.mV, MVT_VARIABLE, 4); + } + mCurBufferp += 4; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::unpackColor4U(LLColor4U &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(4, name); + + htonmemcpy(value.mV, mCurBufferp, MVT_VARIABLE, 4); + mCurBufferp += 4; + return success; +} + + + +BOOL LLDataPackerBinaryBuffer::packVector2(const LLVector2 &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(8, name); + + if (mWriteEnabled) + { + htonmemcpy(mCurBufferp, &value.mV[0], MVT_F32, 4); + htonmemcpy(mCurBufferp+4, &value.mV[1], MVT_F32, 4); + } + mCurBufferp += 8; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::unpackVector2(LLVector2 &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(8, name); + + htonmemcpy(&value.mV[0], mCurBufferp, MVT_F32, 4); + htonmemcpy(&value.mV[1], mCurBufferp+4, MVT_F32, 4); + mCurBufferp += 8; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::packVector3(const LLVector3 &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(12, name); + + if (mWriteEnabled) + { + htonmemcpy(mCurBufferp, value.mV, MVT_LLVector3, 12); + } + mCurBufferp += 12; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::unpackVector3(LLVector3 &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(12, name); + + htonmemcpy(value.mV, mCurBufferp, MVT_LLVector3, 12); + mCurBufferp += 12; + return success; +} + +BOOL LLDataPackerBinaryBuffer::packVector4(const LLVector4 &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(16, name); + + if (mWriteEnabled) + { + htonmemcpy(mCurBufferp, value.mV, MVT_LLVector4, 16); + } + mCurBufferp += 16; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::unpackVector4(LLVector4 &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(16, name); + + htonmemcpy(value.mV, mCurBufferp, MVT_LLVector4, 16); + mCurBufferp += 16; + return success; +} + +BOOL LLDataPackerBinaryBuffer::packUUID(const LLUUID &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(16, name); + + if (mWriteEnabled) + { + htonmemcpy(mCurBufferp, value.mData, MVT_LLUUID, 16); + } + mCurBufferp += 16; + return success; +} + + +BOOL LLDataPackerBinaryBuffer::unpackUUID(LLUUID &value, const char *name) +{ + BOOL success = TRUE; + success &= verifyLength(16, name); + + htonmemcpy(value.mData, mCurBufferp, MVT_LLUUID, 16); + mCurBufferp += 16; + return success; +} + +const LLDataPackerBinaryBuffer& LLDataPackerBinaryBuffer::operator=(const LLDataPackerBinaryBuffer &a) +{ + if (a.getBufferSize() > getBufferSize()) + { + // We've got problems, ack! + llerrs << "Trying to do an assignment with not enough room in the target." << llendl; + } + memcpy(mBufferp, a.mBufferp, a.getBufferSize()); + return *this; +} + +void LLDataPackerBinaryBuffer::dumpBufferToLog() +{ + llwarns << "Binary Buffer Dump, size: " << mBufferSize << llendl; + char line_buffer[256]; /*Flawfinder: ignore*/ + S32 i; + S32 cur_line_pos = 0; + + S32 cur_line = 0; + for (i = 0; i < mBufferSize; i++) + { + snprintf(line_buffer + cur_line_pos*3, sizeof(line_buffer) - cur_line_pos*3, "%02x ", mBufferp[i]); /*Flawfinder: ignore*/ + cur_line_pos++; + if (cur_line_pos >= 16) + { + cur_line_pos = 0; + llwarns << "Offset:" << std::hex << cur_line*16 << std::dec << " Data:" << line_buffer << llendl; + cur_line++; + } + } + if (cur_line_pos) + { + llwarns << "Offset:" << std::hex << cur_line*16 << std::dec << " Data:" << line_buffer << llendl; + } +} + +//--------------------------------------------------------------------------- +// LLDataPackerAsciiBuffer implementation +//--------------------------------------------------------------------------- +BOOL LLDataPackerAsciiBuffer::packString(const char *value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + int numCopied = 0; + if (mWriteEnabled) + { + numCopied = snprintf(mCurBufferp,getBufferSize()-getCurrentSize(),"%s\n", value); /*Flawfinder: ignore*/ + } + else + { + numCopied = (S32)strlen(value) + 1; /*Flawfinder: ignore*/ + } + + // snprintf returns number of bytes that would have been written + // had the output not being truncated. In that case, it will + // return >= passed in size value. so a check needs to be added + // to detect truncation, and if there is any, only account for the + // actual number of bytes written..and not what could have been + // written. + if (numCopied > getBufferSize()-getCurrentSize()) + { + // *NOTE: I believe we need to mark a failure bit at this point. + numCopied = getBufferSize()-getCurrentSize(); + } + mCurBufferp += numCopied; + return success; +} + +BOOL LLDataPackerAsciiBuffer::unpackString(char *value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /*Flawfinder: ignore*/ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + // XXXCHECK: Can result in buffer overrun. Need to pass in size for "value" + strcpy(value, valuestr); /*Flawfinder: ignore*/ + return success; +} + + +BOOL LLDataPackerAsciiBuffer::packBinaryData(const U8 *value, S32 size, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + + int numCopied = 0; + if (mWriteEnabled) + { + numCopied = snprintf(mCurBufferp,getBufferSize()-getCurrentSize(),"%010d ", size); /*Flawfinder: ignore*/ + + // snprintf returns number of bytes that would have been + // written had the output not being truncated. In that case, + // it will retuen >= passed in size value. so a check needs + // to be added to detect truncation, and if there is any, only + // account for the actual number of bytes written..and not + // what could have been written. + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + } + mCurBufferp += numCopied; + + + S32 i; + BOOL bBufferFull = FALSE; + for (i = 0; i < size && !bBufferFull; i++) + { + numCopied = snprintf(mCurBufferp, getBufferSize()-getCurrentSize(), "%02x ", value[i]); /* Flawfinder: ignore */ + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + bBufferFull = TRUE; + } + mCurBufferp += numCopied; + } + + if (!bBufferFull) + { + numCopied = snprintf(mCurBufferp,getBufferSize()-getCurrentSize(), "\n"); /* Flawfinder: ignore */ + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + } + mCurBufferp += numCopied; + } + } + else + { + // why +10 ?? XXXCHECK + numCopied = 10 + 1; // size plus newline + numCopied += size; + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + } + mCurBufferp += numCopied; + } + + return success; +} + + +BOOL LLDataPackerAsciiBuffer::unpackBinaryData(U8 *value, S32 &size, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /* Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + char *cur_pos = &valuestr[0]; + sscanf(valuestr,"%010d", &size); + cur_pos += 11; + + S32 i; + for (i = 0; i < size; i++) + { + S32 val; + sscanf(cur_pos,"%02x", &val); + value[i] = val; + cur_pos += 3; + } + return success; +} + + +BOOL LLDataPackerAsciiBuffer::packBinaryDataFixed(const U8 *value, S32 size, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + + if (mWriteEnabled) + { + S32 i; + int numCopied = 0; + BOOL bBufferFull = FALSE; + for (i = 0; i < size && !bBufferFull; i++) + { + numCopied = snprintf(mCurBufferp, getBufferSize()-getCurrentSize(), "%02x ", value[i]); /* Flawfinder: ignore */ + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + bBufferFull = TRUE; + } + mCurBufferp += numCopied; + + } + if (!bBufferFull) + { + numCopied = snprintf(mCurBufferp,getBufferSize()-getCurrentSize(), "\n"); /* Flawfinder: ignore */ + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + } + + mCurBufferp += numCopied; + } + } + else + { + int numCopied = 2 * size + 1; //hex bytes plus newline + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + } + mCurBufferp += numCopied; + } + return success; +} + + +BOOL LLDataPackerAsciiBuffer::unpackBinaryDataFixed(U8 *value, S32 size, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /* Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + char *cur_pos = &valuestr[0]; + + S32 i; + for (i = 0; i < size; i++) + { + S32 val; + sscanf(cur_pos,"%02x", &val); + value[i] = val; + cur_pos += 3; + } + return success; +} + + + +BOOL LLDataPackerAsciiBuffer::packU8(const U8 value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + int numCopied = 0; + if (mWriteEnabled) + { + numCopied = snprintf(mCurBufferp,getBufferSize()-getCurrentSize(),"%d\n", value); /*Flawfinder: ignore*/ + } + else + { + // just do the write to a temp buffer to get the length + numCopied = snprintf(DUMMY_BUFFER, sizeof(DUMMY_BUFFER), "%d\n", value); /* Flawfinder: ignore */ + } + + // snprintf returns number of bytes that would have been written had the + // output not being truncated. In that case, it will retuen >= passed in size value. + // so a check needs to be added to detect truncation, and if there is any, + // only account for the actual number of bytes written..and not what could have been written. + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + } + + mCurBufferp += numCopied; + + return success; +} + + +BOOL LLDataPackerAsciiBuffer::unpackU8(U8 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /* Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + S32 in_val; + sscanf(valuestr,"%d", &in_val); + value = in_val; + return success; +} + +BOOL LLDataPackerAsciiBuffer::packU16(const U16 value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + int numCopied = 0; + if (mWriteEnabled) + { + numCopied = snprintf(mCurBufferp,getBufferSize()-getCurrentSize(),"%d\n", value); /*Flawfinder: ignore*/ + } + else + { + numCopied = snprintf(DUMMY_BUFFER, sizeof(DUMMY_BUFFER), "%d\n", value); /* Flawfinder: ignore */ + } + + // snprintf returns number of bytes that would have been written had the + // output not being truncated. In that case, it will retuen >= passed in size value. + // so a check needs to be added to detect truncation, and if there is any, + // only account for the actual number of bytes written..and not what could have been written. + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + } + + mCurBufferp += numCopied; + + return success; +} + + +BOOL LLDataPackerAsciiBuffer::unpackU16(U16 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /* Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + S32 in_val; + sscanf(valuestr,"%d", &in_val); + value = in_val; + return success; +} + + +BOOL LLDataPackerAsciiBuffer::packU32(const U32 value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + int numCopied = 0; + if (mWriteEnabled) + { + numCopied = snprintf(mCurBufferp,getBufferSize()-getCurrentSize(),"%u\n", value); /* Flawfinder: ignore */ + } + else + { + numCopied = snprintf(DUMMY_BUFFER, sizeof(DUMMY_BUFFER), "%u\n", value); /* Flawfinder: ignore */ + } + // snprintf returns number of bytes that would have been written had the + // output not being truncated. In that case, it will retuen >= passed in size value. + // so a check needs to be added to detect truncation, and if there is any, + // only account for the actual number of bytes written..and not what could have been written. + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + } + + mCurBufferp += numCopied; + return success; +} + + +BOOL LLDataPackerAsciiBuffer::unpackU32(U32 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /* Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + sscanf(valuestr,"%u", &value); + return success; +} + + +BOOL LLDataPackerAsciiBuffer::packS32(const S32 value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + int numCopied = 0; + if (mWriteEnabled) + { + numCopied = snprintf(mCurBufferp,getBufferSize()-getCurrentSize(),"%d\n", value); /* Flawfinder: ignore */ + } + else + { + numCopied = snprintf(DUMMY_BUFFER, sizeof(DUMMY_BUFFER), "%d\n", value); /* Flawfinder: ignore */ + } + // snprintf returns number of bytes that would have been written had the + // output not being truncated. In that case, it will retuen >= passed in size value. + // so a check needs to be added to detect truncation, and if there is any, + // only account for the actual number of bytes written..and not what could have been written. + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + } + + mCurBufferp += numCopied; + return success; +} + + +BOOL LLDataPackerAsciiBuffer::unpackS32(S32 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /* Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + sscanf(valuestr,"%d", &value); + return success; +} + + +BOOL LLDataPackerAsciiBuffer::packF32(const F32 value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + int numCopied = 0; + if (mWriteEnabled) + { + numCopied = snprintf(mCurBufferp,getBufferSize()-getCurrentSize(),"%g\n", value); /* Flawfinder: ignore */ + } + else + { + numCopied = snprintf(DUMMY_BUFFER, sizeof(DUMMY_BUFFER), "%g\n", value); /* Flawfinder: ignore */ + } + // snprintf returns number of bytes that would have been written had the + // output not being truncated. In that case, it will retuen >= passed in size value. + // so a check needs to be added to detect truncation, and if there is any, + // only account for the actual number of bytes written..and not what could have been written. + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + } + + mCurBufferp += numCopied; + return success; +} + + +BOOL LLDataPackerAsciiBuffer::unpackF32(F32 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /* Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + sscanf(valuestr,"%g", &value); + return success; +} + + +BOOL LLDataPackerAsciiBuffer::packColor4(const LLColor4 &value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + int numCopied = 0; + if (mWriteEnabled) + { + numCopied = snprintf(mCurBufferp,getBufferSize()-getCurrentSize(),"%g %g %g %g\n", value.mV[0], value.mV[1], value.mV[2], value.mV[3]); /* Flawfinder: ignore */ + } + else + { + numCopied = snprintf(DUMMY_BUFFER,sizeof(DUMMY_BUFFER),"%g %g %g %g\n", value.mV[0], value.mV[1], value.mV[2], value.mV[3]); /* Flawfinder: ignore */ + } + // snprintf returns number of bytes that would have been written had the + // output not being truncated. In that case, it will retuen >= passed in size value. + // so a check needs to be added to detect truncation, and if there is any, + // only account for the actual number of bytes written..and not what could have been written. + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + } + + mCurBufferp += numCopied; + return success; +} + + +BOOL LLDataPackerAsciiBuffer::unpackColor4(LLColor4 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /* Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + sscanf(valuestr,"%g %g %g %g", &value.mV[0], &value.mV[1], &value.mV[2], &value.mV[3]); + return success; +} + +BOOL LLDataPackerAsciiBuffer::packColor4U(const LLColor4U &value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + int numCopied = 0; + if (mWriteEnabled) + { + numCopied = snprintf(mCurBufferp,getBufferSize()-getCurrentSize(),"%d %d %d %d\n", value.mV[0], value.mV[1], value.mV[2], value.mV[3]); /* Flawfinder: ignore */ + } + else + { + numCopied = snprintf(DUMMY_BUFFER,sizeof(DUMMY_BUFFER),"%d %d %d %d\n", value.mV[0], value.mV[1], value.mV[2], value.mV[3]); /* Flawfinder: ignore */ + } + // snprintf returns number of bytes that would have been written had the + // output not being truncated. In that case, it will retuen >= passed in size value. + // so a check needs to be added to detect truncation, and if there is any, + // only account for the actual number of bytes written..and not what could have been written. + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + } + + mCurBufferp += numCopied; + return success; +} + + +BOOL LLDataPackerAsciiBuffer::unpackColor4U(LLColor4U &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /* Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + S32 r, g, b, a; + + sscanf(valuestr,"%d %d %d %d", &r, &g, &b, &a); + value.mV[0] = r; + value.mV[1] = g; + value.mV[2] = b; + value.mV[3] = a; + return success; +} + + +BOOL LLDataPackerAsciiBuffer::packVector2(const LLVector2 &value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + int numCopied = 0; + if (mWriteEnabled) + { + numCopied = snprintf(mCurBufferp,getBufferSize()-getCurrentSize(),"%g %g\n", value.mV[0], value.mV[1]); /* Flawfinder: ignore */ + } + else + { + numCopied = snprintf(DUMMY_BUFFER,sizeof(DUMMY_BUFFER),"%g %g\n", value.mV[0], value.mV[1]); /* Flawfinder: ignore */ + } + // snprintf returns number of bytes that would have been written had the + // output not being truncated. In that case, it will retuen >= passed in size value. + // so a check needs to be added to detect truncation, and if there is any, + // only account for the actual number of bytes written..and not what could have been written. + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + } + + mCurBufferp += numCopied; + return success; +} + + +BOOL LLDataPackerAsciiBuffer::unpackVector2(LLVector2 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /* Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + sscanf(valuestr,"%g %g", &value.mV[0], &value.mV[1]); + return success; +} + + +BOOL LLDataPackerAsciiBuffer::packVector3(const LLVector3 &value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + int numCopied = 0; + if (mWriteEnabled) + { + numCopied = snprintf(mCurBufferp,getBufferSize()-getCurrentSize(),"%g %g %g\n", value.mV[0], value.mV[1], value.mV[2]); /* Flawfinder: ignore */ + } + else + { + numCopied = snprintf(DUMMY_BUFFER,sizeof(DUMMY_BUFFER),"%g %g %g\n", value.mV[0], value.mV[1], value.mV[2]); /* Flawfinder: ignore */ + } + // snprintf returns number of bytes that would have been written had the + // output not being truncated. In that case, it will retuen >= passed in size value. + // so a check needs to be added to detect truncation, and if there is any, + // only account for the actual number of bytes written..and not what could have been written. + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + } + + mCurBufferp += numCopied; + return success; +} + + +BOOL LLDataPackerAsciiBuffer::unpackVector3(LLVector3 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /* Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + sscanf(valuestr,"%g %g %g", &value.mV[0], &value.mV[1], &value.mV[2]); + return success; +} + +BOOL LLDataPackerAsciiBuffer::packVector4(const LLVector4 &value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + int numCopied = 0; + if (mWriteEnabled) + { + numCopied = snprintf(mCurBufferp,getBufferSize()-getCurrentSize(),"%g %g %g %g\n", value.mV[0], value.mV[1], value.mV[2], value.mV[3]); /* Flawfinder: ignore */ + } + else + { + numCopied = snprintf(DUMMY_BUFFER,sizeof(DUMMY_BUFFER),"%g %g %g %g\n", value.mV[0], value.mV[1], value.mV[2], value.mV[3]); /* Flawfinder: ignore */ + } + // snprintf returns number of bytes that would have been written had the + // output not being truncated. In that case, it will retuen >= passed in size value. + // so a check needs to be added to detect truncation, and if there is any, + // only account for the actual number of bytes written..and not what could have been written. + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + } + + mCurBufferp += numCopied; + return success; +} + + +BOOL LLDataPackerAsciiBuffer::unpackVector4(LLVector4 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /* Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + sscanf(valuestr,"%g %g %g %g", &value.mV[0], &value.mV[1], &value.mV[2], &value.mV[3]); + return success; +} + + +BOOL LLDataPackerAsciiBuffer::packUUID(const LLUUID &value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + + int numCopied = 0; + if (mWriteEnabled) + { + char tmp_str[64]; /* Flawfinder: ignore */ + value.toString(tmp_str); + numCopied = snprintf(mCurBufferp,getBufferSize()-getCurrentSize(),"%s\n", tmp_str); /* Flawfinder: ignore */ + } + else + { + numCopied = 64 + 1; // UUID + newline + } + // snprintf returns number of bytes that would have been written had the + // output not being truncated. In that case, it will retuen >= passed in size value. + // so a check needs to be added to detect truncation, and if there is any, + // only account for the actual number of bytes written..and not what could have been written. + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + success = FALSE; + } + mCurBufferp += numCopied; + return success; +} + + +BOOL LLDataPackerAsciiBuffer::unpackUUID(LLUUID &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /* Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + char tmp_str[64]; /* Flawfinder: ignore */ + sscanf(valuestr, "%63s", tmp_str); + value.set(tmp_str); + + return success; +} + +void LLDataPackerAsciiBuffer::dump() +{ + llinfos << "Buffer: " << mBufferp << llendl; +} + +void LLDataPackerAsciiBuffer::writeIndentedName(const char *name) +{ + if (mIncludeNames) + { + int numCopied = 0; + if (mWriteEnabled) + { + numCopied = snprintf(mCurBufferp,getBufferSize()-getCurrentSize(),"%s\t", name); /* Flawfinder: ignore */ + } + else + { + numCopied = (S32)strlen(name) + 1; //name + tab /* Flawfinder: ignore */ + } + + // snprintf returns number of bytes that would have been written had the + // output not being truncated. In that case, it will retuen >= passed in size value. + // so a check needs to be added to detect truncation, and if there is any, + // only account for the actual number of bytes written..and not what could have been written. + if (numCopied > getBufferSize()-getCurrentSize()) + { + numCopied = getBufferSize()-getCurrentSize(); + } + } +} + +BOOL LLDataPackerAsciiBuffer::getValueStr(const char *name, char *out_value, S32 value_len) +{ + BOOL success = TRUE; + char buffer[DP_BUFSIZE]; /* Flawfinder: ignore */ + char keyword[DP_BUFSIZE]; /* Flawfinder: ignore */ + char value[DP_BUFSIZE]; /* Flawfinder: ignore */ + + buffer[0] = '\0'; + keyword[0] = '\0'; + value[0] = '\0'; + + if (mIncludeNames) + { + // Read both the name and the value, and validate the name. + sscanf(mCurBufferp, "%511[^\n]", buffer); + // Skip the \n + mCurBufferp += (S32)strlen(buffer) + 1; + + sscanf(buffer, "%511s %511[^\n]", keyword, value); + + if (strcmp(keyword, name)) + { + llwarns << "Data packer expecting keyword of type " << name << ", got " << keyword << " instead!" << llendl; + return FALSE; + } + } + else + { + // Just the value exists + sscanf(mCurBufferp, "%511[^\n]", value); + // Skip the \n + mCurBufferp += (S32)strlen(value) + 1; /* Flawfinder: ignore */ + } + + S32 in_value_len = (S32)strlen(value)+1; /* Flawfinder: ignore */ + S32 min_len = llmin(in_value_len, value_len); + memcpy(out_value, value, min_len); /* Flawfinder: ignore */ + out_value[min_len-1] = 0; + + return success; +} + +//--------------------------------------------------------------------------- +// LLDataPackerAsciiFile implementation +//--------------------------------------------------------------------------- +BOOL LLDataPackerAsciiFile::packString(const char *value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + if (mFP) + { + fprintf(mFP,"%s\n", value); + } + else if (mOutputStream) + { + *mOutputStream << value << "\n"; + } + return success; +} + +BOOL LLDataPackerAsciiFile::unpackString(char *value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /* Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + strncpy(value, valuestr,DP_BUFSIZE); + return success; +} + + +BOOL LLDataPackerAsciiFile::packBinaryData(const U8 *value, S32 size, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + + if (mFP) + { + fprintf(mFP, "%010d ", size); + + S32 i; + for (i = 0; i < size; i++) + { + fprintf(mFP, "%02x ", value[i]); + } + fprintf(mFP, "\n"); + } + else if (mOutputStream) + { + char buffer[32]; /* Flawfinder: ignore */ + snprintf(buffer,sizeof(buffer), "%010d ", size); /* Flawfinder: ignore */ + *mOutputStream << buffer; + + S32 i; + for (i = 0; i < size; i++) + { + snprintf(buffer, sizeof(buffer), "%02x ", value[i]); /* Flawfinder: ignore */ + *mOutputStream << buffer; + } + *mOutputStream << "\n"; + } + return success; +} + + +BOOL LLDataPackerAsciiFile::unpackBinaryData(U8 *value, S32 &size, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /*Flawfinder: ignore*/ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + char *cur_pos = &valuestr[0]; + sscanf(valuestr,"%010d", &size); + cur_pos += 11; + + S32 i; + for (i = 0; i < size; i++) + { + S32 val; + sscanf(cur_pos,"%02x", &val); + value[i] = val; + cur_pos += 3; + } + return success; +} + + +BOOL LLDataPackerAsciiFile::packBinaryDataFixed(const U8 *value, S32 size, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + + if (mFP) + { + S32 i; + for (i = 0; i < size; i++) + { + fprintf(mFP, "%02x ", value[i]); + } + fprintf(mFP, "\n"); + } + else if (mOutputStream) + { + char buffer[32]; /*Flawfinder: ignore*/ + S32 i; + for (i = 0; i < size; i++) + { + snprintf(buffer, sizeof(buffer), "%02x ", value[i]); /*Flawfinder: ignore*/ + *mOutputStream << buffer; + } + *mOutputStream << "\n"; + } + return success; +} + + +BOOL LLDataPackerAsciiFile::unpackBinaryDataFixed(U8 *value, S32 size, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /*Flawfinder: ignore*/ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + char *cur_pos = &valuestr[0]; + + S32 i; + for (i = 0; i < size; i++) + { + S32 val; + sscanf(cur_pos,"%02x", &val); + value[i] = val; + cur_pos += 3; + } + return success; +} + + + +BOOL LLDataPackerAsciiFile::packU8(const U8 value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + if (mFP) + { + fprintf(mFP,"%d\n", value); + } + else if (mOutputStream) + { + // We have to cast this to an integer because streams serialize + // bytes as bytes - not as text. + *mOutputStream << (S32)value << "\n"; + } + return success; +} + + +BOOL LLDataPackerAsciiFile::unpackU8(U8 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /*Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + S32 in_val; + sscanf(valuestr,"%d", &in_val); + value = in_val; + return success; +} + +BOOL LLDataPackerAsciiFile::packU16(const U16 value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + if (mFP) + { + fprintf(mFP,"%d\n", value); + } + else if (mOutputStream) + { + *mOutputStream <<"" << value << "\n"; + } + return success; +} + + +BOOL LLDataPackerAsciiFile::unpackU16(U16 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /*Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + S32 in_val; + sscanf(valuestr,"%d", &in_val); + value = in_val; + return success; +} + + +BOOL LLDataPackerAsciiFile::packU32(const U32 value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + if (mFP) + { + fprintf(mFP,"%u\n", value); + } + else if (mOutputStream) + { + *mOutputStream <<"" << value << "\n"; + } + return success; +} + + +BOOL LLDataPackerAsciiFile::unpackU32(U32 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /*Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + sscanf(valuestr,"%u", &value); + return success; +} + + +BOOL LLDataPackerAsciiFile::packS32(const S32 value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + if (mFP) + { + fprintf(mFP,"%d\n", value); + } + else if (mOutputStream) + { + *mOutputStream <<"" << value << "\n"; + } + return success; +} + + +BOOL LLDataPackerAsciiFile::unpackS32(S32 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /*Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + sscanf(valuestr,"%d", &value); + return success; +} + + +BOOL LLDataPackerAsciiFile::packF32(const F32 value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + if (mFP) + { + fprintf(mFP,"%g\n", value); + } + else if (mOutputStream) + { + *mOutputStream <<"" << value << "\n"; + } + return success; +} + + +BOOL LLDataPackerAsciiFile::unpackF32(F32 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /*Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + sscanf(valuestr,"%g", &value); + return success; +} + + +BOOL LLDataPackerAsciiFile::packColor4(const LLColor4 &value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + if (mFP) + { + fprintf(mFP,"%g %g %g %g\n", value.mV[0], value.mV[1], value.mV[2], value.mV[3]); + } + else if (mOutputStream) + { + *mOutputStream << value.mV[0] << " " << value.mV[1] << " " << value.mV[2] << " " << value.mV[3] << "\n"; + } + return success; +} + + +BOOL LLDataPackerAsciiFile::unpackColor4(LLColor4 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /*Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + sscanf(valuestr,"%g %g %g %g", &value.mV[0], &value.mV[1], &value.mV[2], &value.mV[3]); + return success; +} + +BOOL LLDataPackerAsciiFile::packColor4U(const LLColor4U &value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + if (mFP) + { + fprintf(mFP,"%d %d %d %d\n", value.mV[0], value.mV[1], value.mV[2], value.mV[3]); + } + else if (mOutputStream) + { + *mOutputStream << (S32)(value.mV[0]) << " " << (S32)(value.mV[1]) << " " << (S32)(value.mV[2]) << " " << (S32)(value.mV[3]) << "\n"; + } + return success; +} + + +BOOL LLDataPackerAsciiFile::unpackColor4U(LLColor4U &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /*Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + S32 r, g, b, a; + + sscanf(valuestr,"%d %d %d %d", &r, &g, &b, &a); + value.mV[0] = r; + value.mV[1] = g; + value.mV[2] = b; + value.mV[3] = a; + return success; +} + + +BOOL LLDataPackerAsciiFile::packVector2(const LLVector2 &value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + if (mFP) + { + fprintf(mFP,"%g %g\n", value.mV[0], value.mV[1]); + } + else if (mOutputStream) + { + *mOutputStream << value.mV[0] << " " << value.mV[1] << "\n"; + } + return success; +} + + +BOOL LLDataPackerAsciiFile::unpackVector2(LLVector2 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /*Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + sscanf(valuestr,"%g %g", &value.mV[0], &value.mV[1]); + return success; +} + + +BOOL LLDataPackerAsciiFile::packVector3(const LLVector3 &value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + if (mFP) + { + fprintf(mFP,"%g %g %g\n", value.mV[0], value.mV[1], value.mV[2]); + } + else if (mOutputStream) + { + *mOutputStream << value.mV[0] << " " << value.mV[1] << " " << value.mV[2] << "\n"; + } + return success; +} + + +BOOL LLDataPackerAsciiFile::unpackVector3(LLVector3 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /*Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + sscanf(valuestr,"%g %g %g", &value.mV[0], &value.mV[1], &value.mV[2]); + return success; +} + +BOOL LLDataPackerAsciiFile::packVector4(const LLVector4 &value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + if (mFP) + { + fprintf(mFP,"%g %g %g %g\n", value.mV[0], value.mV[1], value.mV[2], value.mV[3]); + } + else if (mOutputStream) + { + *mOutputStream << value.mV[0] << " " << value.mV[1] << " " << value.mV[2] << " " << value.mV[3] << "\n"; + } + return success; +} + + +BOOL LLDataPackerAsciiFile::unpackVector4(LLVector4 &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /*Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + sscanf(valuestr,"%g %g %g %g", &value.mV[0], &value.mV[1], &value.mV[2], &value.mV[3]); + return success; +} + + +BOOL LLDataPackerAsciiFile::packUUID(const LLUUID &value, const char *name) +{ + BOOL success = TRUE; + writeIndentedName(name); + char tmp_str[64]; /*Flawfinder: ignore */ + value.toString(tmp_str); + if (mFP) + { + fprintf(mFP,"%s\n", tmp_str); + } + else if (mOutputStream) + { + *mOutputStream <<"" << tmp_str << "\n"; + } + return success; +} + + +BOOL LLDataPackerAsciiFile::unpackUUID(LLUUID &value, const char *name) +{ + BOOL success = TRUE; + char valuestr[DP_BUFSIZE]; /*Flawfinder: ignore */ + if (!getValueStr(name, valuestr, DP_BUFSIZE)) + { + return FALSE; + } + + char tmp_str[64]; /*Flawfinder: ignore */ + sscanf(valuestr,"%63s",tmp_str); + value.set(tmp_str); + + return success; +} + + +void LLDataPackerAsciiFile::writeIndentedName(const char *name) +{ + char indent_buf[64]; /*Flawfinder: ignore*/ + + S32 i; + for(i = 0; i < mIndent; i++) + { + indent_buf[i] = '\t'; + } + indent_buf[i] = 0; + if (mFP) + { + fprintf(mFP,"%s%s\t",indent_buf, name); + } + else if (mOutputStream) + { + *mOutputStream << indent_buf << name << "\t"; + } +} + +BOOL LLDataPackerAsciiFile::getValueStr(const char *name, char *out_value, S32 value_len) +{ + BOOL success = FALSE; + char buffer[DP_BUFSIZE]; /*Flawfinder: ignore*/ + char keyword[DP_BUFSIZE]; /*Flawfinder: ignore*/ + char value[DP_BUFSIZE]; /*Flawfinder: ignore*/ + + buffer[0] = '\0'; + keyword[0] = '\0'; + value[0] = '\0'; + + if (mFP) + { + fpos_t last_pos; + fgetpos(mFP, &last_pos); + fgets(buffer, DP_BUFSIZE, mFP); + + sscanf(buffer, "%511s %511[^\n]", keyword, value); + + if (!keyword[0]) + { + llwarns << "Data packer could not get the keyword!" << llendl; + fsetpos(mFP, &last_pos); + return FALSE; + } + if (strcmp(keyword, name)) + { + llwarns << "Data packer expecting keyword of type " << name << ", got " << keyword << " instead!" << llendl; + fsetpos(mFP, &last_pos); + return FALSE; + } + + S32 in_value_len = (S32)strlen(value)+1; /*Flawfinder: ignore*/ + S32 min_len = llmin(in_value_len, value_len); + memcpy(out_value, value, min_len); /*Flawfinder: ignore*/ + out_value[min_len-1] = 0; + success = TRUE; + } + else if (mInputStream) + { + mInputStream->getline(buffer, DP_BUFSIZE); + + sscanf(buffer, "%511s %511[^\n]", keyword, value); + if (!keyword[0]) + { + llwarns << "Data packer could not get the keyword!" << llendl; + return FALSE; + } + if (strcmp(keyword, name)) + { + llwarns << "Data packer expecting keyword of type " << name << ", got " << keyword << " instead!" << llendl; + return FALSE; + } + + S32 in_value_len = (S32)strlen(value)+1; /*Flawfinder: ignore*/ + S32 min_len = llmin(in_value_len, value_len); + memcpy(out_value, value, min_len); /*Flawfinder: ignore*/ + out_value[min_len-1] = 0; + success = TRUE; + } + + return success; +} diff --git a/indra/llmessage/lldatapacker.h b/indra/llmessage/lldatapacker.h new file mode 100644 index 0000000000..10ca35d2c7 --- /dev/null +++ b/indra/llmessage/lldatapacker.h @@ -0,0 +1,398 @@ +/** + * @file lldatapacker.h + * @brief Data packer declaration for tightly storing binary data. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLDATAPACKER_H +#define LL_LLDATAPACKER_H + +#include +#include + +#include "llerror.h" + +class LLColor4; +class LLColor4U; +class LLVector2; +class LLVector3; +class LLVector4; +class LLUUID; + +class LLDataPacker +{ +public: + virtual ~LLDataPacker() {} + + virtual void reset() { llerrs << "Using unimplemented datapacker reset!" << llendl; }; + virtual void dumpBufferToLog() { llerrs << "dumpBufferToLog not implemented for this type!" << llendl; } + + virtual BOOL hasNext() const = 0; + + virtual BOOL packString(const char *value, const char *name) = 0; + virtual BOOL unpackString(char *value, const char *name) = 0; + + virtual BOOL packBinaryData(const U8 *value, S32 size, const char *name) = 0; + virtual BOOL unpackBinaryData(U8 *value, S32 &size, const char *name) = 0; + + // Constant size binary data packing + virtual BOOL packBinaryDataFixed(const U8 *value, S32 size, const char *name) = 0; + virtual BOOL unpackBinaryDataFixed(U8 *value, S32 size, const char *name) = 0; + + virtual BOOL packU8(const U8 value, const char *name) = 0; + virtual BOOL unpackU8(U8 &value, const char *name) = 0; + + virtual BOOL packU16(const U16 value, const char *name) = 0; + virtual BOOL unpackU16(U16 &value, const char *name) = 0; + + virtual BOOL packU32(const U32 value, const char *name) = 0; + virtual BOOL unpackU32(U32 &value, const char *name) = 0; + + virtual BOOL packS32(const S32 value, const char *name) = 0; + virtual BOOL unpackS32(S32 &value, const char *name) = 0; + + virtual BOOL packF32(const F32 value, const char *name) = 0; + virtual BOOL unpackF32(F32 &value, const char *name) = 0; + + // Packs a float into an integer, using the given size + // and picks the right U* data type to pack into. + BOOL packFixed(const F32 value, const char *name, + const BOOL is_signed, const U32 int_bits, const U32 frac_bits); + BOOL unpackFixed(F32 &value, const char *name, + const BOOL is_signed, const U32 int_bits, const U32 frac_bits); + + virtual BOOL packColor4(const LLColor4 &value, const char *name) = 0; + virtual BOOL unpackColor4(LLColor4 &value, const char *name) = 0; + + virtual BOOL packColor4U(const LLColor4U &value, const char *name) = 0; + virtual BOOL unpackColor4U(LLColor4U &value, const char *name) = 0; + + virtual BOOL packVector2(const LLVector2 &value, const char *name) = 0; + virtual BOOL unpackVector2(LLVector2 &value, const char *name) = 0; + + virtual BOOL packVector3(const LLVector3 &value, const char *name) = 0; + virtual BOOL unpackVector3(LLVector3 &value, const char *name) = 0; + + virtual BOOL packVector4(const LLVector4 &value, const char *name) = 0; + virtual BOOL unpackVector4(LLVector4 &value, const char *name) = 0; + + virtual BOOL packUUID(const LLUUID &value, const char *name) = 0; + virtual BOOL unpackUUID(LLUUID &value, const char *name) = 0; + U32 getPassFlags() const { return mPassFlags; } + void setPassFlags(U32 flags) { mPassFlags = flags; } +protected: + LLDataPacker(); +protected: + U32 mPassFlags; + BOOL mWriteEnabled; // disable this to do things like determine filesize without actually copying data +}; + +class LLDataPackerBinaryBuffer : public LLDataPacker +{ +public: + LLDataPackerBinaryBuffer(U8 *bufferp, S32 size) + : LLDataPacker(), + mBufferp(bufferp), + mCurBufferp(bufferp), + mBufferSize(size) + { + mWriteEnabled = TRUE; + } + + LLDataPackerBinaryBuffer() + : LLDataPacker(), + mBufferp(NULL), + mCurBufferp(NULL), + mBufferSize(0) + { + } + + /*virtual*/ BOOL packString(const char *value, const char *name); + /*virtual*/ BOOL unpackString(char *value, const char *name); + + /*virtual*/ BOOL packBinaryData(const U8 *value, S32 size, const char *name); + /*virtual*/ BOOL unpackBinaryData(U8 *value, S32 &size, const char *name); + + // Constant size binary data packing + /*virtual*/ BOOL packBinaryDataFixed(const U8 *value, S32 size, const char *name); + /*virtual*/ BOOL unpackBinaryDataFixed(U8 *value, S32 size, const char *name); + + /*virtual*/ BOOL packU8(const U8 value, const char *name); + /*virtual*/ BOOL unpackU8(U8 &value, const char *name); + + /*virtual*/ BOOL packU16(const U16 value, const char *name); + /*virtual*/ BOOL unpackU16(U16 &value, const char *name); + + /*virtual*/ BOOL packU32(const U32 value, const char *name); + /*virtual*/ BOOL unpackU32(U32 &value, const char *name); + + /*virtual*/ BOOL packS32(const S32 value, const char *name); + /*virtual*/ BOOL unpackS32(S32 &value, const char *name); + + /*virtual*/ BOOL packF32(const F32 value, const char *name); + /*virtual*/ BOOL unpackF32(F32 &value, const char *name); + + /*virtual*/ BOOL packColor4(const LLColor4 &value, const char *name); + /*virtual*/ BOOL unpackColor4(LLColor4 &value, const char *name); + + /*virtual*/ BOOL packColor4U(const LLColor4U &value, const char *name); + /*virtual*/ BOOL unpackColor4U(LLColor4U &value, const char *name); + + /*virtual*/ BOOL packVector2(const LLVector2 &value, const char *name); + /*virtual*/ BOOL unpackVector2(LLVector2 &value, const char *name); + + /*virtual*/ BOOL packVector3(const LLVector3 &value, const char *name); + /*virtual*/ BOOL unpackVector3(LLVector3 &value, const char *name); + + /*virtual*/ BOOL packVector4(const LLVector4 &value, const char *name); + /*virtual*/ BOOL unpackVector4(LLVector4 &value, const char *name); + + /*virtual*/ BOOL packUUID(const LLUUID &value, const char *name); + /*virtual*/ BOOL unpackUUID(LLUUID &value, const char *name); + + S32 getCurrentSize() const { return (S32)(mCurBufferp - mBufferp); } + S32 getBufferSize() const { return mBufferSize; } + void reset() { mCurBufferp = mBufferp; mWriteEnabled = (mCurBufferp != NULL); } + void freeBuffer() { delete [] mBufferp; mBufferp = mCurBufferp = NULL; mBufferSize = 0; mWriteEnabled = FALSE; } + void assignBuffer(U8 *bufferp, S32 size) + { + mBufferp = bufferp; + mCurBufferp = bufferp; + mBufferSize = size; + mWriteEnabled = TRUE; + } + const LLDataPackerBinaryBuffer& operator=(const LLDataPackerBinaryBuffer &a); + + /*virtual*/ BOOL hasNext() const { return getCurrentSize() < getBufferSize(); } + + /*virtual*/ void dumpBufferToLog(); +protected: + inline BOOL verifyLength(const S32 data_size, const char *name); + + U8 *mBufferp; + U8 *mCurBufferp; + S32 mBufferSize; +}; + +inline BOOL LLDataPackerBinaryBuffer::verifyLength(const S32 data_size, const char *name) +{ + if (mWriteEnabled && (mCurBufferp - mBufferp) > mBufferSize - data_size) + { + llwarns << "Buffer overflow in BinaryBuffer length verify, field name " << name << "!" << llendl; + llwarns << "Current pos: " << (int)(mCurBufferp - mBufferp) << " Buffer size: " << mBufferSize << " Data size: " << data_size << llendl; + return FALSE; + } + + return TRUE; +} + +class LLDataPackerAsciiBuffer : public LLDataPacker +{ +public: + LLDataPackerAsciiBuffer(char* bufferp, S32 size) + { + mBufferp = bufferp; + mCurBufferp = bufferp; + mBufferSize = size; + mPassFlags = 0; + mIncludeNames = FALSE; + mWriteEnabled = TRUE; + } + + LLDataPackerAsciiBuffer() + { + mBufferp = NULL; + mCurBufferp = NULL; + mBufferSize = 0; + mPassFlags = 0; + mIncludeNames = FALSE; + mWriteEnabled = FALSE; + } + + /*virtual*/ BOOL packString(const char *value, const char *name); + /*virtual*/ BOOL unpackString(char *value, const char *name); + + /*virtual*/ BOOL packBinaryData(const U8 *value, S32 size, const char *name); + /*virtual*/ BOOL unpackBinaryData(U8 *value, S32 &size, const char *name); + + // Constant size binary data packing + /*virtual*/ BOOL packBinaryDataFixed(const U8 *value, S32 size, const char *name); + /*virtual*/ BOOL unpackBinaryDataFixed(U8 *value, S32 size, const char *name); + + /*virtual*/ BOOL packU8(const U8 value, const char *name); + /*virtual*/ BOOL unpackU8(U8 &value, const char *name); + + /*virtual*/ BOOL packU16(const U16 value, const char *name); + /*virtual*/ BOOL unpackU16(U16 &value, const char *name); + + /*virtual*/ BOOL packU32(const U32 value, const char *name); + /*virtual*/ BOOL unpackU32(U32 &value, const char *name); + + /*virtual*/ BOOL packS32(const S32 value, const char *name); + /*virtual*/ BOOL unpackS32(S32 &value, const char *name); + + /*virtual*/ BOOL packF32(const F32 value, const char *name); + /*virtual*/ BOOL unpackF32(F32 &value, const char *name); + + /*virtual*/ BOOL packColor4(const LLColor4 &value, const char *name); + /*virtual*/ BOOL unpackColor4(LLColor4 &value, const char *name); + + /*virtual*/ BOOL packColor4U(const LLColor4U &value, const char *name); + /*virtual*/ BOOL unpackColor4U(LLColor4U &value, const char *name); + + /*virtual*/ BOOL packVector2(const LLVector2 &value, const char *name); + /*virtual*/ BOOL unpackVector2(LLVector2 &value, const char *name); + + /*virtual*/ BOOL packVector3(const LLVector3 &value, const char *name); + /*virtual*/ BOOL unpackVector3(LLVector3 &value, const char *name); + + /*virtual*/ BOOL packVector4(const LLVector4 &value, const char *name); + /*virtual*/ BOOL unpackVector4(LLVector4 &value, const char *name); + + /*virtual*/ BOOL packUUID(const LLUUID &value, const char *name); + /*virtual*/ BOOL unpackUUID(LLUUID &value, const char *name); + + void setIncludeNames(BOOL b) { mIncludeNames = b; } + + // Include the trailing NULL so it's always a valid string + S32 getCurrentSize() const { return (S32)(mCurBufferp - mBufferp) + 1; } + + S32 getBufferSize() const { return mBufferSize; } + /*virtual*/ void reset() { mCurBufferp = mBufferp; mWriteEnabled = (mCurBufferp != NULL); } + + /*virtual*/ BOOL hasNext() const { return getCurrentSize() < getBufferSize(); } + + inline void freeBuffer(); + inline void assignBuffer(char* bufferp, S32 size); + void dump(); + +protected: + void writeIndentedName(const char *name); + BOOL getValueStr(const char *name, char *out_value, const S32 value_len); + +protected: + inline BOOL verifyLength(const S32 data_size, const char *name); + + char *mBufferp; + char *mCurBufferp; + S32 mBufferSize; + BOOL mIncludeNames; // useful for debugging, print the name of each field +}; + +inline void LLDataPackerAsciiBuffer::freeBuffer() +{ + delete [] mBufferp; + mBufferp = mCurBufferp = NULL; + mBufferSize = 0; + mWriteEnabled = FALSE; +} + +inline void LLDataPackerAsciiBuffer::assignBuffer(char* bufferp, S32 size) +{ + mBufferp = bufferp; + mCurBufferp = bufferp; + mBufferSize = size; + mWriteEnabled = TRUE; +} + +inline BOOL LLDataPackerAsciiBuffer::verifyLength(const S32 data_size, const char *name) +{ + if (mWriteEnabled && (mCurBufferp - mBufferp) > mBufferSize - data_size) + { + llwarns << "Buffer overflow in AsciiBuffer length verify, field name " << name << "!" << llendl; + llwarns << "Current pos: " << (int)(mCurBufferp - mBufferp) << " Buffer size: " << mBufferSize << " Data size: " << data_size << llendl; + return FALSE; + } + + return TRUE; +} + +class LLDataPackerAsciiFile : public LLDataPacker +{ +public: + LLDataPackerAsciiFile(FILE *fp, const S32 indent = 2) + : LLDataPacker(), + mIndent(indent), + mFP(fp), + mOutputStream(NULL), + mInputStream(NULL) + { + } + + LLDataPackerAsciiFile(std::ostream& output_stream, const S32 indent = 2) + : LLDataPacker(), + mIndent(indent), + mFP(NULL), + mOutputStream(&output_stream), + mInputStream(NULL) + { + mWriteEnabled = TRUE; + } + + LLDataPackerAsciiFile(std::istream& input_stream, const S32 indent = 2) + : LLDataPacker(), + mIndent(indent), + mFP(NULL), + mOutputStream(NULL), + mInputStream(&input_stream) + { + } + + /*virtual*/ BOOL packString(const char *value, const char *name); + /*virtual*/ BOOL unpackString(char *value, const char *name); + + /*virtual*/ BOOL packBinaryData(const U8 *value, S32 size, const char *name); + /*virtual*/ BOOL unpackBinaryData(U8 *value, S32 &size, const char *name); + + /*virtual*/ BOOL packBinaryDataFixed(const U8 *value, S32 size, const char *name); + /*virtual*/ BOOL unpackBinaryDataFixed(U8 *value, S32 size, const char *name); + + /*virtual*/ BOOL packU8(const U8 value, const char *name); + /*virtual*/ BOOL unpackU8(U8 &value, const char *name); + + /*virtual*/ BOOL packU16(const U16 value, const char *name); + /*virtual*/ BOOL unpackU16(U16 &value, const char *name); + + /*virtual*/ BOOL packU32(const U32 value, const char *name); + /*virtual*/ BOOL unpackU32(U32 &value, const char *name); + + /*virtual*/ BOOL packS32(const S32 value, const char *name); + /*virtual*/ BOOL unpackS32(S32 &value, const char *name); + + /*virtual*/ BOOL packF32(const F32 value, const char *name); + /*virtual*/ BOOL unpackF32(F32 &value, const char *name); + + /*virtual*/ BOOL packColor4(const LLColor4 &value, const char *name); + /*virtual*/ BOOL unpackColor4(LLColor4 &value, const char *name); + + /*virtual*/ BOOL packColor4U(const LLColor4U &value, const char *name); + /*virtual*/ BOOL unpackColor4U(LLColor4U &value, const char *name); + + /*virtual*/ BOOL packVector2(const LLVector2 &value, const char *name); + /*virtual*/ BOOL unpackVector2(LLVector2 &value, const char *name); + + /*virtual*/ BOOL packVector3(const LLVector3 &value, const char *name); + /*virtual*/ BOOL unpackVector3(LLVector3 &value, const char *name); + + /*virtual*/ BOOL packVector4(const LLVector4 &value, const char *name); + /*virtual*/ BOOL unpackVector4(LLVector4 &value, const char *name); + + /*virtual*/ BOOL packUUID(const LLUUID &value, const char *name); + /*virtual*/ BOOL unpackUUID(LLUUID &value, const char *name); +protected: + void writeIndentedName(const char *name); + BOOL getValueStr(const char *name, char *out_value, const S32 value_len); + + /*virtual*/ BOOL hasNext() const { return true; } + +protected: + S32 mIndent; + FILE *mFP; + std::ostream* mOutputStream; + std::istream* mInputStream; +}; + +#endif // LL_LLDATAPACKER + diff --git a/indra/llmessage/lldbstrings.h b/indra/llmessage/lldbstrings.h new file mode 100644 index 0000000000..73aca880ef --- /dev/null +++ b/indra/llmessage/lldbstrings.h @@ -0,0 +1,208 @@ +/** + * @file lldbstrings.h + * @brief Database String Lengths. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLDBSTRINGS_H +#define LL_LLDBSTRINGS_H + +/** + * Defines the length of strings that are stored in the database (and + * the size of the buffer large enough to hold each one) + */ + +// asset.name varchar(63) +// -also- +// user_inventory_item.name varchar(63) +// -also- +// user_inventory_folder.name varchar(63) was CAT_NAME_SIZE +// Must be >= DB_FULL_NAME_STR_LEN so that calling cards work +const S32 DB_INV_ITEM_NAME_STR_LEN = 63; // was MAX_ASSET_NAME_LENGTH +const S32 DB_INV_ITEM_NAME_BUF_SIZE = 64; // was ITEM_NAME_SIZE + +// asset.description varchar(127) +// -also- +// user_inventory_item.description varchar(127) +const S32 DB_INV_ITEM_DESC_STR_LEN = 127; // was MAX_ASSET_DESCRIPTION_LENGTH +const S32 DB_INV_ITEM_DESC_BUF_SIZE = 128; // was ITEM_DESC_SIZE + +// groups.name varchar(35) +const S32 DB_GROUP_NAME_STR_LEN = 35; +const S32 DB_GROUP_NAME_BUF_SIZE = 36; +const S32 DB_GROUP_NAME_MIN_LEN = 4; + +//group_roles.name +const U32 DB_GROUP_ROLE_NAME_STR_LEN = 20; +const U32 DB_GROUP_ROLE_NAME_BUF_SIZE = DB_GROUP_ROLE_NAME_STR_LEN + 1; + +//group_roles.title +const U32 DB_GROUP_ROLE_TITLE_STR_LEN = 20; +const U32 DB_GROUP_ROLE_TITLE_BUF_SIZE = DB_GROUP_ROLE_TITLE_STR_LEN + 1; + + +// group.charter text +const S32 DB_GROUP_CHARTER_STR_LEN = 511; +const S32 DB_GROUP_CHARTER_BUF_SIZE = 512; + +// group.officer_title varchar(20) +// -also- +// group.member_title varchar(20) +const S32 DB_GROUP_TITLE_STR_LEN = 20; +const S32 DB_GROUP_TITLE_BUF_SIZE = 21; + +// Since chat and im both dump into the database text message log, +// they derive their max size from the same constant. +const S32 MAX_MSG_STR_LEN = 1023; +const S32 MAX_MSG_BUF_SIZE = 1024; + +// instant_message.message text +const S32 DB_IM_MSG_STR_LEN = MAX_MSG_STR_LEN; +const S32 DB_IM_MSG_BUF_SIZE = MAX_MSG_BUF_SIZE; + +// groupnotices +const S32 DB_GROUP_NOTICE_SUBJ_STR_LEN = 63; +const S32 DB_GROUP_NOTICE_SUBJ_STR_SIZE = 64; +const S32 DB_GROUP_NOTICE_MSG_STR_LEN = MAX_MSG_STR_LEN - DB_GROUP_NOTICE_SUBJ_STR_LEN; +const S32 DB_GROUP_NOTICE_MSG_STR_SIZE = MAX_MSG_BUF_SIZE - DB_GROUP_NOTICE_SUBJ_STR_SIZE; + +// log_text_message.message text +const S32 DB_CHAT_MSG_STR_LEN = MAX_MSG_STR_LEN; +const S32 DB_CHAT_MSG_BUF_SIZE = MAX_MSG_BUF_SIZE; + +// money_stipend.description varchar(254) +const S32 DB_STIPEND_DESC_STR_LEN = 254; +const S32 DB_STIPEND_DESC_BUF_SIZE = 255; + +// script_email_message.from_email varchar(78) +const S32 DB_EMAIL_FROM_STR_LEN = 78; +const S32 DB_EMAIL_FROM_BUF_SIZE = 79; + +// script_email_message.subject varchar(72) +const S32 DB_EMAIL_SUBJECT_STR_LEN = 72; +const S32 DB_EMAIL_SUBJECT_BUF_SIZE = 73; + +// system_globals.motd varchar(254) +const S32 DB_MOTD_STR_LEN = 254; +const S32 DB_MOTD_BUF_SIZE = 255; + +// Must be <= user_inventory_item.name so that calling cards work +// First name + " " + last name...or a system assigned "from" name +// instant_message.from_agent_name varchar(63) +// -also- +// user_mute.mute_agent_name varchar(63) +const S32 DB_FULL_NAME_STR_LEN = 63; +const S32 DB_FULL_NAME_BUF_SIZE = 64; // was USER_NAME_SIZE + +// user.username varchar(31) +const S32 DB_FIRST_NAME_STR_LEN = 31; +const S32 DB_FIRST_NAME_BUF_SIZE = 32; // was MAX_FIRST_NAME + +// user_last_name.name varchar(31) +const S32 DB_LAST_NAME_STR_LEN = 31; +const S32 DB_LAST_NAME_BUF_SIZE = 32; // was MAX_LAST_NAME + +// user.password varchar(100) +const S32 DB_USER_PASSWORD_STR_LEN = 100; +const S32 DB_USER_PASSWORD_BUF_SIZE = 101; // was MAX_PASSWORD + +// user.email varchar(254) +const S32 DB_USER_EMAIL_ADDR_STR_LEN = 254; +const S32 DB_USER_EMAIL_ADDR_BUF_SIZE = 255; + +// user.about text +const S32 DB_USER_ABOUT_STR_LEN = 511; +const S32 DB_USER_ABOUT_BUF_SIZE = 512; + +// user.fl_about_text text +// Must be 255 not 256 as gets packed into message Variable 1 +const S32 DB_USER_FL_ABOUT_STR_LEN = 254; +const S32 DB_USER_FL_ABOUT_BUF_SIZE = 255; + +// user.profile_url text +// Must be 255 not 256 as gets packed into message Variable 1 +const S32 DB_USER_PROFILE_URL_STR_LEN = 254; +const S32 DB_USER_PROFILE_URL_BUF_SIZE = 255; + +// user.want_to varchar(254) +const S32 DB_USER_WANT_TO_STR_LEN = 254; +const S32 DB_USER_WANT_TO_BUF_SIZE = 255; + +// user.skills varchar(254) +const S32 DB_USER_SKILLS_STR_LEN = 254; +const S32 DB_USER_SKILLS_BUF_SIZE = 255; + +// user_nv.name varchar(128) +const S32 DB_NV_NAME_STR_LEN = 128; +const S32 DB_NV_NAME_BUF_SIZE = 129; + +// votes.vote_text varchar(254) +const S32 DB_VOTE_TEXT_STR_LEN = 254; +const S32 DB_VOTE_TEXT_BUF_SIZE = 255; + +// vpte type text varchar(9) +const S32 DB_VOTE_TYPE_STR_LEN = 9; +const S32 DB_VOTE_TYPE_BUF_SIZE = 10; + +// vote result text +const S32 DB_VOTE_RESULT_BUF_LEN = 8; +const S32 DB_VOTE_RESULT_BUF_SIZE = 9; + +// user_start_location.location_name varchar(254) +const S32 DB_START_LOCATION_STR_LEN = 254; +const S32 DB_START_LOCATION_BUF_SIZE = 255; + +// money_tax_assessment.sim varchar(100) +//const S32 DB_SIM_NAME_STR_LEN = 100; +//const S32 DB_SIM_NAME_BUF_SIZE = 101; + +// born on date date +const S32 DB_BORN_STR_LEN = 15; +const S32 DB_BORN_BUF_SIZE = 16; + +// place.name +const S32 DB_PLACE_NAME_LEN = 63; +const S32 DB_PLACE_NAME_SIZE = 64; +const S32 DB_PARCEL_NAME_LEN = 63; +const S32 DB_PARCEL_NAME_SIZE = 64; + +// place.desc +const S32 DB_PLACE_DESC_LEN = 255; +const S32 DB_PLACE_DESC_SIZE = 256; +const S32 DB_PARCEL_DESC_LEN = 255; +const S32 DB_PARCEL_DESC_SIZE = 256; +const S32 DB_PARCEL_MUSIC_URL_LEN = 255; +const S32 DB_PARCEL_MEDIA_URL_LEN = 255; +const S32 DB_PARCEL_MUSIC_URL_SIZE = 256; + +// date time that is easily human readable +const S32 DB_DATETIME_STR_LEN = 35; +const S32 DB_DATETIME_BUF_SIZE = 36; + +// date time that isn't easily human readable +const S32 DB_TERSE_DATETIME_STR_LEN = 15; +const S32 DB_TERSE_DATETIME_BUF_SIZE = 16; + +// indra.simulator constants +const S32 DB_SIM_NAME_STR_LEN = 35; +const S32 DB_SIM_NAME_BUF_SIZE = 36; +const S32 DB_HOST_NAME_STR_LEN = 100; +const S32 DB_HOST_NAME_BUF_SIZE = 101; +const S32 DB_ESTATE_NAME_STR_LEN = 63; +const S32 DB_ESTATE_NAME_BUF_SIZE = DB_ESTATE_NAME_STR_LEN + 1; + +// user_note.note +const S32 DB_USER_NOTE_LEN = 1023; +const S32 DB_USER_NOTE_SIZE = 1024; + +// pick.name +const S32 DB_PICK_NAME_LEN = 63; +const S32 DB_PICK_NAME_SIZE = 64; + +// pick.desc +const S32 DB_PICK_DESC_LEN = 1023; +const S32 DB_PICK_DESC_SIZE = 1024; + +#endif // LL_LLDBSTRINGS_H diff --git a/indra/llmessage/lldispatcher.cpp b/indra/llmessage/lldispatcher.cpp new file mode 100644 index 0000000000..8ba051765e --- /dev/null +++ b/indra/llmessage/lldispatcher.cpp @@ -0,0 +1,126 @@ +/** + * @file lldispatcher.cpp + * @brief Implementation of the dispatcher object. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lldispatcher.h" + +#include +#include "llstl.h" +#include "message.h" + +///---------------------------------------------------------------------------- +/// Class lldispatcher +///---------------------------------------------------------------------------- + + +LLDispatcher::LLDispatcher() +{ +} + +LLDispatcher::~LLDispatcher() +{ +} + +bool LLDispatcher::isHandlerPresent(const key_t& name) const +{ + if(mHandlers.find(name) != mHandlers.end()) + { + return true; + } + return false; +} + +void LLDispatcher::copyAllHandlerNames(keys_t& names) const +{ + // copy the names onto the vector we are given + std::transform( + mHandlers.begin(), + mHandlers.end(), + std::back_insert_iterator(names), + llselect1st()); +} + +bool LLDispatcher::dispatch( + const key_t& name, + const LLUUID& invoice, + const sparam_t& strings) const +{ + dispatch_map_t::const_iterator it = mHandlers.find(name); + if(it != mHandlers.end()) + { + LLDispatchHandler* func = (*it).second; + return (*func)(this, name, invoice, strings); + } + return false; +} + +LLDispatchHandler* LLDispatcher::addHandler( + const key_t& name, LLDispatchHandler* func) +{ + dispatch_map_t::iterator it = mHandlers.find(name); + LLDispatchHandler* old_handler = NULL; + if(it != mHandlers.end()) + { + old_handler = (*it).second; + mHandlers.erase(it); + } + if(func) + { + // only non-null handlers so that we don't have to worry about + // it later. + mHandlers.insert(dispatch_map_t::value_type(name, func)); + } + return old_handler; +} + +// static +bool LLDispatcher::unpackMessage( + LLMessageSystem* msg, + LLDispatcher::key_t& method, + LLUUID& invoice, + LLDispatcher::sparam_t& parameters) +{ + char buf[MAX_STRING]; /*Flawfinder: ignore*/ + msg->getStringFast(_PREHASH_MethodData, _PREHASH_Method, MAX_STRING, buf); + method.assign(buf); + msg->getUUIDFast(_PREHASH_MethodData, _PREHASH_Invoice, invoice); + S32 size; + S32 count = msg->getNumberOfBlocksFast(_PREHASH_ParamList); + for (S32 i = 0; i < count; ++i) + { + // we treat the SParam as binary data (since it might be an + // LLUUID in compressed form which may have embedded \0's,) + size = msg->getSizeFast(_PREHASH_ParamList, i, _PREHASH_Parameter); + msg->getBinaryDataFast( + _PREHASH_ParamList, _PREHASH_Parameter, + buf, size, i, MAX_STRING-1); + + // If the last byte of the data is 0x0, this is either a normally + // packed string, or a binary packed UUID (which for these messages + // are packed with a 17th byte 0x0). Unpack into a std::string + // without the trailing \0, so "abc\0" becomes std::string("abc", 3) + // which matches const char* "abc". + if (size > 0 + && buf[size-1] == 0x0) + { + // special char*/size constructor because UUIDs may have embedded + // 0x0 bytes. + std::string binary_data(buf, size-1); + parameters.push_back(binary_data); + } + else + { + // This is either a NULL string, or a string that was packed + // incorrectly as binary data, without the usual trailing '\0'. + std::string string_data(buf, size); + parameters.push_back(string_data); + } + } + return true; +} diff --git a/indra/llmessage/lldispatcher.h b/indra/llmessage/lldispatcher.h new file mode 100644 index 0000000000..e0eb706be1 --- /dev/null +++ b/indra/llmessage/lldispatcher.h @@ -0,0 +1,95 @@ +/** + * @file lldispatcher.h + * @brief LLDispatcher class header file. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLDISPATCHER_H +#define LL_LLDISPATCHER_H + +#include +#include +#include + +class LLDispatcher; +class LLMessageSystem; +class LLUUID; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLDispatchHandler +// +// Abstract base class for handling dispatches. Derive your own +// classes, construct them, and add them to the dispatcher you want to +// use. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLDispatchHandler +{ +public: + typedef std::vector sparam_t; + //typedef std::vector iparam_t; + LLDispatchHandler() {} + virtual ~LLDispatchHandler() {} + virtual bool operator()( + const LLDispatcher* dispatcher, + const std::string& key, + const LLUUID& invoice, + const sparam_t& string) = 0; + //const iparam_t& integers) = 0; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLDispatcher +// +// Basic utility class that handles dispatching keyed operations to +// function objects implemented as LLDispatchHandler derivations. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLDispatcher +{ +public: + typedef std::string key_t; + typedef std::vector keys_t; + typedef std::vector sparam_t; + //typedef std::vector iparam_t; + + // construct a dispatcher. + LLDispatcher(); + virtual ~LLDispatcher(); + + // Returns if they keyed handler exists in this dispatcher. + bool isHandlerPresent(const key_t& name) const; + + // copy all known keys onto keys_t structure + void copyAllHandlerNames(keys_t& names) const; + + // Call this method with the name of the request that has come + // in. If the handler is present, it is called with the params and + // returns the return value from + bool dispatch( + const key_t& name, + const LLUUID& invoice, + const sparam_t& strings) const; + //const iparam_t& itegers) const; + + // Add a handler. If one with the same key already exists, its + // pointer is returned, otherwise returns NULL. This object does + // not do memory management of the LLDispatchHandler, and relies + // on the caller to delete the object if necessary. + LLDispatchHandler* addHandler(const key_t& name, LLDispatchHandler* func); + + // Helper method to unpack the dispatcher message bus + // format. Returns true on success. + static bool unpackMessage( + LLMessageSystem* msg, + key_t& method, + LLUUID& invoice, + sparam_t& parameters); + +protected: + typedef std::map dispatch_map_t; + dispatch_map_t mHandlers; +}; + +#endif // LL_LLDISPATCHER_H diff --git a/indra/llmessage/lleventflags.h b/indra/llmessage/lleventflags.h new file mode 100644 index 0000000000..90120b2648 --- /dev/null +++ b/indra/llmessage/lleventflags.h @@ -0,0 +1,17 @@ +/** + * @file lleventflags.h + * @brief Flags for events. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLEVENTFLAGS_H +#define LL_LLEVENTFLAGS_H + +const U32 EVENT_FLAG_NONE = 0x0000; + +// set for mature events +const U32 EVENT_FLAG_MATURE = 0x0001; + +#endif diff --git a/indra/llmessage/llfiltersd2xmlrpc.cpp b/indra/llmessage/llfiltersd2xmlrpc.cpp new file mode 100644 index 0000000000..6d5f92e983 --- /dev/null +++ b/indra/llmessage/llfiltersd2xmlrpc.cpp @@ -0,0 +1,728 @@ +/** + * @file llfiltersd2xmlrpc.cpp + * @author Phoenix + * @date 2005-04-26 + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +/** + * xml rpc request: + * + * + * examples.getStateName + * 41 + * + * + * + * xml rpc response: + * + * + * + * South Dakota + * + * + * + * xml rpc fault: + * + * + * + * + * faultCode4 + * faultString... + * + * + * + * + * llsd rpc request: + * + * { 'method':'...', 'parameter':...]} + * + * + * llsd rpc response: + * + * { 'response':... } + * + * + * llsd rpc fault: + * + * { 'fault': {'code':i..., 'description':'...'} } + * + * + */ + +#include "linden_common.h" +#include "llfiltersd2xmlrpc.h" + +#include +#include +#include +#include "apr-1/apr_base64.h" + +#include "llbuffer.h" +#include "llbufferstream.h" +#include "llmemorystream.h" +#include "llsd.h" +#include "llsdserialize.h" +#include "lluuid.h" + +// spammy mode +//#define LL_SPEW_STREAM_OUT_DEBUGGING 1 + +/** + * String constants + */ +static const char XML_HEADER[] = ""; +static const char XMLRPC_REQUEST_HEADER_1[] = ""; +static const char XMLRPC_REQUEST_HEADER_2[] = ""; +static const char XMLRPC_REQUEST_FOOTER[] = ""; +static const char XMLRPC_METHOD_RESPONSE_HEADER[] = ""; +static const char XMLRPC_METHOD_RESPONSE_FOOTER[] = ""; +static const char XMLRPC_RESPONSE_HEADER[] = ""; +static const char XMLRPC_RESPONSE_FOOTER[] = ""; +static const char XMLRPC_FAULT_1[] = "faultCode"; +static const char XMLRPC_FAULT_2[] = "faultString"; +static const char XMLRPC_FAULT_3[] = ""; +static const char LLSDRPC_RESPONSE_HEADER[] = "{'response':"; +static const char LLSDRPC_RESPONSE_FOOTER[] = "}"; +const char LLSDRPC_REQUEST_HEADER_1[] = "{'method':'"; +const char LLSDRPC_REQUEST_HEADER_2[] = "', 'parameter': "; +const char LLSDRPC_REQUEST_FOOTER[] = "}"; +static const char LLSDRPC_FAULT_HADER_1[] = "{ 'fault': {'code':i"; +static const char LLSDRPC_FAULT_HADER_2[] = ", 'description':"; +static const char LLSDRPC_FAULT_FOOTER[] = "} }"; +static const S32 DEFAULT_PRECISION = 20; + +/** + * LLFilterSD2XMLRPC + */ +LLFilterSD2XMLRPC::LLFilterSD2XMLRPC() +{ +} + +LLFilterSD2XMLRPC::~LLFilterSD2XMLRPC() +{ +} + +std::string xml_escape_string(const std::string& in) +{ + std::ostringstream out; + std::string::const_iterator it = in.begin(); + std::string::const_iterator end = in.end(); + for(; it != end; ++it) + { + switch((*it)) + { + case '<': + out << "<"; + break; + case '>': + out << ">"; + break; + case '&': + out << "&"; + break; + case '\'': + out << "'"; + break; + case '"': + out << """; + break; + default: + out << (*it); + break; + } + } + return out.str(); +} + +void LLFilterSD2XMLRPC::streamOut(std::ostream& ostr, const LLSD& sd) +{ + ostr << ""; + switch(sd.type()) + { + case LLSD::TypeMap: + { +#if LL_SPEW_STREAM_OUT_DEBUGGING + llinfos << "streamOut(map) BEGIN" << llendl; +#endif + ostr << ""; + if(ostr.fail()) + { + llinfos << "STREAM FAILURE writing struct" << llendl; + } + LLSD::map_const_iterator it = sd.beginMap(); + LLSD::map_const_iterator end = sd.endMap(); + for(; it != end; ++it) + { + ostr << "" << xml_escape_string((*it).first) + << ""; + streamOut(ostr, (*it).second); + if(ostr.fail()) + { + llinfos << "STREAM FAILURE writing '" << (*it).first + << "' with sd type " << (*it).second.type() << llendl; + } + ostr << ""; + } + ostr << ""; +#if LL_SPEW_STREAM_OUT_DEBUGGING + llinfos << "streamOut(map) END" << llendl; +#endif + break; + } + case LLSD::TypeArray: + { +#if LL_SPEW_STREAM_OUT_DEBUGGING + llinfos << "streamOut(array) BEGIN" << llendl; +#endif + ostr << ""; + LLSD::array_const_iterator it = sd.beginArray(); + LLSD::array_const_iterator end = sd.endArray(); + for(; it != end; ++it) + { + streamOut(ostr, *it); + if(ostr.fail()) + { + llinfos << "STREAM FAILURE writing array element sd type " + << (*it).type() << llendl; + } + } +#if LL_SPEW_STREAM_OUT_DEBUGGING + llinfos << "streamOut(array) END" << llendl; +#endif + ostr << ""; + break; + } + case LLSD::TypeUndefined: + // treat undefined as a bool with a false value. + case LLSD::TypeBoolean: +#if LL_SPEW_STREAM_OUT_DEBUGGING + llinfos << "streamOut(bool)" << llendl; +#endif + ostr << "" << (sd.asBoolean() ? "1" : "0") << ""; + break; + case LLSD::TypeInteger: +#if LL_SPEW_STREAM_OUT_DEBUGGING + llinfos << "streamOut(int)" << llendl; +#endif + ostr << "" << sd.asInteger() << ""; + break; + case LLSD::TypeReal: +#if LL_SPEW_STREAM_OUT_DEBUGGING + llinfos << "streamOut(real)" << llendl; +#endif + ostr << "" << sd.asReal() << ""; + break; + case LLSD::TypeString: +#if LL_SPEW_STREAM_OUT_DEBUGGING + llinfos << "streamOut(string)" << llendl; +#endif + ostr << "" << xml_escape_string(sd.asString()) << ""; + break; + case LLSD::TypeUUID: +#if LL_SPEW_STREAM_OUT_DEBUGGING + llinfos << "streamOut(uuid)" << llendl; +#endif + // serialize it as a string + ostr << "" << sd.asString() << ""; + break; + case LLSD::TypeURI: + { +#if LL_SPEW_STREAM_OUT_DEBUGGING + llinfos << "streamOut(uri)" << llendl; +#endif + // serialize it as a string + ostr << "" << xml_escape_string(sd.asString()) << ""; + break; + } + case LLSD::TypeBinary: + { +#if LL_SPEW_STREAM_OUT_DEBUGGING + llinfos << "streamOut(binary)" << llendl; +#endif + // this is pretty inefficient, but we'll deal with that + // problem when it becomes one. + ostr << ""; + LLSD::Binary buffer = sd.asBinary(); + if(!buffer.empty()) + { + int b64_buffer_length = apr_base64_encode_len(buffer.size()); + char* b64_buffer = new char[b64_buffer_length]; + b64_buffer_length = apr_base64_encode_binary( + b64_buffer, + &buffer[0], + buffer.size()); + ostr.write(b64_buffer, b64_buffer_length - 1); + delete[] b64_buffer; + } + ostr << ""; + break; + } + case LLSD::TypeDate: +#if LL_SPEW_STREAM_OUT_DEBUGGING + llinfos << "streamOut(date)" << llendl; +#endif + // no need to escape this since it will be alpha-numeric. + ostr << "" << sd.asString() << ""; + break; + default: + // unhandled type + llwarns << "Unhandled structured data type: " << sd.type() + << llendl; + break; + } + ostr << ""; +} + +/** + * LLFilterSD2XMLRPCResponse + */ + +LLFilterSD2XMLRPCResponse::LLFilterSD2XMLRPCResponse() +{ +} + +LLFilterSD2XMLRPCResponse::~LLFilterSD2XMLRPCResponse() +{ +} + + +// virtual +LLIOPipe::EStatus LLFilterSD2XMLRPCResponse::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + // This pipe does not work if it does not have everyting. This + // could be addressed by making a stream parser for llsd which + // handled partial information. + if(!eos) + { + return STATUS_BREAK; + } + + PUMP_DEBUG; + // we have everyting in the buffer, so turn the structure data rpc + // response into an xml rpc response. + LLBufferStream stream(channels, buffer.get()); + stream << XML_HEADER << XMLRPC_METHOD_RESPONSE_HEADER; + LLSD sd; + LLSDSerialize::fromNotation(sd, stream); + + PUMP_DEBUG; + LLIOPipe::EStatus rv = STATUS_ERROR; + if(sd.has("response")) + { + PUMP_DEBUG; + // it is a normal response. pack it up and ship it out. + stream.precision(DEFAULT_PRECISION); + stream << XMLRPC_RESPONSE_HEADER; + streamOut(stream, sd["response"]); + stream << XMLRPC_RESPONSE_FOOTER << XMLRPC_METHOD_RESPONSE_FOOTER; + rv = STATUS_DONE; + } + else if(sd.has("fault")) + { + PUMP_DEBUG; + // it is a fault. + stream << XMLRPC_FAULT_1 << sd["fault"]["code"].asInteger() + << XMLRPC_FAULT_2 + << xml_escape_string(sd["fault"]["description"].asString()) + << XMLRPC_FAULT_3 << XMLRPC_METHOD_RESPONSE_FOOTER; + rv = STATUS_DONE; + } + else + { + llwarns << "Unable to determine the type of LLSD response." << llendl; + } + PUMP_DEBUG; + return rv; +} + +/** + * LLFilterSD2XMLRPCRequest + */ +LLFilterSD2XMLRPCRequest::LLFilterSD2XMLRPCRequest() +{ +} + +LLFilterSD2XMLRPCRequest::LLFilterSD2XMLRPCRequest(const char* method) +{ + if(method) + { + mMethod.assign(method); + } +} + +LLFilterSD2XMLRPCRequest::~LLFilterSD2XMLRPCRequest() +{ +} + +// virtual +LLIOPipe::EStatus LLFilterSD2XMLRPCRequest::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + // This pipe does not work if it does not have everyting. This + // could be addressed by making a stream parser for llsd which + // handled partial information. + PUMP_DEBUG; + if(!eos) + { + llinfos << "!eos" << llendl; + return STATUS_BREAK; + } + + // See if we can parse it + LLBufferStream stream(channels, buffer.get()); + LLSD sd; + LLSDSerialize::fromNotation(sd, stream); + if(stream.fail()) + { + llinfos << "STREAM FAILURE reading structure data." << llendl; + } + + PUMP_DEBUG; + // We can get the method and parameters from either the member + // function or passed in via the buffer. We prefer the buffer if + // we found a parameter and a method, or fall back to using + // mMethod and putting everyting in the buffer into the parameter. + std::string method; + LLSD param_sd; + if(sd.has("method") && sd.has("parameter")) + { + method = sd["method"].asString(); + param_sd = sd["parameter"]; + } + else + { + method = mMethod; + param_sd = sd; + } + if(method.empty()) + { + llwarns << "SD -> XML Request no method found." << llendl; + return STATUS_ERROR; + } + + PUMP_DEBUG; + // We have a method, and some kind of parameter, so package it up + // and send it out. + LLBufferStream ostream(channels, buffer.get()); + ostream.precision(DEFAULT_PRECISION); + if(ostream.fail()) + { + llinfos << "STREAM FAILURE setting precision" << llendl; + } + ostream << XML_HEADER << XMLRPC_REQUEST_HEADER_1 + << xml_escape_string(method) << XMLRPC_REQUEST_HEADER_2; + if(ostream.fail()) + { + llinfos << "STREAM FAILURE writing method headers" << llendl; + } + switch(param_sd.type()) + { + case LLSD::TypeMap: + // If the params are a map, then we do not want to iterate + // through them since the iterators returned will be map + // ordered un-named values, which will lose the names, and + // only stream the values, turning it into an array. + ostream << ""; + streamOut(ostream, param_sd); + ostream << ""; + break; + case LLSD::TypeArray: + { + + LLSD::array_iterator it = param_sd.beginArray(); + LLSD::array_iterator end = param_sd.endArray(); + for(; it != end; ++it) + { + ostream << ""; + streamOut(ostream, *it); + ostream << ""; + } + break; + } + default: + ostream << ""; + streamOut(ostream, param_sd); + ostream << ""; + break; + } + + stream << XMLRPC_REQUEST_FOOTER; + return STATUS_DONE; +} + +/** + * LLFilterXMLRPCResponse2LLSD + */ +// this is a c function here since it's really an implementation +// detail that requires a header file just get the definition of the +// parameters. +LLIOPipe::EStatus stream_out(std::ostream& ostr, XMLRPC_VALUE value) +{ + XMLRPC_VALUE_TYPE_EASY type = XMLRPC_GetValueTypeEasy(value); + LLIOPipe::EStatus status = LLIOPipe::STATUS_OK; + switch(type) + { + case xmlrpc_type_base64: + { + S32 len = XMLRPC_GetValueStringLen(value); + const char* buf = XMLRPC_GetValueBase64(value); + ostr << " b("; + if((len > 0) && buf) + { + ostr << len << ")\""; + ostr.write(buf, len); + ostr << "\""; + } + else + { + ostr << "0)\"\""; + } + break; + } + case xmlrpc_type_boolean: + //lldebugs << "stream_out() bool" << llendl; + ostr << " " << (XMLRPC_GetValueBoolean(value) ? "true" : "false"); + break; + case xmlrpc_type_datetime: + ostr << " d\"" << XMLRPC_GetValueDateTime_ISO8601(value) << "\""; + break; + case xmlrpc_type_double: + ostr << " r" << XMLRPC_GetValueDouble(value); + //lldebugs << "stream_out() double" << XMLRPC_GetValueDouble(value) + // << llendl; + break; + case xmlrpc_type_int: + ostr << " i" << XMLRPC_GetValueInt(value); + //lldebugs << "stream_out() integer:" << XMLRPC_GetValueInt(value) + // << llendl; + break; + case xmlrpc_type_string: + //lldebugs << "stream_out() string: " << str << llendl; + ostr << " s(" << XMLRPC_GetValueStringLen(value) << ")'" + << XMLRPC_GetValueString(value) << "'"; + break; + case xmlrpc_type_array: // vector + case xmlrpc_type_mixed: // vector + { + //lldebugs << "stream_out() array" << llendl; + ostr << " ["; + U32 needs_comma = 0; + XMLRPC_VALUE current = XMLRPC_VectorRewind(value); + while(current && (LLIOPipe::STATUS_OK == status)) + { + if(needs_comma++) ostr << ","; + status = stream_out(ostr, current); + current = XMLRPC_VectorNext(value); + } + ostr << "]"; + break; + } + case xmlrpc_type_struct: // still vector + { + //lldebugs << "stream_out() struct" << llendl; + ostr << " {"; + std::string name; + U32 needs_comma = 0; + XMLRPC_VALUE current = XMLRPC_VectorRewind(value); + while(current && (LLIOPipe::STATUS_OK == status)) + { + if(needs_comma++) ostr << ","; + name.assign(XMLRPC_GetValueID(current)); + ostr << "'" << LLSDNotationFormatter::escapeString(name) << "':"; + status = stream_out(ostr, current); + current = XMLRPC_VectorNext(value); + } + ostr << "}"; + break; + } + case xmlrpc_type_empty: + case xmlrpc_type_none: + default: + status = LLIOPipe::STATUS_ERROR; + llwarns << "Found an empty xmlrpc type.." << llendl; + // not much we can do here... + break; + }; + return status; +} + +LLFilterXMLRPCResponse2LLSD::LLFilterXMLRPCResponse2LLSD() +{ +} + +LLFilterXMLRPCResponse2LLSD::~LLFilterXMLRPCResponse2LLSD() +{ +} + +LLIOPipe::EStatus LLFilterXMLRPCResponse2LLSD::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + if(!eos) return STATUS_BREAK; + if(!buffer) return STATUS_ERROR; + + PUMP_DEBUG; + // *FIX: This technique for reading data is far from optimal. We + // need to have some kind of istream interface into the xml + // parser... + S32 bytes = buffer->countAfter(channels.in(), NULL); + if(!bytes) return STATUS_ERROR; + char* buf = new char[bytes + 1]; + buf[bytes] = '\0'; + buffer->readAfter(channels.in(), NULL, (U8*)buf, bytes); + + //lldebugs << "xmlrpc response: " << buf << llendl; + + PUMP_DEBUG; + XMLRPC_REQUEST response = XMLRPC_REQUEST_FromXML( + buf, + bytes, + NULL); + if(!response) + { + llwarns << "XML -> SD Response unable to parse xml." << llendl; + delete[] buf; + return STATUS_ERROR; + } + + PUMP_DEBUG; + LLBufferStream stream(channels, buffer.get()); + stream.precision(DEFAULT_PRECISION); + if(XMLRPC_ResponseIsFault(response)) + { + PUMP_DEBUG; + stream << LLSDRPC_FAULT_HADER_1 + << XMLRPC_GetResponseFaultCode(response) + << LLSDRPC_FAULT_HADER_2; + const char* fault_str = XMLRPC_GetResponseFaultString(response); + std::string fault_string; + if(fault_str) + { + fault_string.assign(fault_str); + } + stream << "'" << LLSDNotationFormatter::escapeString(fault_string) + << "'" <countAfter(channels.in(), NULL); + if(!bytes) return STATUS_ERROR; + char* buf = new char[bytes + 1]; + buf[bytes] = '\0'; + buffer->readAfter(channels.in(), NULL, (U8*)buf, bytes); + + //lldebugs << "xmlrpc request: " << buf << llendl; + + PUMP_DEBUG; + XMLRPC_REQUEST request = XMLRPC_REQUEST_FromXML( + buf, + bytes, + NULL); + if(!request) + { + llwarns << "XML -> SD Request process parse error." << llendl; + delete[] buf; + return STATUS_ERROR; + } + + PUMP_DEBUG; + LLBufferStream stream(channels, buffer.get()); + stream.precision(DEFAULT_PRECISION); + const char* name = XMLRPC_RequestGetMethodName(request); + stream << LLSDRPC_REQUEST_HEADER_1 << (name ? name : "") + << LLSDRPC_REQUEST_HEADER_2; + XMLRPC_VALUE param = XMLRPC_RequestGetData(request); + if(param) + { + PUMP_DEBUG; + S32 size = XMLRPC_VectorSize(param); + if(size > 1) + { + // if there are multiple parameters, stuff the values into + // an array so that the next step in the chain can read them. + stream << "["; + } + XMLRPC_VALUE current = XMLRPC_VectorRewind(param); + bool needs_comma = false; + while(current) + { + if(needs_comma) + { + stream << ","; + } + needs_comma = true; + stream_out(stream, current); + current = XMLRPC_VectorNext(param); + } + if(size > 1) + { + // close the array + stream << "]"; + } + } + stream << LLSDRPC_REQUEST_FOOTER; + XMLRPC_RequestFree(request, 1); + delete[] buf; + PUMP_DEBUG; + return STATUS_DONE; +} + diff --git a/indra/llmessage/llfiltersd2xmlrpc.h b/indra/llmessage/llfiltersd2xmlrpc.h new file mode 100644 index 0000000000..efde349271 --- /dev/null +++ b/indra/llmessage/llfiltersd2xmlrpc.h @@ -0,0 +1,253 @@ +/** + * @file llfiltersd2xmlrpc.h + * @author Phoenix + * @date 2005-04-26 + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLFILTERSD2XMLRPC_H +#define LL_LLFILTERSD2XMLRPC_H + +/** + * These classes implement the necessary pipes for translating between + * xmlrpc and llsd rpc. The llsd rpcs mechanism was developed as an + * extensible and easy to parse serialization grammer which maintains + * a time efficient in-memory representation. + */ + +#include +#include "lliopipe.h" + +/** + * @class LLFilterSD2XMLRPC + * @brief Filter from serialized LLSD to an XMLRPC method call + * + * This clas provides common functionality for the LLFilterSD2XMLRPRC + * request and response classes. + */ +class LLFilterSD2XMLRPC : public LLIOPipe +{ +public: + LLFilterSD2XMLRPC(); + virtual ~LLFilterSD2XMLRPC(); + +protected: + /** + * @brief helper method + */ + void streamOut(std::ostream& ostr, const LLSD& sd); +}; + +/** + * @class LLFilterSD2XMLRPCResponse + * @brief Filter from serialized LLSD to an XMLRPC response + * + * This class filters a serialized LLSD object to an xmlrpc + * repsonse. Since resonses are limited to a single param, the xmlrprc + * response only serializes it as one object. + * This class correctly handles normal llsd responses as well as llsd + * rpc faults. + * + * For example, if given: + * {'response':[ i200, r3.4, {"foo":"bar"} ]} + * Would generate: + * + * + * + * 200 + * 3.4 + * + * foobar + * + * + * + */ +class LLFilterSD2XMLRPCResponse : public LLFilterSD2XMLRPC +{ +public: + // constructor + LLFilterSD2XMLRPCResponse(); + + // destructor + virtual ~LLFilterSD2XMLRPCResponse(); + + /* @name LLIOPipe virtual implementations + */ + //@{ +protected: + /** + * @brief Process the data in buffer. + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} +}; + +/** + * @class LLFilterSD2XMLRPCRequest + * @brief Filter from serialized LLSD to an XMLRPC method call + * + * This class will accept any kind of serialized LLSD object, but you + * probably want to have an array on the outer boundary since this + * object will interpret each element in the top level LLSD as a + * parameter into the xmlrpc spec. + * + * For example, you would represent 3 params as: + * + * {'method'='foo', 'parameter':[i200, r3.4, {"foo":"bar"}]} + * + * To generate: + * + * + * + * 200 + * 3.4 + * + * foobar + * + * + * + * This class will accept 2 different kinds of encodings. The first + * just an array of params as long as you specify the method in the + * constructor. It will also accept a structured data in the form: + * {'method':'$method_name', 'parameter':[...] } In the latter form, the + * encoded 'method' will be used regardless of the construction of the + * object, and the 'parameter' will be used as parameter to the call. + */ +class LLFilterSD2XMLRPCRequest : public LLFilterSD2XMLRPC +{ +public: + // constructor + LLFilterSD2XMLRPCRequest(); + + // constructor + LLFilterSD2XMLRPCRequest(const char* method); + + // destructor + virtual ~LLFilterSD2XMLRPCRequest(); + + /* @name LLIOPipe virtual implementations + */ + //@{ +protected: + /** + * @brief Process the data in buffer. + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: + // The method name of this request. + std::string mMethod; +}; + +/** + * @class LLFilterXMLRPCResponse2LLSD + * @brief Filter from serialized XMLRPC method response to LLSD + * + * The xmlrpc spec states that responses can only have one element + * which can be of any supported type. + * This takes in xml of the form: + * + * + * ok + * + * And processes it into: + * 'ok' + * + */ +class LLFilterXMLRPCResponse2LLSD : public LLIOPipe +{ +public: + // constructor + LLFilterXMLRPCResponse2LLSD(); + + // destructor + virtual ~LLFilterXMLRPCResponse2LLSD(); + + /* @name LLIOPipe virtual implementations + */ + //@{ +protected: + /** + * @brief Process the data in buffer. + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: +}; + +/** + * @class LLFilterXMLRPCRequest2LLSD + * @brief Filter from serialized XMLRPC method call to LLSD + * + * This takes in xml of the form: + * + * + * repeat + * + * 4 + * ok + * + * + * And processes it into: + * { 'method':'repeat', 'params':[i4, 'ok'] } + */ +class LLFilterXMLRPCRequest2LLSD : public LLIOPipe +{ +public: + // constructor + LLFilterXMLRPCRequest2LLSD(); + + // destructor + virtual ~LLFilterXMLRPCRequest2LLSD(); + + /* @name LLIOPipe virtual implementations + */ + //@{ +protected: + /** + * @brief Process the data in buffer. + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: +}; + +/** + * @brief This function takes string, and escapes it appropritately + * for inclusion as xml data. + */ +std::string xml_escape_string(const std::string& in); + +/** + * @brief Externally available constants + */ +extern const char LLSDRPC_REQUEST_HEADER_1[]; +extern const char LLSDRPC_REQUEST_HEADER_2[]; +extern const char LLSDRPC_REQUEST_FOOTER[]; + +#endif // LL_LLFILTERSD2XMLRPC_H diff --git a/indra/llmessage/llfollowcamparams.h b/indra/llmessage/llfollowcamparams.h new file mode 100644 index 0000000000..1fa2a089ae --- /dev/null +++ b/indra/llmessage/llfollowcamparams.h @@ -0,0 +1,43 @@ +/** + * @file llfollowcamparams.h + * @brief Follow camera parameters. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_FOLLOWCAM_PARAMS_H +#define LL_FOLLOWCAM_PARAMS_H + + +//Ventrella Follow Cam Script Stuff +enum EFollowCamAttributes { + FOLLOWCAM_PITCH = 0, + FOLLOWCAM_FOCUS_OFFSET, + FOLLOWCAM_FOCUS_OFFSET_X, //this HAS to come after FOLLOWCAM_FOCUS_OFFSET in this list + FOLLOWCAM_FOCUS_OFFSET_Y, + FOLLOWCAM_FOCUS_OFFSET_Z, + FOLLOWCAM_POSITION_LAG, + FOLLOWCAM_FOCUS_LAG, + FOLLOWCAM_DISTANCE, + FOLLOWCAM_BEHINDNESS_ANGLE, + FOLLOWCAM_BEHINDNESS_LAG, + FOLLOWCAM_POSITION_THRESHOLD, + FOLLOWCAM_FOCUS_THRESHOLD, + FOLLOWCAM_ACTIVE, + FOLLOWCAM_POSITION, + FOLLOWCAM_POSITION_X, //this HAS to come after FOLLOWCAM_POSITION in this list + FOLLOWCAM_POSITION_Y, + FOLLOWCAM_POSITION_Z, + FOLLOWCAM_FOCUS, + FOLLOWCAM_FOCUS_X, //this HAS to come after FOLLOWCAM_FOCUS in this list + FOLLOWCAM_FOCUS_Y, + FOLLOWCAM_FOCUS_Z, + FOLLOWCAM_POSITION_LOCKED, + FOLLOWCAM_FOCUS_LOCKED, + NUM_FOLLOWCAM_ATTRIBUTES +}; + +//end Ventrella + +#endif //FOLLOWCAM_PARAMS_H diff --git a/indra/llmessage/llhost.cpp b/indra/llmessage/llhost.cpp new file mode 100644 index 0000000000..f4a1740663 --- /dev/null +++ b/indra/llmessage/llhost.cpp @@ -0,0 +1,216 @@ +/** + * @file llhost.cpp + * @brief Encapsulates an IP address and a port. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + + +#if !LL_WINDOWS +#include +#include // ntonl() +#include +#include +#include +#endif + +#include "llhost.h" + +#include "llerror.h" + +LLHost LLHost::invalid(INVALID_PORT,INVALID_HOST_IP_ADDRESS); + +LLHost::LLHost(const std::string& ip_and_port) +{ + std::string::size_type colon_index = ip_and_port.find(":"); + if (colon_index != std::string::npos) + { + mIP = ip_string_to_u32(ip_and_port.c_str()); + mPort = 0; + } + else + { + std::string ip_str(ip_and_port, 0, colon_index); + std::string port_str(ip_and_port, colon_index+1); + + mIP = ip_string_to_u32(ip_str.c_str()); + mPort = atol(port_str.c_str()); + } +} + +void LLHost::getString(char* buffer, S32 length) const +{ + if (((U32) length) < MAXADDRSTR + 1 + 5) + { + llerrs << "LLHost::getString - string too short" << llendl; + return; + } + + snprintf(buffer, length, "%s:%u", u32_to_ip_string(mIP), mPort); /*Flawfinder: ignore*/ +} + +void LLHost::getIPString(char* buffer, S32 length) const +{ + if ( ((U32) length) < MAXADDRSTR) + { + llerrs << "LLHost::getIPString - string too short" << llendl; + return; + } + + snprintf(buffer, length, "%s", u32_to_ip_string(mIP)); /*Flawfinder: ignore*/ +} + + +std::string LLHost::getIPandPort() const +{ + char buffer[MAXADDRSTR + 1 + 5]; + getString(buffer, sizeof(buffer)); + return buffer; +} + + +std::string LLHost::getIPString() const +{ + return std::string( u32_to_ip_string( mIP ) ); +} + + +void LLHost::getHostName(char *buf, S32 len) const +{ + hostent *he; + + if (INVALID_HOST_IP_ADDRESS == mIP) + { + llwarns << "LLHost::getHostName() : Invalid IP address" << llendl; + buf[0] = '\0'; + return; + } + he = gethostbyaddr((char *)&mIP, sizeof(mIP), AF_INET); + if (!he) + { +#if LL_WINDOWS + llwarns << "LLHost::getHostName() : Couldn't find host name for address " << mIP << ", Error: " + << WSAGetLastError() << llendl; +#else + llwarns << "LLHost::getHostName() : Couldn't find host name for address " << mIP << ", Error: " + << h_errno << llendl; +#endif + buf[0] = '\0'; + } + else + { + strncpy(buf, he->h_name, len); /*Flawfinder: ignore*/ + buf[len-1] = '\0'; + } +} + +LLString LLHost::getHostName() const +{ + hostent *he; + + if (INVALID_HOST_IP_ADDRESS == mIP) + { + llwarns << "LLHost::getHostName() : Invalid IP address" << llendl; + return ""; + } + he = gethostbyaddr((char *)&mIP, sizeof(mIP), AF_INET); + if (!he) + { +#if LL_WINDOWS + llwarns << "LLHost::getHostName() : Couldn't find host name for address " << mIP << ", Error: " + << WSAGetLastError() << llendl; +#else + llwarns << "LLHost::getHostName() : Couldn't find host name for address " << mIP << ", Error: " + << h_errno << llendl; +#endif + return ""; + } + else + { + LLString hostname = he->h_name; + return hostname; + } +} + +BOOL LLHost::setHostByName(const char *string) +{ + hostent *he; + char local_name[MAX_STRING]; /*Flawfinder: ignore*/ + + if (strlen(string)+1 > MAX_STRING) /*Flawfinder: ignore*/ + { + llerrs << "LLHost::setHostByName() : Address string is too long: " + << string << llendl; + } + + strncpy(local_name, string,MAX_STRING); /*Flawfinder: ignore*/ + local_name[MAX_STRING-1] = '\0'; +#if LL_WINDOWS + // We may need an equivalent for Linux, but not sure - djs + _strupr(local_name); +#endif + + he = gethostbyname(local_name); + if(!he) + { + U32 ip_address = inet_addr(string); + he = gethostbyaddr((char *)&ip_address, sizeof(ip_address), AF_INET); + } + + if (he) + { + mIP = *(U32 *)he->h_addr_list[0]; + return TRUE; + } + else + { + setAddress(local_name); + + // In windows, h_errno is a macro for WSAGetLastError(), so store value here + S32 error_number = h_errno; + switch(error_number) + { + case TRY_AGAIN: // XXX how to handle this case? + llwarns << "LLHost::setAddress(): try again" << llendl; + break; + case HOST_NOT_FOUND: + case NO_ADDRESS: // NO_DATA + llwarns << "LLHost::setAddress(): host not found" << llendl; + break; + case NO_RECOVERY: + llwarns << "LLHost::setAddress(): unrecoverable error" << llendl; + break; + default: + llwarns << "LLHost::setAddress(): unknown error - " << error_number << llendl; + break; + } + return FALSE; + } +} + +LLHost& LLHost::operator=(const LLHost &rhs) +{ + if (this != &rhs) + { + set(rhs.getAddress(), rhs.getPort()); + } + return *this; +} + + +std::ostream& operator<< (std::ostream& os, const LLHost &hh) +{ + os << u32_to_ip_string(hh.mIP) << ":" << hh.mPort ; + return os; +} + + +std::istream& operator>> (std::istream& is, LLHost &rh) +{ + is >> rh.mIP; + is >> rh.mPort; + return is; +} diff --git a/indra/llmessage/llhost.h b/indra/llmessage/llhost.h new file mode 100644 index 0000000000..09dbae61b9 --- /dev/null +++ b/indra/llmessage/llhost.h @@ -0,0 +1,133 @@ +/** + * @file llhost.h + * @brief a LLHost uniquely defines a host (Simulator, Proxy or other) + * across the network + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLHOST_H +#define LL_LLHOST_H + +#include +#include + +#include "net.h" + +#include "llstring.h" + +const U32 INVALID_PORT = 0; +const U32 INVALID_HOST_IP_ADDRESS = 0x0; + +class LLHost { +protected: + U32 mPort; + U32 mIP; +public: + + static LLHost invalid; + + // CREATORS + LLHost() + : mPort(INVALID_PORT), + mIP(INVALID_HOST_IP_ADDRESS) + { } // STL's hash_map expect this T() + + LLHost( U32 ipv4_addr, U32 port ) + : mPort( port ) + { + mIP = ipv4_addr; + } + + LLHost( const char *ipv4_addr, U32 port ) + : mPort( port ) + { + mIP = ip_string_to_u32(ipv4_addr); + } + + explicit LLHost(const U64 ip_port) + { + U32 ip = (U32)(ip_port >> 32); + U32 port = (U32)(ip_port & (U64)0xFFFFFFFF); + mIP = ip; + mPort = port; + } + + explicit LLHost(const std::string& ip_and_port); + + ~LLHost() + { } + + // MANIPULATORS + void set( U32 ip, U32 port ) { mIP = ip; mPort = port; } + void set( const char* ipstr, U32 port ) { mIP = ip_string_to_u32(ipstr); mPort = port; } + void setAddress( const char* ipstr ) { mIP = ip_string_to_u32(ipstr); } + void setAddress( U32 ip ) { mIP = ip; } + void setPort( U32 port ) { mPort = port; } + BOOL setHostByName(const char *hname); + + LLHost& operator=(const LLHost &rhs); + void invalidate() { mIP = INVALID_HOST_IP_ADDRESS; mPort = INVALID_PORT;}; + + // READERS + U32 getAddress() const { return mIP; } + U32 getPort() const { return mPort; } + BOOL isOk() const { return (mIP != INVALID_HOST_IP_ADDRESS) && (mPort != INVALID_PORT); } + size_t hash() const { return (mIP << 16) | (mPort & 0xffff); } + void getString(char* buffer, S32 length) const; // writes IP:port into buffer + void getIPString(char* buffer, S32 length) const; // writes IP into buffer + std::string getIPString() const; + void getHostName(char *buf, S32 len) const; + LLString getHostName() const; + std::string getIPandPort() const; + + friend std::ostream& operator<< (std::ostream& os, const LLHost &hh); + friend std::istream& operator>> (std::istream& is, LLHost &hh); + friend bool operator==( const LLHost &lhs, const LLHost &rhs ); + friend bool operator!=( const LLHost &lhs, const LLHost &rhs ); + friend bool operator<(const LLHost &lhs, const LLHost &rhs); +}; + + +// Function Object required for STL templates using LLHost as key +class LLHostHash +{ +public: + size_t operator() (const LLHost &hh) const { return hh.hash(); } +}; + + +inline bool operator==( const LLHost &lhs, const LLHost &rhs ) +{ + return (lhs.mIP == rhs.mIP) && (lhs.mPort == rhs.mPort); +} + +inline bool operator!=( const LLHost &lhs, const LLHost &rhs ) +{ + return (lhs.mIP != rhs.mIP) || (lhs.mPort != rhs.mPort); +} + +inline bool operator<(const LLHost &lhs, const LLHost &rhs) +{ + if (lhs.mIP < rhs.mIP) + { + return true; + } + if (lhs.mIP > rhs.mIP) + { + return false; + } + + if (lhs.mPort < rhs.mPort) + { + return true; + } + else + { + return false; + } +} + + +#endif // LL_LLHOST_H diff --git a/indra/llmessage/llhttpassetstorage.cpp b/indra/llmessage/llhttpassetstorage.cpp new file mode 100644 index 0000000000..12d9d610cc --- /dev/null +++ b/indra/llmessage/llhttpassetstorage.cpp @@ -0,0 +1,996 @@ +/** + * @file llhttpassetstorage.cpp + * @brief Subclass capable of loading asset data to/from an external + * source. Currently, a web server accessed via curl + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llhttpassetstorage.h" + +#include "indra_constants.h" +#include "llvfile.h" +#include "llvfs.h" + +#include "zlib/zlib.h" + +const F32 MAX_PROCESSING_TIME = 0.005f; +const S32 CURL_XFER_BUFFER_SIZE = 65536; +// Try for 30 minutes for now. +const F32 GET_URL_TO_FILE_TIMEOUT = 1800.0f; + +const S32 COMPRESSED_INPUT_BUFFER_SIZE = 4096; + +const S32 HTTP_OK = 200; +const S32 HTTP_PUT_OK = 201; +const S32 HTTP_NO_CONTENT = 204; +const S32 HTTP_MISSING = 404; +const S32 HTTP_SERVER_BAD_GATEWAY = 502; +const S32 HTTP_SERVER_TEMP_UNAVAILABLE = 503; + +///////////////////////////////////////////////////////////////////////////////// +// LLTempAssetData +// An asset not stored on central asset store, but on a simulator node somewhere. +///////////////////////////////////////////////////////////////////////////////// +struct LLTempAssetData +{ + LLUUID mAssetID; + LLUUID mAgentID; + std::string mHostName; +}; + +///////////////////////////////////////////////////////////////////////////////// +// LLHTTPAssetRequest +///////////////////////////////////////////////////////////////////////////////// + +class LLHTTPAssetRequest : public LLAssetRequest +{ +public: + LLHTTPAssetRequest(LLHTTPAssetStorage *asp, const LLUUID &uuid, LLAssetType::EType type, const char *url, CURLM *curl_multi); + virtual ~LLHTTPAssetRequest(); + + void setupCurlHandle(); + + void prepareCompressedUpload(); + void finishCompressedUpload(); + size_t readCompressedData(void* data, size_t size); + + static size_t curlCompressedUploadCallback( + void *data, size_t size, size_t nmemb, void *user_data); + +public: + LLHTTPAssetStorage *mAssetStoragep; + + CURL *mCurlHandle; + CURLM *mCurlMultiHandle; + char *mURLBuffer; + struct curl_slist *mHTTPHeaders; + LLVFile *mVFile; + LLUUID mTmpUUID; + BOOL mIsUpload; + BOOL mIsLocalUpload; + BOOL mIsDownload; + + bool mZInitialized; + z_stream mZStream; + char* mZInputBuffer; + bool mZInputExhausted; + + FILE *mFP; +}; + + +LLHTTPAssetRequest::LLHTTPAssetRequest(LLHTTPAssetStorage *asp, const LLUUID &uuid, LLAssetType::EType type, const char *url, CURLM *curl_multi) + : LLAssetRequest(uuid, type), + mZInitialized(false) +{ + mAssetStoragep = asp; + mCurlHandle = NULL; + mCurlMultiHandle = curl_multi; + mVFile = NULL; + mIsUpload = FALSE; + mIsLocalUpload = FALSE; + mIsDownload = FALSE; + mHTTPHeaders = NULL; + + mURLBuffer = new char[strlen(url) + 1]; /*Flawfinder: ignore*/ + if (mURLBuffer) + { + strcpy(mURLBuffer, url); + } +} + +LLHTTPAssetRequest::~LLHTTPAssetRequest() +{ + // Cleanup/cancel the request + if (mCurlHandle) + { + curl_multi_remove_handle(mCurlMultiHandle, mCurlHandle); + curl_easy_cleanup(mCurlHandle); + if (mAssetStoragep) + { + // Terminating a request. Thus upload or download is no longer pending. + if (mIsUpload) + { + mAssetStoragep->clearPendingUpload(); + } + else if (mIsLocalUpload) + { + mAssetStoragep->clearPendingLocalUpload(); + } + else if (mIsDownload) + { + mAssetStoragep->clearPendingDownload(); + } + else + { + llerrs << "LLHTTPAssetRequest::~LLHTTPAssetRequest - Destroyed request is not upload OR download, this is bad!" << llendl; + } + } + else + { + llerrs << "LLHTTPAssetRequest::~LLHTTPAssetRequest - No asset storage associated with this request!" << llendl; + } + } + if (mHTTPHeaders) + { + curl_slist_free_all(mHTTPHeaders); + } + delete[] mURLBuffer; + delete mVFile; + finishCompressedUpload(); +} + +void LLHTTPAssetRequest::setupCurlHandle() +{ + mCurlHandle = curl_easy_init(); + curl_easy_setopt(mCurlHandle, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(mCurlHandle, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(mCurlHandle, CURLOPT_URL, mURLBuffer); + curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, this); + if (mIsDownload) + { + curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, ""); + // only do this on downloads, as uploads + // to some apache configs (like our test grids) + // mistakenly claim the response is gzip'd if the resource + // name ends in .gz, even though in a PUT, the response is + // just plain HTML saying "created" + } + /* Remove the Pragma: no-cache header that libcurl inserts by default; + we want the cached version, if possible. */ + if (mZInitialized) + { + curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, ""); + // disable use of proxy, which can't handle chunked transfers + } + mHTTPHeaders = curl_slist_append(mHTTPHeaders, "Pragma:"); + // resist the temptation to explicitly add the Transfer-Encoding: chunked + // header here - invokes a libCURL bug + curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mHTTPHeaders); + if (mAssetStoragep) + { + // Set the appropriate pending upload or download flag + if (mIsUpload) + { + mAssetStoragep->setPendingUpload(); + } + else if (mIsLocalUpload) + { + mAssetStoragep->setPendingLocalUpload(); + } + else if (mIsDownload) + { + mAssetStoragep->setPendingDownload(); + } + else + { + llerrs << "LLHTTPAssetRequest::setupCurlHandle - Request is not upload OR download, this is bad!" << llendl; + } + } + else + { + llerrs << "LLHTTPAssetRequest::setupCurlHandle - No asset storage associated with this request!" << llendl; + } +} + +void LLHTTPAssetRequest::prepareCompressedUpload() +{ + mZStream.next_in = Z_NULL; + mZStream.avail_in = 0; + mZStream.zalloc = Z_NULL; + mZStream.zfree = Z_NULL; + mZStream.opaque = Z_NULL; + + int r = deflateInit2(&mZStream, + 1, // compression level + Z_DEFLATED, // the only method defined + 15 + 16, // the default windowBits + gzip header flag + 8, // the default memLevel + Z_DEFAULT_STRATEGY); + + if (r != Z_OK) + { + llerrs << "LLHTTPAssetRequest::prepareCompressedUpload defalateInit2() failed" << llendl; + } + + mZInitialized = true; + mZInputBuffer = new char[COMPRESSED_INPUT_BUFFER_SIZE]; + mZInputExhausted = false; + + mVFile = new LLVFile(gAssetStorage->mVFS, + getUUID(), getType(), LLVFile::READ); +} + +void LLHTTPAssetRequest::finishCompressedUpload() +{ + if (mZInitialized) + { + llinfos << "LLHTTPAssetRequest::finishCompressedUpload: " + << "read " << mZStream.total_in << " byte asset file, " + << "uploaded " << mZStream.total_out << " byte compressed asset" + << llendl; + + deflateEnd(&mZStream); + delete[] mZInputBuffer; + } +} + +size_t LLHTTPAssetRequest::readCompressedData(void* data, size_t size) +{ + mZStream.next_out = (Bytef*)data; + mZStream.avail_out = size; + + while (mZStream.avail_out > 0) + { + if (mZStream.avail_in == 0 && !mZInputExhausted) + { + S32 to_read = llmin(COMPRESSED_INPUT_BUFFER_SIZE, + (S32)(mVFile->getSize() - mVFile->tell())); + + mVFile->read((U8*)mZInputBuffer, to_read); /*Flawfinder: ignore*/ + + mZStream.next_in = (Bytef*)mZInputBuffer; + mZStream.avail_in = mVFile->getLastBytesRead(); + + mZInputExhausted = mZStream.avail_in == 0; + } + + int r = deflate(&mZStream, + mZInputExhausted ? Z_FINISH : Z_NO_FLUSH); + + if (r == Z_STREAM_END) + { + break; + } + } + + return size - mZStream.avail_out; +} + +//static +size_t LLHTTPAssetRequest::curlCompressedUploadCallback( + void *data, size_t size, size_t nmemb, void *user_data) +{ + if (!gAssetStorage) + { + return 0; + } + CURL *curl_handle = (CURL *)user_data; + LLHTTPAssetRequest *req = NULL; + curl_easy_getinfo(curl_handle, CURLINFO_PRIVATE, &req); + + return req->readCompressedData(data, size * nmemb); +} + +///////////////////////////////////////////////////////////////////////////////// +// LLHTTPAssetStorage +///////////////////////////////////////////////////////////////////////////////// + + +LLHTTPAssetStorage::LLHTTPAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, + LLVFS *vfs, const LLHost &upstream_host, + const char *web_host, + const char *local_web_host, + const char *host_name) + : LLAssetStorage(msg, xfer, vfs, upstream_host) +{ + _init(web_host, local_web_host, host_name); +} + +LLHTTPAssetStorage::LLHTTPAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, + LLVFS *vfs, + const char *web_host, + const char *local_web_host, + const char *host_name) + : LLAssetStorage(msg, xfer, vfs) +{ + _init(web_host, local_web_host, host_name); +} + +void LLHTTPAssetStorage::_init(const char *web_host, const char *local_web_host, const char* host_name) +{ + mBaseURL = web_host; + mLocalBaseURL = local_web_host; + mHostName = host_name; + + // Do not change this "unless you are familiar with and mean to control + // internal operations of libcurl" + // - http://curl.haxx.se/libcurl/c/curl_global_init.html + curl_global_init(CURL_GLOBAL_ALL); + + mCurlMultiHandle = curl_multi_init(); + + mPendingDownload = FALSE; + mPendingUpload = FALSE; + mPendingLocalUpload = FALSE; +} + +LLHTTPAssetStorage::~LLHTTPAssetStorage() +{ + curl_multi_cleanup(mCurlMultiHandle); + mCurlMultiHandle = NULL; + + curl_global_cleanup(); +} + +// storing data is simpler than getting it, so we just overload the whole method +void LLHTTPAssetStorage::storeAssetData( + const LLUUID& uuid, + LLAssetType::EType type, + LLAssetStorage::LLStoreAssetCallback callback, + void* user_data, + bool temp_file, + bool is_priority, + bool store_local, + const LLUUID& requesting_agent_id) +{ + if (mVFS->getExists(uuid, type)) + { + LLAssetRequest *req = new LLAssetRequest(uuid, type); + req->mUpCallback = callback; + req->mUserData = user_data; + req->mRequestingAgentID = requesting_agent_id; + + // this will get picked up and transmitted in checkForTimeouts + if(store_local) + { + mPendingLocalUploads.push_back(req); + } + else if(is_priority) + { + mPendingUploads.push_front(req); + } + else + { + mPendingUploads.push_back(req); + } + } + else + { + llwarns << "AssetStorage: attempt to upload non-existent vfile " << uuid << ":" << LLAssetType::lookup(type) << llendl; + if (callback) + { + callback(uuid, user_data, LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE ); + } + } +} + +// virtual +void LLHTTPAssetStorage::storeAssetData( + const char* filename, + const LLUUID& asset_id, + LLAssetType::EType asset_type, + LLStoreAssetCallback callback, + void* user_data, + bool temp_file, + bool is_priority) +{ + llinfos << "LLAssetStorage::storeAssetData (legacy)" << asset_id << ":" << LLAssetType::lookup(asset_type) << llendl; + + LLLegacyAssetRequest *legacy = new LLLegacyAssetRequest; + + legacy->mUpCallback = callback; + legacy->mUserData = user_data; + + FILE *fp = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/ + if (fp) + { + LLVFile file(mVFS, asset_id, asset_type, LLVFile::WRITE); + + fseek(fp, 0, SEEK_END); + S32 size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + file.setMaxSize(size); + + const S32 buf_size = 65536; + U8 copy_buf[buf_size]; + while ((size = (S32)fread(copy_buf, 1, buf_size, fp))) + { + file.write(copy_buf, size); + } + fclose(fp); + + // if this upload fails, the caller needs to setup a new tempfile for us + if (temp_file) + { + LLFile::remove(filename); + } + + storeAssetData( + asset_id, + asset_type, + legacyStoreDataCallback, + (void**)legacy, + temp_file, + is_priority); + } + else + { + if (callback) + { + callback(LLUUID::null, user_data, LL_ERR_CANNOT_OPEN_FILE); + } + } +} + +// internal requester, used by getAssetData in superclass +void LLHTTPAssetStorage::_queueDataRequest(const LLUUID& uuid, LLAssetType::EType type, + void (*callback)(LLVFS *vfs, const LLUUID&, LLAssetType::EType, void *, S32), + void *user_data, BOOL duplicate, + BOOL is_priority) +{ + // stash the callback info so we can find it after we get the response message + LLAssetRequest *req = new LLAssetRequest(uuid, type); + req->mDownCallback = callback; + req->mUserData = user_data; + req->mIsPriority = is_priority; + + // this will get picked up and downloaded in checkForTimeouts + + // + // HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACK! Asset requests were taking too long and timing out. + // Since texture requests are the LEAST sensitive (on the simulator) to being delayed, add + // non-texture requests to the front, and add texture requests to the back. The theory is + // that we always want them first, even if they're out of order. + // + + if (req->getType() == LLAssetType::AT_TEXTURE) + { + mPendingDownloads.push_back(req); + } + else + { + mPendingDownloads.push_front(req); + } +} + +// overloaded to additionally move data to/from the webserver +void LLHTTPAssetStorage::checkForTimeouts() +{ + LLAssetRequest *req = NULL; + if (mPendingDownloads.size() > 0 && !mPendingDownload) + { + req = mPendingDownloads.front(); + // Setup this curl download request + // We need to generate a new request here + // since the one in the list could go away + char tmp_url[MAX_STRING]; /*Flawfinder: ignore*/ + char uuid_str[UUID_STR_LENGTH]; /*Flawfinder: ignore*/ + req->getUUID().toString(uuid_str); + std::string base_url = getBaseURL(req->getUUID(), req->getType()); + snprintf(tmp_url, sizeof(tmp_url), "%s/%36s.%s", base_url.c_str() , uuid_str, LLAssetType::lookup(req->getType())); /*Flawfinder: ignore*/ + + LLHTTPAssetRequest *new_req = new LLHTTPAssetRequest(this, req->getUUID(), req->getType(), tmp_url, mCurlMultiHandle); + new_req->mTmpUUID.generate(); + new_req->mIsDownload = TRUE; + + // Sets pending download flag internally + new_req->setupCurlHandle(); + curl_easy_setopt(new_req->mCurlHandle, CURLOPT_FOLLOWLOCATION, TRUE); + curl_easy_setopt(new_req->mCurlHandle, CURLOPT_WRITEFUNCTION, &curlDownCallback); + curl_easy_setopt(new_req->mCurlHandle, CURLOPT_WRITEDATA, new_req->mCurlHandle); + + curl_multi_add_handle(mCurlMultiHandle, new_req->mCurlHandle); + llinfos << "Requesting " << new_req->mURLBuffer << llendl; + + } + + + if (mPendingUploads.size() > 0 && !mPendingUpload) + { + req = mPendingUploads.front(); + // setup this curl upload request + + bool do_compress = req->getType() == LLAssetType::AT_OBJECT; + + char tmp_url[MAX_STRING];/*Flawfinder: ignore*/ + char uuid_str[UUID_STR_LENGTH];/*Flawfinder: ignore*/ + req->getUUID().toString(uuid_str); + snprintf(tmp_url, sizeof(tmp_url), /*Flawfinder: ignore*/ + do_compress ? "%s/%s.%s.gz" : "%s/%s.%s", + mBaseURL.c_str(), uuid_str, LLAssetType::lookup(req->getType())); + + LLHTTPAssetRequest *new_req = new LLHTTPAssetRequest(this, req->getUUID(), req->getType(), tmp_url, mCurlMultiHandle); + new_req->mIsUpload = TRUE; + if (do_compress) + { + new_req->prepareCompressedUpload(); + } + + // Sets pending upload flag internally + new_req->setupCurlHandle(); + curl_easy_setopt(new_req->mCurlHandle, CURLOPT_UPLOAD, 1); + curl_easy_setopt(new_req->mCurlHandle, CURLOPT_WRITEFUNCTION, &nullOutputCallback); + + if (do_compress) + { + curl_easy_setopt(new_req->mCurlHandle, CURLOPT_READFUNCTION, + &LLHTTPAssetRequest::curlCompressedUploadCallback); + } + else + { + LLVFile file(mVFS, req->getUUID(), req->getType()); + curl_easy_setopt(new_req->mCurlHandle, CURLOPT_INFILESIZE, file.getSize()); + curl_easy_setopt(new_req->mCurlHandle, CURLOPT_READFUNCTION, + &curlUpCallback); + } + curl_easy_setopt(new_req->mCurlHandle, CURLOPT_READDATA, new_req->mCurlHandle); + + curl_multi_add_handle(mCurlMultiHandle, new_req->mCurlHandle); + llinfos << "Requesting PUT " << new_req->mURLBuffer << llendl; + // Pending upload will have been flagged by the request + } + + + if (mPendingLocalUploads.size() > 0 && !mPendingLocalUpload) + { + req = mPendingLocalUploads.front(); + // setup this curl upload request + LLVFile file(mVFS, req->getUUID(), req->getType()); + + char tmp_url[MAX_STRING]; /*Flawfinder: ignore*/ + char uuid_str[UUID_STR_LENGTH]; /*Flawfinder: ignore*/ + req->getUUID().toString(uuid_str); + + // KLW - All temporary uploads are saved locally "http://localhost:12041/asset" + snprintf(tmp_url, sizeof(tmp_url), "%s/%36s.%s", mLocalBaseURL.c_str(), uuid_str, LLAssetType::lookup(req->getType())); /*Flawfinder: ignore*/ + + LLHTTPAssetRequest *new_req = new LLHTTPAssetRequest(this, req->getUUID(), req->getType(), tmp_url, mCurlMultiHandle); + new_req->mIsLocalUpload = TRUE; + new_req->mRequestingAgentID = req->mRequestingAgentID; + + // Sets pending upload flag internally + new_req->setupCurlHandle(); + curl_easy_setopt(new_req->mCurlHandle, CURLOPT_PUT, 1); + curl_easy_setopt(new_req->mCurlHandle, CURLOPT_INFILESIZE, file.getSize()); + curl_easy_setopt(new_req->mCurlHandle, CURLOPT_WRITEFUNCTION, &nullOutputCallback); + curl_easy_setopt(new_req->mCurlHandle, CURLOPT_READFUNCTION, &curlUpCallback); + curl_easy_setopt(new_req->mCurlHandle, CURLOPT_READDATA, new_req->mCurlHandle); + + curl_multi_add_handle(mCurlMultiHandle, new_req->mCurlHandle); + llinfos << "TAT: LLHTTPAssetStorage::checkForTimeouts() : pending local!" + << " Requesting PUT " << new_req->mURLBuffer << llendl; + // Pending upload will have been flagged by the request + } + S32 count = 0; + CURLMcode mcode; + int queue_length; + do + { + mcode = curl_multi_perform(mCurlMultiHandle, &queue_length); + count++; + } while (mcode == CURLM_CALL_MULTI_PERFORM && (count < 5)); + + CURLMsg *curl_msg; + do + { + curl_msg = curl_multi_info_read(mCurlMultiHandle, &queue_length); + if (curl_msg && curl_msg->msg == CURLMSG_DONE) + { + long curl_result = 0; + S32 xfer_result = 0; + + LLHTTPAssetRequest *req = NULL; + curl_easy_getinfo(curl_msg->easy_handle, CURLINFO_PRIVATE, &req); + + curl_easy_getinfo(curl_msg->easy_handle, CURLINFO_HTTP_CODE, &curl_result); + if (req->mIsUpload || req->mIsLocalUpload) + { + if (curl_msg->data.result == CURLE_OK && (curl_result == HTTP_OK || curl_result == HTTP_PUT_OK || curl_result == HTTP_NO_CONTENT)) + { + llinfos << "Success uploading " << req->getUUID() << " to " << req->mURLBuffer << llendl; + if (req->mIsLocalUpload) + { + addTempAssetData(req->getUUID(), req->mRequestingAgentID, mHostName); + } + } + else if (curl_msg->data.result == CURLE_COULDNT_CONNECT || + curl_msg->data.result == CURLE_OPERATION_TIMEOUTED || + curl_result == HTTP_SERVER_BAD_GATEWAY || + curl_result == HTTP_SERVER_TEMP_UNAVAILABLE) + { + llwarns << "Re-requesting upload for " << req->getUUID() << ". Received upload error to " << req->mURLBuffer << + " with result " << curl_easy_strerror(curl_msg->data.result) << ", http result " << curl_result << llendl; + } + else + { + llwarns << "Failure uploading " << req->getUUID() << " to " << req->mURLBuffer << + " with result " << curl_easy_strerror(curl_msg->data.result) << ", http result " << curl_result << llendl; + + xfer_result = LL_ERR_ASSET_REQUEST_FAILED; + } + + if (!(curl_msg->data.result == CURLE_COULDNT_CONNECT || + curl_msg->data.result == CURLE_OPERATION_TIMEOUTED || + curl_result == HTTP_SERVER_BAD_GATEWAY || + curl_result == HTTP_SERVER_TEMP_UNAVAILABLE)) + { + // shared upload finished callback + // in the base class, this is called from processUploadComplete + _callUploadCallbacks(req->getUUID(), req->getType(), (xfer_result == 0)); + // Pending upload flag will get cleared when the request is deleted + } + } + else if (req->mIsDownload) + { + if (curl_result == HTTP_OK && curl_msg->data.result == CURLE_OK) + { + if (req->mVFile && req->mVFile->getSize() > 0) + { + llinfos << "Success downloading " << req->mURLBuffer << ", size " << req->mVFile->getSize() << llendl; + + req->mVFile->rename(req->getUUID(), req->getType()); + } + else + { + // TODO: if this actually indicates a bad asset on the server + // (not certain at this point), then delete it + llwarns << "Found " << req->mURLBuffer << " to be zero size" << llendl; + xfer_result = LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE; + } + } + else + { + // KLW - TAT See if an avatar owns this texture, and if so request re-upload. + llwarns << "Failure downloading " << req->mURLBuffer << + " with result " << curl_easy_strerror(curl_msg->data.result) << ", http result " << curl_result << llendl; + + xfer_result = (curl_result == HTTP_MISSING) ? LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE : LL_ERR_ASSET_REQUEST_FAILED; + + if (req->mVFile) + { + req->mVFile->remove(); + } + } + + // call the static callback for transfer completion + // this will cleanup all requests for this asset, including ours + downloadCompleteCallback(xfer_result, (void *)req); + // Pending download flag will get cleared when the request is deleted + } + else + { + // nothing, just axe this request + // currently this can only mean an asset delete + } + + // Deleting clears the pending upload/download flag if it's set and the request is transferring + delete req; + req = NULL; + } + + } while (curl_msg && queue_length > 0); + + + LLAssetStorage::checkForTimeouts(); +} + +// static +size_t LLHTTPAssetStorage::curlDownCallback(void *data, size_t size, size_t nmemb, void *user_data) +{ + if (!gAssetStorage) + { + llwarns << "Missing gAssetStorage, aborting curl download callback!" << llendl; + return 0; + } + S32 bytes = (S32)(size * nmemb); + CURL *curl_handle = (CURL *)user_data; + LLHTTPAssetRequest *req = NULL; + curl_easy_getinfo(curl_handle, CURLINFO_PRIVATE, &req); + + if (! req->mVFile) + { + req->mVFile = new LLVFile(gAssetStorage->mVFS, req->mTmpUUID, LLAssetType::AT_NONE, LLVFile::APPEND); + } + + double content_length = 0.0; + curl_easy_getinfo(curl_handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &content_length); + + // sanitize content_length, reconcile w/ actual data + S32 file_length = llmax(0, (S32)llmin(content_length, 20000000.0), bytes + req->mVFile->getSize()); + + req->mVFile->setMaxSize(file_length); + req->mVFile->write((U8*)data, bytes); + + return nmemb; +} + +// static +size_t LLHTTPAssetStorage::curlUpCallback(void *data, size_t size, size_t nmemb, void *user_data) +{ + if (!gAssetStorage) + { + llwarns << "Missing gAssetStorage, aborting curl download callback!" << llendl; + return 0; + } + CURL *curl_handle = (CURL *)user_data; + LLHTTPAssetRequest *req = NULL; + curl_easy_getinfo(curl_handle, CURLINFO_PRIVATE, &req); + + if (! req->mVFile) + { + req->mVFile = new LLVFile(gAssetStorage->mVFS, req->getUUID(), req->getType(), LLVFile::READ); + } + + S32 bytes = llmin((S32)(size * nmemb), (S32)(req->mVFile->getSize() - req->mVFile->tell())); + + req->mVFile->read((U8*)data, bytes);/*Flawfinder: ignore*/ + + return req->mVFile->getLastBytesRead(); +} + +// static +size_t LLHTTPAssetStorage::nullOutputCallback(void *data, size_t size, size_t nmemb, void *user_data) +{ + // do nothing, this is here to soak up script output so it doesn't end up on stdout + + return nmemb; +} + + + +// blocking asset fetch which bypasses the VFS +// this is a very limited function for use by the simstate loader and other one-offs +S32 LLHTTPAssetStorage::getURLToFile(const LLUUID& uuid, LLAssetType::EType asset_type, const LLString &url, const char *filename, progress_callback callback, void *userdata) +{ + // FIXME: There is no guarantee that the uuid and the asset_type match + // - not that it matters. - Doug + lldebugs << "LLHTTPAssetStorage::getURLToFile() - " << url << llendl; + + FILE *fp = LLFile::fopen(filename, "wb"); /*Flawfinder: ignore*/ + if (! fp) + { + llwarns << "Failed to open " << filename << " for writing" << llendl; + return LL_ERR_ASSET_REQUEST_FAILED; + } + + // make sure we use the normal curl setup, even though we don't really need a request object + LLHTTPAssetRequest req(this, uuid, asset_type, url.c_str(), mCurlMultiHandle); + req.mFP = fp; + req.mIsDownload = TRUE; + + req.setupCurlHandle(); + curl_easy_setopt(req.mCurlHandle, CURLOPT_FOLLOWLOCATION, TRUE); + curl_easy_setopt(req.mCurlHandle, CURLOPT_WRITEFUNCTION, &curlFileDownCallback); + curl_easy_setopt(req.mCurlHandle, CURLOPT_WRITEDATA, req.mCurlHandle); + + curl_multi_add_handle(mCurlMultiHandle, req.mCurlHandle); + llinfos << "Requesting as file " << req.mURLBuffer << llendl; + + // braindead curl loop + int queue_length; + CURLMsg *curl_msg; + LLTimer timeout; + timeout.setTimerExpirySec(GET_URL_TO_FILE_TIMEOUT); + bool success = false; + S32 xfer_result = 0; + do + { + curl_multi_perform(mCurlMultiHandle, &queue_length); + curl_msg = curl_multi_info_read(mCurlMultiHandle, &queue_length); + + if (callback) + { + callback(userdata); + } + + if ( curl_msg && (CURLMSG_DONE == curl_msg->msg) ) + { + success = true; + } + else if (timeout.hasExpired()) + { + llwarns << "Request for " << url << " has timed out." << llendl; + success = false; + xfer_result = LL_ERR_ASSET_REQUEST_FAILED; + break; + } + } while (!success); + + if (success) + { + long curl_result = 0; + curl_easy_getinfo(curl_msg->easy_handle, CURLINFO_HTTP_CODE, &curl_result); + + if (curl_result == HTTP_OK && curl_msg->data.result == CURLE_OK) + { + S32 size = ftell(req.mFP); + if (size > 0) + { + // everything seems to be in order + llinfos << "Success downloading " << req.mURLBuffer << " to file, size " << size << llendl; + } + else + { + llwarns << "Found " << req.mURLBuffer << " to be zero size" << llendl; + xfer_result = LL_ERR_ASSET_REQUEST_FAILED; + } + } + else + { + xfer_result = curl_result == HTTP_MISSING ? LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE : LL_ERR_ASSET_REQUEST_FAILED; + llinfos << "Failure downloading " << req.mURLBuffer << + " with result " << curl_easy_strerror(curl_msg->data.result) << ", http result " << curl_result << llendl; + } + } + + fclose(fp); + if (xfer_result) + { + LLFile::remove(filename); + } + return xfer_result; +} + + +// static +size_t LLHTTPAssetStorage::curlFileDownCallback(void *data, size_t size, size_t nmemb, void *user_data) +{ + CURL *curl_handle = (CURL *)user_data; + LLHTTPAssetRequest *req = NULL; + curl_easy_getinfo(curl_handle, CURLINFO_PRIVATE, &req); + + if (! req->mFP) + { + llwarns << "Missing mFP, aborting curl file download callback!" << llendl; + return 0; + } + + return fwrite(data, size, nmemb, req->mFP); +} + +// virtual +void LLHTTPAssetStorage::addTempAssetData(const LLUUID& asset_id, const LLUUID& agent_id, const std::string& host_name) +{ + if (agent_id.isNull() || asset_id.isNull()) + { + llwarns << "TAT: addTempAssetData bad id's asset_id: " << asset_id << " agent_id: " << agent_id << llendl; + return; + } + + LLTempAssetData temp_asset_data; + temp_asset_data.mAssetID = asset_id; + temp_asset_data.mAgentID = agent_id; + temp_asset_data.mHostName = host_name; + + mTempAssets[asset_id] = temp_asset_data; +} + +// virtual +BOOL LLHTTPAssetStorage::hasTempAssetData(const LLUUID& texture_id) const +{ + uuid_tempdata_map::const_iterator citer = mTempAssets.find(texture_id); + BOOL found = (citer != mTempAssets.end()); + return found; +} + +// virtual +std::string LLHTTPAssetStorage::getTempAssetHostName(const LLUUID& texture_id) const +{ + uuid_tempdata_map::const_iterator citer = mTempAssets.find(texture_id); + if (citer != mTempAssets.end()) + { + return citer->second.mHostName; + } + else + { + return std::string(); + } +} + +// virtual +LLUUID LLHTTPAssetStorage::getTempAssetAgentID(const LLUUID& texture_id) const +{ + uuid_tempdata_map::const_iterator citer = mTempAssets.find(texture_id); + if (citer != mTempAssets.end()) + { + return citer->second.mAgentID; + } + else + { + return LLUUID::null; + } +} + +// virtual +void LLHTTPAssetStorage::removeTempAssetData(const LLUUID& asset_id) +{ + mTempAssets.erase(asset_id); +} + +// virtual +void LLHTTPAssetStorage::removeTempAssetDataByAgentID(const LLUUID& agent_id) +{ + uuid_tempdata_map::iterator it = mTempAssets.begin(); + uuid_tempdata_map::iterator end = mTempAssets.end(); + + while (it != end) + { + const LLTempAssetData& asset_data = it->second; + if (asset_data.mAgentID == agent_id) + { + mTempAssets.erase(it++); + } + else + { + ++it; + } + } +} + +std::string LLHTTPAssetStorage::getBaseURL(const LLUUID& asset_id, LLAssetType::EType asset_type) +{ + if (LLAssetType::AT_TEXTURE == asset_type) + { + uuid_tempdata_map::const_iterator citer = mTempAssets.find(asset_id); + if (citer != mTempAssets.end()) + { + const std::string& host_name = citer->second.mHostName; + std::string url = llformat(LOCAL_ASSET_URL_FORMAT, host_name.c_str()); + return url; + } + } + + return mBaseURL; +} + +void LLHTTPAssetStorage::dumpTempAssetData(const LLUUID& avatar_id) const +{ + uuid_tempdata_map::const_iterator it = mTempAssets.begin(); + uuid_tempdata_map::const_iterator end = mTempAssets.end(); + S32 count = 0; + for ( ; it != end; ++it) + { + const LLTempAssetData& temp_asset_data = it->second; + if (avatar_id.isNull() + || avatar_id == temp_asset_data.mAgentID) + { + llinfos << "TAT: dump agent " << temp_asset_data.mAgentID + << " texture " << temp_asset_data.mAssetID + << " host " << temp_asset_data.mHostName + << llendl; + count++; + } + } + + if (avatar_id.isNull()) + { + llinfos << "TAT: dumped " << count << " entries for all avatars" << llendl; + } + else + { + llinfos << "TAT: dumped " << count << " entries for avatar " << avatar_id << llendl; + } +} + +void LLHTTPAssetStorage::clearTempAssetData() +{ + llinfos << "TAT: Clearing temp asset data map" << llendl; + mTempAssets.clear(); +} + diff --git a/indra/llmessage/llhttpassetstorage.h b/indra/llmessage/llhttpassetstorage.h new file mode 100644 index 0000000000..a6ba5c795c --- /dev/null +++ b/indra/llmessage/llhttpassetstorage.h @@ -0,0 +1,116 @@ +/** + * @file llhttpassetstorage.h + * @brief Class for loading asset data to/from an external source over http. + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LLHTTPASSETSTORAGE_H +#define LLHTTPASSETSTORAGE_H + +#include "llassetstorage.h" +#include "curl/curl.h" + +class LLVFile; +typedef void (*progress_callback)(void* userdata); + +struct LLTempAssetData; + +typedef std::map uuid_tempdata_map; + +class LLHTTPAssetStorage : public LLAssetStorage +{ +public: + LLHTTPAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, + LLVFS *vfs, const LLHost &upstream_host, + const char *web_host, + const char *local_web_host, + const char *host_name); + + LLHTTPAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, + LLVFS *vfs, + const char *web_host, + const char *local_web_host, + const char *host_name); + + + virtual ~LLHTTPAssetStorage(); + + virtual void storeAssetData( + const LLUUID& uuid, + LLAssetType::EType atype, + LLStoreAssetCallback callback, + void* user_data, + bool temp_file = false, + bool is_priority = false, + bool store_local = false, + const LLUUID& requesting_agent_id = LLUUID::null); + + virtual void storeAssetData( + const char* filename, + const LLUUID& asset_id, + LLAssetType::EType atype, + LLStoreAssetCallback callback, + void* user_data, + bool temp_file, + bool is_priority); + + // Hack. One off curl download an URL to a file. Probably should be elsewhere. + // Only used by lldynamicstate. The API is broken, and should be replaced with + // a generic HTTP file fetch - Doug 9/25/06 + S32 getURLToFile(const LLUUID& uuid, LLAssetType::EType asset_type, const LLString &url, const char *filename, progress_callback callback, void *userdata); + + void checkForTimeouts(); + + static size_t curlDownCallback(void *data, size_t size, size_t nmemb, void *user_data); + static size_t curlFileDownCallback(void *data, size_t size, size_t nmemb, void *user_data); + static size_t curlUpCallback(void *data, size_t size, size_t nmemb, void *user_data); + static size_t nullOutputCallback(void *data, size_t size, size_t nmemb, void *user_data); + + // Should only be used by the LLHTTPAssetRequest + void setPendingUpload() { mPendingUpload = TRUE; } + void setPendingLocalUpload() { mPendingLocalUpload = TRUE; } + void setPendingDownload() { mPendingDownload = TRUE; } + void clearPendingUpload() { mPendingUpload = FALSE; } + void clearPendingLocalUpload() { mPendingLocalUpload = FALSE; } + void clearPendingDownload() { mPendingDownload = FALSE; } + + // Temp assets are stored on sim nodes, they have agent ID and location data associated with them. + virtual void addTempAssetData(const LLUUID& asset_id, const LLUUID& agent_id, const std::string& host_name); + virtual BOOL hasTempAssetData(const LLUUID& texture_id) const; + virtual std::string getTempAssetHostName(const LLUUID& texture_id) const; + virtual LLUUID getTempAssetAgentID(const LLUUID& texture_id) const; + virtual void removeTempAssetData(const LLUUID& asset_id); + virtual void removeTempAssetDataByAgentID(const LLUUID& agent_id); + + // Pass LLUUID::null for all + virtual void dumpTempAssetData(const LLUUID& avatar_id) const; + virtual void clearTempAssetData(); + +protected: + void _queueDataRequest(const LLUUID& uuid, LLAssetType::EType type, + void (*callback)(LLVFS *vfs, const LLUUID&, LLAssetType::EType, void *, S32), + void *user_data, BOOL duplicate, BOOL is_priority); + +private: + void _init(const char *web_host, const char *local_web_host, const char* host_name); + + // This will return the correct base URI for any http asset request + std::string getBaseURL(const LLUUID& asset_id, LLAssetType::EType asset_type); + +protected: + std::string mBaseURL; + std::string mLocalBaseURL; + std::string mHostName; + + CURLM *mCurlMultiHandle; + + BOOL mPendingDownload; + BOOL mPendingUpload; + BOOL mPendingLocalUpload; + + uuid_tempdata_map mTempAssets; +}; + +#endif diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp new file mode 100644 index 0000000000..38dee26723 --- /dev/null +++ b/indra/llmessage/llhttpclient.cpp @@ -0,0 +1,278 @@ +/** + * @file llhttpclient.cpp + * @brief Implementation of classes for making HTTP requests. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llassetstorage.h" +#include "llhttpclient.h" +#include "lliopipe.h" +#include "llurlrequest.h" +#include "llbufferstream.h" +#include "llsdserialize.h" +#include "llvfile.h" +#include "llvfs.h" + +static const F32 HTTP_REQUEST_EXPIRY_SECS = 60.0f; + +static std::string gCABundle; + + + + +LLHTTPClient::Responder::Responder() + : mReferenceCount(0) +{ +} + +LLHTTPClient::Responder::~Responder() +{ +} + +// virtual +void LLHTTPClient::Responder::error(U32 status, const std::string& reason) +{ + llinfos << "LLHTTPClient::Responder::error " + << status << ": " << reason << llendl; +} + +// virtual +void LLHTTPClient::Responder::result(const LLSD& content) +{ +} + +// virtual +void LLHTTPClient::Responder::completed(U32 status, const std::string& reason, const LLSD& content) +{ + if (200 <= status && status < 300) + { + result(content); + } + else + { + error(status, reason); + } +} + + +namespace +{ + class LLHTTPClientURLAdaptor : public LLURLRequestComplete + { + public: + LLHTTPClientURLAdaptor(LLHTTPClient::ResponderPtr responder) + : mResponder(responder), + mStatus(499), mReason("LLURLRequest complete w/no status") + { + } + + ~LLHTTPClientURLAdaptor() + { + } + + virtual void httpStatus(U32 status, const std::string& reason) + { + mStatus = status; + mReason = reason; + } + + virtual void complete(const LLChannelDescriptors& channels, + const buffer_ptr_t& buffer) + { + LLBufferStream istr(channels, buffer.get()); + LLSD content; + + if (200 <= mStatus && mStatus < 300) + { + LLSDSerialize::fromXML(content, istr); + } + + if (mResponder.get()) + { + mResponder->completed(mStatus, mReason, content); + } + } + + private: + LLHTTPClient::ResponderPtr mResponder; + U32 mStatus; + std::string mReason; + }; + + class Injector : public LLIOPipe + { + public: + virtual const char* contentType() = 0; + }; + + class LLSDInjector : public Injector + { + public: + LLSDInjector(const LLSD& sd) : mSD(sd) {} + virtual ~LLSDInjector() {} + + const char* contentType() { return "application/xml"; } + + virtual EStatus process_impl(const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump) + { + LLBufferStream ostream(channels, buffer.get()); + LLSDSerialize::toXML(mSD, ostream); + eos = true; + return STATUS_DONE; + } + + const LLSD mSD; + }; + + class FileInjector : public Injector + { + public: + FileInjector(const std::string& filename) : mFilename(filename) {} + virtual ~FileInjector() {} + + const char* contentType() { return "application/octet-stream"; } + + virtual EStatus process_impl(const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump) + { + LLBufferStream ostream(channels, buffer.get()); + + llifstream fstream(mFilename.c_str(), std::iostream::binary | std::iostream::out); + fstream.seekg(0, std::ios::end); + U32 fileSize = fstream.tellg(); + fstream.seekg(0, std::ios::beg); + char* fileBuffer; + fileBuffer = new char [fileSize]; + fstream.read(fileBuffer, fileSize); + ostream.write(fileBuffer, fileSize); + fstream.close(); + eos = true; + return STATUS_DONE; + } + + const std::string mFilename; + }; + + class VFileInjector : public Injector + { + public: + VFileInjector(const LLUUID& uuid, LLAssetType::EType asset_type) : mUUID(uuid), mAssetType(asset_type) {} + virtual ~VFileInjector() {} + + const char* contentType() { return "application/octet-stream"; } + + virtual EStatus process_impl(const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump) + { + LLBufferStream ostream(channels, buffer.get()); + + LLVFile vfile(gVFS, mUUID, mAssetType, LLVFile::READ); + S32 fileSize = vfile.getSize(); + U8* fileBuffer; + fileBuffer = new U8 [fileSize]; + vfile.read(fileBuffer, fileSize); + ostream.write((char*)fileBuffer, fileSize); + eos = true; + return STATUS_DONE; + } + + const LLUUID mUUID; + LLAssetType::EType mAssetType; + }; + + + LLPumpIO* theClientPump = NULL; +} + +static void request(const std::string& url, LLURLRequest::ERequestAction method, + Injector* body_injector, LLHTTPClient::ResponderPtr responder) +{ + if (!LLHTTPClient::hasPump()) + { + responder->completed(U32_MAX, "No pump", LLSD()); + return; + } + + LLPumpIO::chain_t chain; + + LLURLRequest *req = new LLURLRequest(method, url); + req->requestEncoding(""); + if (!gCABundle.empty()) + { + req->checkRootCertificate(true, gCABundle.c_str()); + } + req->setCallback(new LLHTTPClientURLAdaptor(responder)); + + if (method == LLURLRequest::HTTP_PUT || method == LLURLRequest::HTTP_POST) + { + req->addHeader(llformat("Content-Type: %s", body_injector->contentType()).c_str()); + chain.push_back(LLIOPipe::ptr_t(body_injector)); + } + chain.push_back(LLIOPipe::ptr_t(req)); + + theClientPump->addChain(chain, HTTP_REQUEST_EXPIRY_SECS); +} + +void LLHTTPClient::get(const std::string& url, ResponderPtr responder) +{ + request(url, LLURLRequest::HTTP_GET, NULL, responder); +} + +void LLHTTPClient::put(const std::string& url, const LLSD& body, ResponderPtr responder) +{ + request(url, LLURLRequest::HTTP_PUT, new LLSDInjector(body), responder); +} + +void LLHTTPClient::post(const std::string& url, const LLSD& body, ResponderPtr responder) +{ + request(url, LLURLRequest::HTTP_POST, new LLSDInjector(body), responder); +} + +#if 1 +void LLHTTPClient::postFile(const std::string& url, const std::string& filename, ResponderPtr responder) +{ + request(url, LLURLRequest::HTTP_POST, new FileInjector(filename), responder); +} + +void LLHTTPClient::postFile(const std::string& url, const LLUUID& uuid, + LLAssetType::EType asset_type, ResponderPtr responder) +{ + request(url, LLURLRequest::HTTP_POST, new VFileInjector(uuid, asset_type), responder); +} +#endif + +void LLHTTPClient::setPump(LLPumpIO& pump) +{ + theClientPump = &pump; +} + +bool LLHTTPClient::hasPump() +{ + return theClientPump != NULL; +} + +void LLHTTPClient::setCABundle(const std::string& caBundle) +{ + gCABundle = caBundle; +} + +namespace boost +{ + void intrusive_ptr_add_ref(LLHTTPClient::Responder* p) + { + ++p->mReferenceCount; + } + + void intrusive_ptr_release(LLHTTPClient::Responder* p) + { + if(0 == --p->mReferenceCount) + { + delete p; + } + } +}; + diff --git a/indra/llmessage/llhttpclient.h b/indra/llmessage/llhttpclient.h new file mode 100644 index 0000000000..563450f07e --- /dev/null +++ b/indra/llmessage/llhttpclient.h @@ -0,0 +1,83 @@ +/** + * @file llhttpclient.h + * @brief Declaration of classes for making HTTP client requests. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLHTTPCLIENT_H +#define LL_LLHTTPCLIENT_H + +/** + * These classes represent the HTTP client framework. + */ + +#include + +#include +#include "llassetstorage.h" + +class LLPumpIO; +class LLSD; + + +class LLHTTPClient +{ +public: + class Responder + { + public: + Responder(); + virtual ~Responder(); + + virtual void error(U32 status, const std::string& reason); // called with bad status codes + + virtual void result(const LLSD& content); + + virtual void completed(U32 status, const std::string& reason, const LLSD& content); + /**< The default implemetnation calls + either: + * result(), or + * error() + */ + + public: /* but not really -- don't touch this */ + U32 mReferenceCount; + }; + + typedef boost::intrusive_ptr ResponderPtr; + + static void get(const std::string& url, ResponderPtr); + static void put(const std::string& url, const LLSD& body, ResponderPtr); + ///< non-blocking + static void post(const std::string& url, const LLSD& body, ResponderPtr); + static void postFile(const std::string& url, const std::string& filename, ResponderPtr); + static void postFile(const std::string& url, const LLUUID& uuid, + LLAssetType::EType asset_type, ResponderPtr responder); + + static void del(const std::string& url, ResponderPtr); + ///< sends a DELETE method, but we can't call it delete in c++ + + + static void setPump(LLPumpIO& pump); + ///< must be called before any of the above calls are made + static bool hasPump(); + ///< for testing + + static void setCABundle(const std::string& caBundle); + ///< use this root CA bundle when checking SSL connections + ///< defaults to the standard system root CA bundle + ///< @see LLURLRequest::checkRootCertificate() +}; + + + +namespace boost +{ + void intrusive_ptr_add_ref(LLHTTPClient::Responder* p); + void intrusive_ptr_release(LLHTTPClient::Responder* p); +}; + + +#endif // LL_LLHTTPCLIENT_H diff --git a/indra/llmessage/llhttpnode.cpp b/indra/llmessage/llhttpnode.cpp new file mode 100644 index 0000000000..e7d441b22c --- /dev/null +++ b/indra/llmessage/llhttpnode.cpp @@ -0,0 +1,449 @@ +/** + * @file llhttpnode.cpp + * @brief Implementation of classes for generic HTTP/LSL/REST handling. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llhttpnode.h" + +#include "boost/tokenizer.hpp" + +#include "llstl.h" + +static const std::string CONTEXT_REQUEST("request"); +static const std::string CONTEXT_WILDCARD("wildcard"); + +/** + * LLHTTPNode + */ + +class LLHTTPNode::Impl +{ +public: + typedef std::map ChildMap; + + ChildMap mNamedChildren; + LLHTTPNode* mWildcardChild; + std::string mWildcardName; + std::string mWildcardKey; + LLHTTPNode* mParentNode; + + Impl() : mWildcardChild(NULL), mParentNode(NULL) { } + + LLHTTPNode* findNamedChild(const std::string& name) const; +}; + + +LLHTTPNode* LLHTTPNode::Impl::findNamedChild(const std::string& name) const +{ + LLHTTPNode* child = get_ptr_in_map(mNamedChildren, name); + + if (!child && ((name[0] == '*') || (name == mWildcardName))) + { + child = mWildcardChild; + } + + return child; +} + + +LLHTTPNode::LLHTTPNode() + : impl(* new Impl) +{ +} + +// virtual +LLHTTPNode::~LLHTTPNode() +{ + std::for_each(impl.mNamedChildren.begin(), impl.mNamedChildren.end(), + DeletePairedPointer()); + + delete impl.mWildcardChild; + + delete &impl; +} + + +namespace { + class NotImplemented + { + }; +} + +// virtual +LLSD LLHTTPNode::get() const +{ + throw NotImplemented(); +} + +// virtual +LLSD LLHTTPNode::put(const LLSD& input) const +{ + throw NotImplemented(); +} + +// virtual +LLSD LLHTTPNode::post(const LLSD& input) const +{ + throw NotImplemented(); +} + + +// virtual +void LLHTTPNode::get(LLHTTPNode::ResponsePtr response, const LLSD& context) const +{ + try + { + response->result(get()); + } + catch (NotImplemented) + { + response->methodNotAllowed(); + } +} + +// virtual +void LLHTTPNode::put(LLHTTPNode::ResponsePtr response, const LLSD& context, const LLSD& input) const +{ + try + { + response->result(put(input)); + } + catch (NotImplemented) + { + response->methodNotAllowed(); + } +} + +// virtual +void LLHTTPNode::post(LLHTTPNode::ResponsePtr response, const LLSD& context, const LLSD& input) const +{ + try + { + response->result(post(input)); + } + catch (NotImplemented) + { + response->methodNotAllowed(); + } +} + +// virtual +void LLHTTPNode::del(LLHTTPNode::ResponsePtr response, const LLSD& context) const +{ + try + { + response->result(del(context)); + } + catch (NotImplemented) + { + response->methodNotAllowed(); + } + +} + +// virtual +LLSD LLHTTPNode::del() const +{ + throw NotImplemented(); +} + +// virtual +LLSD LLHTTPNode::del(const LLSD&) const +{ + return del(); +} + + +// virtual +LLHTTPNode* LLHTTPNode::getChild(const std::string& name, LLSD& context) const +{ + LLHTTPNode* namedChild = get_ptr_in_map(impl.mNamedChildren, name); + if (namedChild) + { + return namedChild; + } + + if (impl.mWildcardChild + && impl.mWildcardChild->validate(name, context)) + { + context[CONTEXT_REQUEST][CONTEXT_WILDCARD][impl.mWildcardKey] = name; + return impl.mWildcardChild; + } + + return NULL; +} + + +// virtual +bool LLHTTPNode::handles(const LLSD& remainder, LLSD& context) const +{ + return remainder.size() == 0; +} + +// virtual +bool LLHTTPNode::validate(const std::string& name, LLSD& context) const +{ + return false; +} + +const LLHTTPNode* LLHTTPNode::traverse( + const std::string& path, LLSD& context) const +{ + typedef boost::tokenizer< boost::char_separator > tokenizer; + boost::char_separator sep("/", "", boost::drop_empty_tokens); + tokenizer tokens(path, sep); + tokenizer::iterator iter = tokens.begin(); + tokenizer::iterator end = tokens.end(); + + const LLHTTPNode* node = this; + for(; iter != end; ++iter) + { + LLHTTPNode* child = node->getChild(*iter, context); + if(!child) + { + lldebugs << "LLHTTPNode::traverse: Couldn't find '" << *iter << "'" << llendl; + break; + } + lldebugs << "LLHTTPNode::traverse: Found '" << *iter << "'" << llendl; + + node = child; + } + + LLSD& remainder = context[CONTEXT_REQUEST]["remainder"]; + for(; iter != end; ++iter) + { + remainder.append(*iter); + } + + return node->handles(remainder, context) ? node : NULL; +} + + + +void LLHTTPNode::addNode(const std::string& path, LLHTTPNode* nodeToAdd) +{ + typedef boost::tokenizer< boost::char_separator > tokenizer; + boost::char_separator sep("/", "", boost::drop_empty_tokens); + tokenizer tokens(path, sep); + tokenizer::iterator iter = tokens.begin(); + tokenizer::iterator end = tokens.end(); + + LLHTTPNode* node = this; + for(; iter != end; ++iter) + { + LLHTTPNode* child = node->impl.findNamedChild(*iter); + if (!child) { break; } + node = child; + } + + if (iter == end) + { + llwarns << "LLHTTPNode::addNode: already a node that handles " + << path << llendl; + return; + } + + while (true) + { + std::string pathPart = *iter; + + ++iter; + bool lastOne = iter == end; + + LLHTTPNode* nextNode = lastOne ? nodeToAdd : new LLHTTPNode(); + + switch (pathPart[0]) + { + case '<': + // *NOTE: This should really validate that it is of + // the proper form: so that the substr() + // generates the correct key name. + node->impl.mWildcardChild = nextNode; + node->impl.mWildcardName = pathPart; + if(node->impl.mWildcardKey.empty()) + { + node->impl.mWildcardKey = pathPart.substr( + 1, + pathPart.size() - 2); + } + break; + case '*': + node->impl.mWildcardChild = nextNode; + if(node->impl.mWildcardName.empty()) + { + node->impl.mWildcardName = pathPart; + } + break; + + default: + node->impl.mNamedChildren[pathPart] = nextNode; + } + nextNode->impl.mParentNode = node; + + if (lastOne) break; + node = nextNode; + } +} + +static void append_node_paths(LLSD& result, + const std::string& name, const LLHTTPNode* node) +{ + result.append(name); + + LLSD paths = node->allNodePaths(); + LLSD::array_const_iterator i = paths.beginArray(); + LLSD::array_const_iterator end = paths.endArray(); + + for (; i != end; ++i) + { + result.append(name + "/" + (*i).asString()); + } +} + +LLSD LLHTTPNode::allNodePaths() const +{ + LLSD result; + + Impl::ChildMap::const_iterator i = impl.mNamedChildren.begin(); + Impl::ChildMap::const_iterator end = impl.mNamedChildren.end(); + for (; i != end; ++i) + { + append_node_paths(result, i->first, i->second); + } + + if (impl.mWildcardChild) + { + append_node_paths(result, impl.mWildcardName, impl.mWildcardChild); + } + + return result; +} + + +const LLHTTPNode* LLHTTPNode::rootNode() const +{ + const LLHTTPNode* node = this; + + while (true) + { + const LLHTTPNode* next = node->impl.mParentNode; + if (!next) + { + return node; + } + node = next; + } +} + + +const LLHTTPNode* LLHTTPNode::findNode(const std::string& name) const +{ + return impl.findNamedChild(name); +} + +LLHTTPNode::Response::~Response() +{ +} + +void LLHTTPNode::Response::status(S32 code) +{ + status(code, "Unknown Error"); +} + +void LLHTTPNode::Response::notFound(const std::string& message) +{ + status(404, message); +} + +void LLHTTPNode::Response::notFound() +{ + status(404, "Not Found"); +} + +void LLHTTPNode::Response::methodNotAllowed() +{ + status(405, "Method Not Allowed"); +} + +void LLHTTPNode::describe(Description& desc) const +{ + desc.shortInfo("unknown service (missing describe() method)"); +} + + +const LLChainIOFactory* LLHTTPNode::getProtocolHandler() const +{ + return NULL; +} + + + +namespace +{ + typedef std::map FactoryMap; + + FactoryMap& factoryMap() + { + static FactoryMap theMap; + return theMap; + } +} + +LLHTTPRegistrar::NodeFactory::~NodeFactory() { } + +void LLHTTPRegistrar::registerFactory( + const std::string& path, NodeFactory& factory) +{ + factoryMap()[path] = &factory; +} + +void LLHTTPRegistrar::buildAllServices(LLHTTPNode& root) +{ + const FactoryMap& map = factoryMap(); + + FactoryMap::const_iterator i = map.begin(); + FactoryMap::const_iterator end = map.end(); + for (; i != end; ++i) + { + llinfos << "LLHTTPRegistrar::buildAllServices adding node for path " + << i->first << llendl; + + root.addNode(i->first, i->second->build()); + } +} + +LLPointer LLSimpleResponse::create() +{ + return new LLSimpleResponse(); +} + +LLSimpleResponse::~LLSimpleResponse() +{ +} + +void LLSimpleResponse::result(const LLSD& result) +{ + status(200, "OK"); +} + +void LLSimpleResponse::status(S32 code, const std::string& message) +{ + mCode = code; + mMessage = message; +} + +void LLSimpleResponse::print(std::ostream& out) const +{ + out << mCode << " " << mMessage; +} + + +std::ostream& operator<<(std::ostream& out, const LLSimpleResponse& resp) +{ + resp.print(out); + return out; +} diff --git a/indra/llmessage/llhttpnode.h b/indra/llmessage/llhttpnode.h new file mode 100644 index 0000000000..1e799c18b9 --- /dev/null +++ b/indra/llmessage/llhttpnode.h @@ -0,0 +1,306 @@ +/** + * @file llhttpnode.h + * @brief Declaration of classes for generic HTTP/LSL/REST handling. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLHTTPNODE_H +#define LL_LLHTTPNODE_H + +#include "llmemory.h" +#include "llsd.h" + +class LLChainIOFactory; + + +/** + * These classes represent the HTTP framework: The URL tree, and the LLSD + * REST interface that such nodes implement. + * + * To implement a service, in most cases, subclass LLHTTPNode, implement + * get() or post(), and create a global instance of LLHTTPRegistration<>. + * This can all be done in a .cpp file, with no publically declared parts. + * + * To implement a server see lliohttpserver.h + * @see LLHTTPWireServer + */ + +/** + * @class LLHTTPNode + * @brief Base class which handles url traversal, response routing + * and support for standard LLSD services + * + * Users of the HTTP responder will typically derive a class from this + * one, implement the get(), put() and/or post() methods, and then + * use LLHTTPRegistration to insert it into the URL tree. + * + * The default implementation handles servicing the request and creating + * the pipe fittings needed to read the headers, manage them, convert + * to and from LLSD, etc. + */ +class LLHTTPNode +{ +public: + LLHTTPNode(); + virtual ~LLHTTPNode(); + + /** @name Responses + Most subclasses override one or more of these methods to provide + the service. By default, the rest of the LLHTTPNode architecture + will handle requests, create the needed LLIOPump, parse the input + to LLSD, and format the LLSD result to the output. + + The default implementation of each of these is to call + response->methodNotAllowed(); The "simple" versions can be + overridden instead in those cases where the service can return + an immediately computed response. + */ + //@{ +public: + virtual LLSD get() const; + virtual LLSD put(const LLSD& input) const; + virtual LLSD post(const LLSD& input) const; + + virtual LLSD del() const; + virtual LLSD del(const LLSD& context) const; + + class Response : public LLRefCount + { + public: + virtual ~Response(); + + virtual void result(const LLSD&) = 0; + virtual void status(S32 code, const std::string& message) = 0; + + void status(S32 code); + void notFound(const std::string& message); + void notFound(); + void methodNotAllowed(); + }; + + typedef LLPointer ResponsePtr; + + virtual void get(ResponsePtr, const LLSD& context) const; + virtual void put(ResponsePtr, const LLSD& context, const LLSD& input) const; + virtual void post(ResponsePtr, const LLSD& context, const LLSD& input) const; + virtual void del(ResponsePtr, const LLSD& context) const; + //@} + + + /** @name URL traversal + The tree is traversed by calling getChild() with successive + path components, on successive results. When getChild() returns + null, or there are no more components, the last child responds to + the request. + + The default behavior is generally correct, though wildcard nodes + will want to implement validate(). + */ + //@{ +public: + virtual LLHTTPNode* getChild(const std::string& name, LLSD& context) const; + /**< returns a child node, if any, at the given name + default looks at children and wildcard child (see below) + */ + + virtual bool handles(const LLSD& remainder, LLSD& context) const; + /**< return true if this node can service the remaining components; + default returns true if there are no remaining components + */ + + virtual bool validate(const std::string& name, LLSD& context) const; + /**< called only on wildcard nodes, to check if they will handle + the name; default is false; overrides will want to check + name, and return true if the name will construct to a valid url. + For convenience, the getChild() method above will + automatically insert the name in + context["request"]["wildcard"][key] if this method returns true. + For example, the node "agent//detail" will set + context["request"]["wildcard"]["agent_id"] eqaul to the value + found during traversal. + */ + + const LLHTTPNode* traverse(const std::string& path, LLSD& context) const; + /**< find a node, if any, that can service this path + set up context["request"] information + */ + //@} + + /** @name Child Nodes + The standard node can have any number of child nodes under + fixed names, and optionally one "wildcard" node that can + handle all other names. + + Usually, child nodes are add through LLHTTPRegistration, not + by calling this interface directly. + + The added node will be now owned by the parent node. + */ + //@{ + + virtual void addNode(const std::string& path, LLHTTPNode* nodeToAdd); + + LLSD allNodePaths() const; + ///< Returns an arrary of node paths at and under this node + + const LLHTTPNode* rootNode() const; + const LLHTTPNode* findNode(const std::string& name) const; + + //@} + + /* @name Description system + The Description object contains information about a service. + All subclasses of LLHTTPNode should override description() and use + the methods of the Description class to set the various properties. + */ + //@{ + class Description + { + public: + void shortInfo(const std::string& s){ mInfo["description"] = s; } + void longInfo(const std::string& s) { mInfo["details"] = s; } + + // Call this method when the service supports the specified verb. + void getAPI() { mInfo["api"].append("GET"); } + void putAPI() { mInfo["api"].append("PUT"); } + void postAPI() { mInfo["api"].append("POST"); } + void delAPI() { mInfo["api"].append("DELETE"); } + + void input(const std::string& s) { mInfo["input"] = s; } + void output(const std::string& s) { mInfo["output"] = s; } + void source(const char* f, int l) { mInfo["__file__"] = f; + mInfo["__line__"] = l; } + + LLSD getInfo() const { return mInfo; } + + private: + LLSD mInfo; + }; + + virtual void describe(Description&) const; + + //@} + + + virtual const LLChainIOFactory* getProtocolHandler() const; + /**< Return a factory object for handling wire protocols. + * The base class returns NULL, as it doesn't know about + * wire protocols at all. This is okay for most nodes + * as LLIOHTTPServer is smart enough to use a default + * wire protocol for HTTP for such nodes. Specialized + * subclasses that handle things like XML-RPC will want + * to implement this. (See LLXMLSDRPCServerFactory.) + */ + +private: + class Impl; + Impl& impl; +}; + + + +class LLSimpleResponse : public LLHTTPNode::Response +{ +public: + static LLPointer create(); + ~LLSimpleResponse(); + + void result(const LLSD& result); + void status(S32 code, const std::string& message); + + void print(std::ostream& out) const; + + S32 mCode; + std::string mMessage; + +private: + LLSimpleResponse() {;} // Must be accessed through LLPointer. +}; + +std::ostream& operator<<(std::ostream& out, const LLSimpleResponse& resp); + + + +/** + * @name Automatic LLHTTPNode registration + * + * To register a node type at a particular url path, construct a global instance + * of LLHTTPRegistration: + * + * LLHTTPRegistration gHTTPServiceAlphaBeta("/alpha/beta"); + * + * (Note the naming convention carefully.) This object must be global and not + * static. However, it needn't be declared in your .h file. It can exist + * solely in the .cpp file. The same is true of your subclass of LLHTTPNode: + * it can be declared and defined wholly within the .cpp file. + * + * When constructing a web server, use LLHTTPRegistrar to add all the registered + * nodes to the url tree: + * + * LLHTTPRegistrar::buidlAllServices(mRootNode); + */ +//@{ + +class LLHTTPRegistrar +{ +public: + class NodeFactory + { + public: + virtual ~NodeFactory(); + virtual LLHTTPNode* build() const = 0; + }; + + static void buildAllServices(LLHTTPNode& root); + + static void registerFactory(const std::string& path, NodeFactory& factory); + ///< construct an LLHTTPRegistration below to call this +}; + +template < class NodeType > +class LLHTTPRegistration +{ +public: + LLHTTPRegistration(const std::string& path) + { + LLHTTPRegistrar::registerFactory(path, mFactory); + } + +private: + class ThisNodeFactory : public LLHTTPRegistrar::NodeFactory + { + public: + virtual LLHTTPNode* build() const { return new NodeType; } + }; + + ThisNodeFactory mFactory; +}; + +template < class NodeType> +class LLHTTPParamRegistration +{ +public: + LLHTTPParamRegistration(const std::string& path, LLSD params) : + mFactory(params) + { + LLHTTPRegistrar::registerFactory(path, mFactory); + } + +private: + class ThisNodeFactory : public LLHTTPRegistrar::NodeFactory + { + public: + ThisNodeFactory(LLSD params) : mParams(params) {} + virtual LLHTTPNode* build() const { return new NodeType(mParams); } + private: + LLSD mParams; + }; + + ThisNodeFactory mFactory; +}; + +//@} + +#endif // LL_LLHTTPNODE_H diff --git a/indra/llmessage/llinstantmessage.cpp b/indra/llmessage/llinstantmessage.cpp new file mode 100644 index 0000000000..2bfa82a0ce --- /dev/null +++ b/indra/llmessage/llinstantmessage.cpp @@ -0,0 +1,299 @@ +/** + * @file llinstantmessage.cpp + * @author Phoenix + * @date 2005-08-29 + * @brief Constants and functions used in IM. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lldbstrings.h" +#include "llinstantmessage.h" +#include "llhost.h" +#include "lluuid.h" +#include "llsd.h" +#include "llsdserialize.h" +#include "llmemory.h" +#include "message.h" + +#include "message.h" + +const U8 IM_ONLINE = 0; +const U8 IM_OFFLINE = 1; + +const S32 VOTE_YES = 1; +const S32 VOTE_NO = 0; +const S32 VOTE_ABSTAIN = -1; + +const S32 VOTE_MAJORITY = 0; +const S32 VOTE_SUPER_MAJORITY = 1; +const S32 VOTE_UNANIMOUS = 2; + +const char EMPTY_BINARY_BUCKET[] = ""; +const S32 EMPTY_BINARY_BUCKET_SIZE = 1; +const U32 NO_TIMESTAMP = 0; +const char SYSTEM_FROM[] = "Second Life"; +const S32 IM_TTL = 1; + + +/** + * LLIMInfo + */ +LLIMInfo::LLIMInfo() : + mParentEstateID(0), + mOffline(0), + mViewerThinksToIsOnline(false), + mTimeStamp(0), + mSource(IM_FROM_SIM), + mTTL(IM_TTL) +{ +} + +LLIMInfo::LLIMInfo( + const LLUUID& from_id, + BOOL from_group, + const LLUUID& to_id, + EInstantMessage im_type, + const std::string& name, + const std::string& message, + const LLUUID& id, + U32 parent_estate_id, + const LLUUID& region_id, + const LLVector3& position, + LLSD data, + U8 offline, + U32 timestamp, + EIMSource source, + S32 ttl) : + mFromID(from_id), + mFromGroup(from_group), + mToID(to_id), + mParentEstateID(0), + mRegionID(region_id), + mPosition(position), + mOffline(offline), + mViewerThinksToIsOnline(false), + mIMType(im_type), + mID(id), + mTimeStamp(timestamp), + mName(name), + mMessage(message), + mData(data), + mSource(source), + mTTL(ttl) +{ +} + +LLIMInfo::LLIMInfo(LLMessageSystem* msg, EIMSource source, S32 ttl) : + mViewerThinksToIsOnline(false), + mSource(source), + mTTL(ttl) +{ + unpackMessageBlock(msg); +} + +LLIMInfo::~LLIMInfo() +{ +} + +void LLIMInfo::packInstantMessage(LLMessageSystem* msg) const +{ + lldebugs << "LLIMInfo::packInstantMessage()" << llendl; + msg->newMessageFast(_PREHASH_ImprovedInstantMessage); + packMessageBlock(msg); +} + +void LLIMInfo::packMessageBlock(LLMessageSystem* msg) const +{ + // Construct binary bucket + std::vector bucket; + if (mData.has("binary_bucket")) + { + bucket = mData["binary_bucket"].asBinary(); + } + pack_instant_message_block( + msg, + mFromID, + mFromGroup, + LLUUID::null, + mToID, + mName.c_str(), + mMessage.c_str(), + mOffline, + mIMType, + mID, + mParentEstateID, + mRegionID, + mPosition, + mTimeStamp, + &bucket[0], + bucket.size()); +} + +void pack_instant_message( + LLMessageSystem* msg, + const LLUUID& from_id, + BOOL from_group, + const LLUUID& session_id, + const LLUUID& to_id, + const char* name, + const char* message, + U8 offline, + EInstantMessage dialog, + const LLUUID& id, + U32 parent_estate_id, + const LLUUID& region_id, + const LLVector3& position, + U32 timestamp, + const U8* binary_bucket, + S32 binary_bucket_size) +{ + lldebugs << "pack_instant_message()" << llendl; + msg->newMessageFast(_PREHASH_ImprovedInstantMessage); + pack_instant_message_block( + msg, + from_id, + from_group, + session_id, + to_id, + name, + message, + offline, + dialog, + id, + parent_estate_id, + region_id, + position, + timestamp, + binary_bucket, + binary_bucket_size); +} + +void pack_instant_message_block( + LLMessageSystem* msg, + const LLUUID& from_id, + BOOL from_group, + const LLUUID& session_id, + const LLUUID& to_id, + const char* name, + const char* message, + U8 offline, + EInstantMessage dialog, + const LLUUID& id, + U32 parent_estate_id, + const LLUUID& region_id, + const LLVector3& position, + U32 timestamp, + const U8* binary_bucket, + S32 binary_bucket_size) +{ + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, from_id); + msg->addUUIDFast(_PREHASH_SessionID, session_id); + msg->nextBlockFast(_PREHASH_MessageBlock); + msg->addBOOLFast(_PREHASH_FromGroup, from_group); + msg->addUUIDFast(_PREHASH_ToAgentID, to_id); + msg->addU32Fast(_PREHASH_ParentEstateID, parent_estate_id); + msg->addUUIDFast(_PREHASH_RegionID, region_id); + msg->addVector3Fast(_PREHASH_Position, position); + msg->addU8Fast(_PREHASH_Offline, offline); + msg->addU8Fast(_PREHASH_Dialog, (U8) dialog); + msg->addUUIDFast(_PREHASH_ID, id); + msg->addU32Fast(_PREHASH_Timestamp, timestamp); + msg->addStringFast(_PREHASH_FromAgentName, name); + S32 bytes_left = MTUBYTES; + if(message) + { + char buffer[MTUBYTES]; + bytes_left -= snprintf(buffer, MTUBYTES, "%s", message); + bytes_left = llmax(0, bytes_left); + msg->addStringFast(_PREHASH_Message, buffer); + } + else + { + msg->addStringFast(_PREHASH_Message, NULL); + } + const U8* bb; + if(binary_bucket) + { + bb = binary_bucket; + binary_bucket_size = llmin(bytes_left, binary_bucket_size); + } + else + { + bb = (const U8*)EMPTY_BINARY_BUCKET; + binary_bucket_size = EMPTY_BINARY_BUCKET_SIZE; + } + msg->addBinaryDataFast(_PREHASH_BinaryBucket, bb, binary_bucket_size); +} + +void LLIMInfo::unpackMessageBlock(LLMessageSystem* msg) +{ + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, mFromID); + msg->getBOOLFast(_PREHASH_MessageBlock, _PREHASH_FromGroup, mFromGroup); + msg->getUUIDFast(_PREHASH_MessageBlock, _PREHASH_ToAgentID, mToID); + msg->getU32Fast(_PREHASH_MessageBlock, _PREHASH_ParentEstateID, mParentEstateID); + msg->getUUIDFast(_PREHASH_MessageBlock, _PREHASH_RegionID, mRegionID); + msg->getVector3Fast(_PREHASH_MessageBlock, _PREHASH_Position, mPosition); + msg->getU8Fast(_PREHASH_MessageBlock, _PREHASH_Offline, mOffline); + U8 dialog; + msg->getU8Fast(_PREHASH_MessageBlock, _PREHASH_Dialog, dialog); + mIMType = (EInstantMessage) dialog; + msg->getUUIDFast(_PREHASH_MessageBlock, _PREHASH_ID, mID); + msg->getU32Fast(_PREHASH_MessageBlock, _PREHASH_Timestamp, mTimeStamp); + char name[DB_FULL_NAME_BUF_SIZE]; + msg->getStringFast(_PREHASH_MessageBlock, _PREHASH_FromAgentName, DB_FULL_NAME_BUF_SIZE, name); + mName.assign(name); + + char message[DB_IM_MSG_BUF_SIZE]; + msg->getStringFast(_PREHASH_MessageBlock, _PREHASH_Message, DB_IM_MSG_BUF_SIZE, message); + mMessage.assign(message); + + S32 binary_bucket_size = llmin( + MTUBYTES, + msg->getSizeFast( + _PREHASH_MessageBlock, + _PREHASH_BinaryBucket)); + if(binary_bucket_size) + { + std::vector bucket; + bucket.resize(binary_bucket_size); + + msg->getBinaryDataFast( + _PREHASH_MessageBlock, + _PREHASH_BinaryBucket, + &bucket[0], + 0, + 0, + binary_bucket_size); + mData["binary_bucket"] = bucket; + } + else + { + mData.clear(); + } +} + +LLPointer LLIMInfo::clone() +{ + return new LLIMInfo( + mFromID, + mFromGroup, + mToID, + mIMType, + mName, + mMessage, + mID, + mParentEstateID, + mRegionID, + mPosition, + mData, + mOffline, + mTimeStamp, + mSource, + mTTL); +} + diff --git a/indra/llmessage/llinstantmessage.h b/indra/llmessage/llinstantmessage.h new file mode 100644 index 0000000000..c8138cf491 --- /dev/null +++ b/indra/llmessage/llinstantmessage.h @@ -0,0 +1,308 @@ +/** + * @file llinstantmessage.h + * @brief Constants and declarations used by instant messages. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLINSTANTMESSAGE_H +#define LL_LLINSTANTMESSAGE_H + +#include "llhost.h" +#include "lluuid.h" +#include "llsd.h" +#include "llmemory.h" +#include "v3math.h" + +class LLMessageSystem; + +// The ImprovedInstantMessage only supports 8 bits in the "Dialog" +// field, so don't go past the byte boundary +enum EInstantMessage +{ + // default. ID is meaningless, nothing in the binary bucket. + IM_NOTHING_SPECIAL = 0, + + // pops a messagebox with a single OK button + IM_MESSAGEBOX = 1, + + // pops a countdown messagebox with a single OK button + // IM_MESSAGEBOX_COUNTDOWN = 2, + + // You've been invited to join a group. + // ID is the group id. + + // The binary bucket contains a null terminated string + // representation of the officer/member status and join cost for + // the invitee. (bug # 7672) The format is 1 byte for + // officer/member (O for officer, M for member), and as many bytes + // as necessary for cost. + IM_GROUP_INVITATION = 3, + + // Inventory offer. + // ID is the transaction id + // Binary bucket is a list of inventory uuid and type. + IM_INVENTORY_OFFERED = 4, + IM_INVENTORY_ACCEPTED = 5, + IM_INVENTORY_DECLINED = 6, + + // Group vote + // Name is name of person who called vote. + // ID is vote ID used for internal tracking + IM_GROUP_VOTE = 7, + + // Group message + // This means that the message is meant for everyone in the + // agent's group. This will result in a database query to find all + // participants and start an im session. + IM_GROUP_MESSAGE_DEPRECATED = 8, + + // Task inventory offer. + // ID is the transaction id + // Binary bucket is a (mostly) complete packed inventory item + IM_TASK_INVENTORY_OFFERED = 9, + IM_TASK_INVENTORY_ACCEPTED = 10, + IM_TASK_INVENTORY_DECLINED = 11, + + // Copied as pending, type LL_NOTHING_SPECIAL, for new users + // used by offline tools + IM_NEW_USER_DEFAULT = 12, + + // + // session based messaging - the way that people usually actually + // communicate with each other. + // + + // Start a session, or add users to a session. + IM_SESSION_ADD = 13, + + // Start a session, but don't prune offline users + IM_SESSION_OFFLINE_ADD = 14, + + // start a session with your gruop + IM_SESSION_GROUP_START = 15, + + // start a session without a calling card (finder or objects) + IM_SESSION_CARDLESS_START = 16, + + // send a message to a session. + IM_SESSION_SEND = 17, + + // leave a session + IM_SESSION_DROP = 18, + + // an instant message from an object - for differentiation on the + // viewer, since you can't IM an object yet. + IM_FROM_TASK = 19, + + // sent an IM to a busy user, this is the auto response + IM_BUSY_AUTO_RESPONSE = 20, + + // Shows the message in the console and chat history + IM_CONSOLE_AND_CHAT_HISTORY = 21, + + // IM Types used for luring your friends + IM_LURE_USER = 22, + IM_LURE_ACCEPTED = 23, + IM_LURE_DECLINED = 24, + IM_GODLIKE_LURE_USER = 25, + IM_YET_TO_BE_USED = 26, + + // IM that notifie of a new group election. + // Name is name of person who called vote. + // ID is election ID used for internal tracking + IM_GROUP_ELECTION_DEPRECATED = 27, + + // IM to tell the user to go to an URL. Put a text message in the + // message field, and put the url with a trailing \0 in the binary + // bucket. + IM_GOTO_URL = 28, + + // IM for help from the GAURDIAN_ANGELS + // Binary bucket contains the name of the session. + IM_SESSION_911_START = 29, + + // IM sent automatically on call for help, + // sends a lure to each Helper reached + IM_LURE_911 = 30, + + // a message generated by a script which we don't want to + // be sent through e-mail. Similar to IM_FROM_TASK, but + // it is shown as an alert on the viewer. + IM_FROM_TASK_AS_ALERT = 31, + + // IM from group officer to all group members. + IM_GROUP_NOTICE = 32, + IM_GROUP_NOTICE_INVENTORY_ACCEPTED = 33, + IM_GROUP_NOTICE_INVENTORY_DECLINED = 34, + + IM_GROUP_INVITATION_ACCEPT = 35, + IM_GROUP_INVITATION_DECLINE = 36, + + IM_GROUP_NOTICE_REQUESTED = 37, + + IM_FRIENDSHIP_OFFERED = 38, + IM_FRIENDSHIP_ACCEPTED = 39, + IM_FRIENDSHIP_DECLINED = 40, + + IM_TYPING_START = 41, + IM_TYPING_STOP = 42, + + IM_COUNT +}; + + +// Hooks for quickly hacking in experimental admin debug messages +// without needing to recompile the viewer +// *NOTE: This functionality has been moved to be a string based +// operation so that we don't even have to do a full recompile. This +// enumeration will be phased out soon. +enum EGodlikeRequest +{ + GOD_WANTS_NOTHING, + + // for requesting physics information about an object + GOD_WANTS_HAVOK_INFO, + + // two unused requests that can be appropriated for debug + // purposes (no viewer recompile necessary) + GOD_WANTS_FOO, + GOD_WANTS_BAR, + + // to dump simulator terrain data to terrain.raw file + GOD_WANTS_TERRAIN_SAVE, + // to load simulator terrain data from terrain.raw file + GOD_WANTS_TERRAIN_LOAD, + + GOD_WANTS_TOGGLE_AVATAR_GEOMETRY, // HACK for testing new avatar geom + + // real-time telehub operations + GOD_WANTS_TELEHUB_INFO, + GOD_WANTS_CONNECT_TELEHUB, + GOD_WANTS_DELETE_TELEHUB, + GOD_WANTS_ADD_TELEHUB_SPAWNPOINT, + GOD_WANTS_REMOVE_TELEHUB_SPAWNPOINT, + +}; + +enum EIMSource +{ + IM_FROM_VIEWER, + IM_FROM_DATASERVER, + IM_FROM_SIM +}; + +extern const U8 IM_ONLINE; +extern const U8 IM_OFFLINE; + +extern const S32 VOTE_YES; +extern const S32 VOTE_NO; +extern const S32 VOTE_ABSTAIN; + +extern const S32 VOTE_MAJORITY; +extern const S32 VOTE_SUPER_MAJORITY; +extern const S32 VOTE_UNANIMOUS; + +extern const char EMPTY_BINARY_BUCKET[]; +extern const S32 EMPTY_BINARY_BUCKET_SIZE; + +extern const U32 NO_TIMESTAMP; +extern const char SYSTEM_FROM[]; + +// Number of retry attempts on sending the im. +extern const S32 IM_TTL; + + +class LLIMInfo : public LLRefCount +{ +protected: + LLIMInfo(); + ~LLIMInfo(); + +public: + LLIMInfo(LLMessageSystem* msg, + EIMSource source = IM_FROM_SIM, + S32 ttl = IM_TTL); + + LLIMInfo( + const LLUUID& from_id, + BOOL from_group, + const LLUUID& to_id, + EInstantMessage im_type, + const std::string& name, + const std::string& message, + const LLUUID& id, + U32 parent_estate_id, + const LLUUID& region_id, + const LLVector3& position, + LLSD data, + U8 offline, + U32 timestamp, + EIMSource source, + S32 ttl = IM_TTL); + + void packInstantMessage(LLMessageSystem* msg) const; + void packMessageBlock(LLMessageSystem* msg) const; + void unpackMessageBlock(LLMessageSystem* msg); + LLPointer clone(); +public: + LLUUID mFromID; + BOOL mFromGroup; + LLUUID mToID; + U32 mParentEstateID; + LLUUID mRegionID; + LLVector3 mPosition; + U8 mOffline; + bool mViewerThinksToIsOnline; + EInstantMessage mIMType; + LLUUID mID; + U32 mTimeStamp; + std::string mName; + std::string mMessage; + LLSD mData; + + EIMSource mSource; + S32 mTTL; +}; + + +void pack_instant_message( + LLMessageSystem* msgsystem, + const LLUUID& from_id, + BOOL from_group, + const LLUUID& session_id, + const LLUUID& to_id, + const char* name, + const char* message, + U8 offline = IM_ONLINE, + EInstantMessage dialog = IM_NOTHING_SPECIAL, + const LLUUID& id = LLUUID::null, + U32 parent_estate_id = 0, + const LLUUID& region_id = LLUUID::null, + const LLVector3& position = LLVector3::zero, + U32 timestamp = NO_TIMESTAMP, + const U8* binary_bucket = (U8*)EMPTY_BINARY_BUCKET, + S32 binary_bucket_size = EMPTY_BINARY_BUCKET_SIZE); + +void pack_instant_message_block( + LLMessageSystem* msgsystem, + const LLUUID& from_id, + BOOL from_group, + const LLUUID& session_id, + const LLUUID& to_id, + const char* name, + const char* message, + U8 offline = IM_ONLINE, + EInstantMessage dialog = IM_NOTHING_SPECIAL, + const LLUUID& id = LLUUID::null, + U32 parent_estate_id = 0, + const LLUUID& region_id = LLUUID::null, + const LLVector3& position = LLVector3::zero, + U32 timestamp = NO_TIMESTAMP, + const U8* binary_bucket = (U8*)EMPTY_BINARY_BUCKET, + S32 binary_bucket_size = EMPTY_BINARY_BUCKET_SIZE); + + +#endif // LL_LLINSTANTMESSAGE_H + diff --git a/indra/llmessage/llinvite.h b/indra/llmessage/llinvite.h new file mode 100644 index 0000000000..5c66abc1eb --- /dev/null +++ b/indra/llmessage/llinvite.h @@ -0,0 +1,15 @@ +/** + * @file llinvite.h + * @brief Constants used for inviting users to join groups. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLINVITE_H +#define LL_LLINVITE_H + +const S32 INVITE_LIST_STR_LEN = 324; // Would be larger, but we don't have much room in the CreateGroupRequest msg. +const S32 INVITE_LIST_BUF_SIZE = 325; + +#endif // LL_LLINVITE_H diff --git a/indra/llmessage/lliobuffer.cpp b/indra/llmessage/lliobuffer.cpp new file mode 100644 index 0000000000..f5d1ebd595 --- /dev/null +++ b/indra/llmessage/lliobuffer.cpp @@ -0,0 +1,96 @@ +/** + * @file lliobuffer.cpp + * @author Phoenix + * @date 2005-05-04 + * @brief Definition of buffer based implementations of IO Pipes. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "lliobuffer.h" + +// +// LLIOBuffer +// +LLIOBuffer::LLIOBuffer() : + mBuffer(NULL), + mBufferSize(0L), + mReadHead(NULL), + mWriteHead(NULL) +{ +} + +LLIOBuffer::~LLIOBuffer() +{ + if(mBuffer) + { + delete[] mBuffer; + } +} + +U8* LLIOBuffer::data() const +{ + return mBuffer; +} + +S64 LLIOBuffer::size() const +{ + return mBufferSize; +} + +U8* LLIOBuffer::current() const +{ + return mReadHead; +} + +S64 LLIOBuffer::bytesLeft() const +{ + return mWriteHead - mReadHead; +} + +void LLIOBuffer::clear() +{ + mReadHead = mBuffer; + mWriteHead = mBuffer; +} + +LLIOPipe::EStatus LLIOBuffer::seek(LLIOBuffer::EHead head, S64 delta) +{ + LLIOPipe::EStatus status = STATUS_ERROR; + switch(head) + { + case READ: + if(((delta >= 0) && ((mReadHead + delta) <= mWriteHead)) + || (delta < 0) && ((mReadHead + delta) >= mBuffer)) + { + mReadHead += delta; + status = STATUS_OK; + } + break; + case WRITE: + if(((delta >= 0) && ((mWriteHead + delta) < (mBuffer + mBufferSize))) + || (delta < 0) && ((mWriteHead + delta) > mReadHead)) + { + mWriteHead += delta; + status = STATUS_OK; + } + default: + break; + } + return status; +} + +// virtual +LLIOPipe::EStatus LLIOBuffer::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + // no-op (I think) + llwarns << "You are using an LLIOBuffer which is deprecated." << llendl; + return STATUS_OK; +} diff --git a/indra/llmessage/lliobuffer.h b/indra/llmessage/lliobuffer.h new file mode 100644 index 0000000000..770738f2dd --- /dev/null +++ b/indra/llmessage/lliobuffer.h @@ -0,0 +1,117 @@ +/** + * @file lliobuffer.h + * @author Phoenix + * @date 2005-05-04 + * @brief Declaration of buffers for use in IO Pipes. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLIOBUFFER_H +#define LL_LLIOBUFFER_H + +#include "lliopipe.h" + +/** + * @class LLIOBuffer + * @brief This class is an io class that represents an automtically + * resizing io buffer. + * @see LLIOPipe + * + * This class is currently impelemented quick and dirty, but should be + * correct. This class should be extended to have a more flexible + * (and capped) memory allocation and usage scheme. Eventually, I + * would like to have the ability to share this buffer between + * different objects. + */ +class LLIOBuffer : public LLIOPipe +{ +public: + LLIOBuffer(); + virtual ~LLIOBuffer(); + + /** + * @brief Return a raw pointer to the current data set. + * + * The pointer returned can be used for reading or even adjustment + * if you are a bit crazy up to size() bytes into memory. + * @return A potentially NULL pointer to the raw buffer data + */ + U8* data() const; + + /** + * @brief Return the size of the buffer + */ + S64 size() const; + + /** + * @brief Return a raw pointer to the current read position in the data. + * + * The pointer returned can be used for reading or even adjustment + * if you are a bit crazy up to bytesLeft() bytes into memory. + * @return A potentially NULL pointer to the buffer data starting + * at the read point + */ + U8* current() const; + + /** + * @brief Return the number of unprocessed bytes in buffer. + */ + S64 bytesLeft() const; + + /** + * @brief Move the buffer offsets back to the beginning. + * + * This method effectively clears what has been stored here, + * without mucking around with memory allocation. + */ + void clear(); + + /** + * @brief Enumeration passed into the seek function + * + * The READ head is used for where to start processing data for + * the next link in the chain, while the WRITE head specifies + * where new data processed from the previous link in the chain + * will be written. + */ + enum EHead + { + READ, + WRITE + }; + + /** + * @brief Seek to a place in the buffer + * + * @param head The READ or WRITE head. + * @param delta The offset from the current position to seek. + * @return The status of the operation. status >= if head moved. + */ + EStatus seek(EHead head, S64 delta); + +public: + /* @name LLIOPipe virtual implementations + */ + //@{ +protected: + /** + * @brief Process the data in buffer + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: + U8* mBuffer; + S64 mBufferSize; + U8* mReadHead; + U8* mWriteHead; +}; + +#endif // LL_LLIOBUFFER_H diff --git a/indra/llmessage/lliohttpserver.cpp b/indra/llmessage/lliohttpserver.cpp new file mode 100644 index 0000000000..c1a9bc442e --- /dev/null +++ b/indra/llmessage/lliohttpserver.cpp @@ -0,0 +1,833 @@ +/** + * @file lliohttpserver.cpp + * @author Phoenix + * @date 2005-10-05 + * @brief Implementation of the http server classes + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "lliohttpserver.h" + +#include "boost/tokenizer.hpp" + +#include "llapr.h" +#include "llbuffer.h" +#include "llbufferstream.h" +#include "llhttpnode.h" +#include "lliopipe.h" +#include "lliosocket.h" +#include "llioutil.h" +#include "llmemtype.h" +#include "llmemorystream.h" +#include "llpumpio.h" +#include "llsd.h" +#include "llsdserialize_xml.h" +#include "llstl.h" + +static const char HTTP_VERSION_STR[] = "HTTP/1.0"; +static const std::string CONTEXT_REQUEST("request"); +static const std::string HTTP_VERB_GET("GET"); +static const std::string HTTP_VERB_PUT("PUT"); +static const std::string HTTP_VERB_POST("POST"); +static const std::string HTTP_VERB_DELETE("DELETE"); + + +class LLHTTPPipe : public LLIOPipe +{ +public: + LLHTTPPipe(const LLHTTPNode& node) + : mNode(node), mResponse(NULL), mState(STATE_INVOKE), mChainLock(0), mStatusCode(0) + { } + virtual ~LLHTTPPipe() + { + if (mResponse.notNull()) + { + mResponse->nullPipe(); + } + } + +private: + // LLIOPipe API implementation. + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + LLIOPipe::buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + + const LLHTTPNode& mNode; + + class Response : public LLHTTPNode::Response + { + public: + + static LLPointer create(LLHTTPPipe* pipe); + virtual ~Response(); + + // from LLHTTPNode::Response + virtual void result(const LLSD&); + virtual void status(S32 code, const std::string& message); + + void nullPipe(); + + private: + Response() {;} // Must be accessed through LLPointer. + LLHTTPPipe* mPipe; + }; + friend class Response; + + LLPointer mResponse; + + enum State + { + STATE_INVOKE, + STATE_DELAYED, + STATE_LOCKED, + STATE_GOOD_RESULT, + STATE_STATUS_RESULT + }; + State mState; + + S32 mChainLock; + LLPumpIO* mLockedPump; + + void lockChain(LLPumpIO*); + void unlockChain(); + + LLSD mGoodResult; + S32 mStatusCode; + std::string mStatusMessage; +}; + +LLIOPipe::EStatus LLHTTPPipe::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + lldebugs << "LLSDHTTPServer::process_impl" << llendl; + + // Once we have all the data, We need to read the sd on + // the the in channel, and respond on the out channel + + if(!eos) return STATUS_BREAK; + if(!pump || !buffer) return STATUS_PRECONDITION_NOT_MET; + + PUMP_DEBUG; + if (mState == STATE_INVOKE) + { + PUMP_DEBUG; + mState = STATE_DELAYED; + // assume deferred unless mResponse does otherwise + mResponse = Response::create(this); + + // TODO: Babbage: Parameterize parser? + LLBufferStream istr(channels, buffer.get()); + + std::string verb = context[CONTEXT_REQUEST]["verb"]; + if(verb == HTTP_VERB_GET) + { + mNode.get(LLHTTPNode::ResponsePtr(mResponse), context); + } + else if(verb == HTTP_VERB_PUT) + { + LLSD input; + LLSDSerialize::fromXML(input, istr); + + mNode.put(LLHTTPNode::ResponsePtr(mResponse), context, input); + } + else if(verb == HTTP_VERB_POST) + { + LLSD input; + LLSDSerialize::fromXML(input, istr); + + mNode.post(LLHTTPNode::ResponsePtr(mResponse), context, input); + } + else if(verb == HTTP_VERB_DELETE) + { + mNode.del(LLHTTPNode::ResponsePtr(mResponse), context); + } + else + { + mResponse->methodNotAllowed(); + } + + // Log Internal Server Errors + if(mStatusCode == 500) + { + llwarns << "LLHTTPPipe::process_impl:500:Internal Server Error" + << llendl; + } + } + + PUMP_DEBUG; + switch (mState) + { + case STATE_DELAYED: + lockChain(pump); + mState = STATE_LOCKED; + return STATUS_BREAK; + + case STATE_LOCKED: + // should never ever happen! + return STATUS_ERROR; + + case STATE_GOOD_RESULT: + { + context["response"]["contentType"] = "application/xml"; + LLBufferStream ostr(channels, buffer.get()); + LLSDSerialize::toXML(mGoodResult, ostr); + + return STATUS_DONE; + } + + case STATE_STATUS_RESULT: + { + context["response"]["contentType"] = "text/plain"; + context["response"]["statusCode"] = mStatusCode; + context["response"]["statusMessage"] = mStatusMessage; + LLBufferStream ostr(channels, buffer.get()); + ostr << mStatusMessage << std::ends; + + return STATUS_DONE; + } + default: + llwarns << "LLHTTPPipe::process_impl: unexpected state " + << mState << llendl; + + return STATUS_BREAK; + } +// PUMP_DEBUG; // unreachable +} + +LLPointer LLHTTPPipe::Response::create(LLHTTPPipe* pipe) +{ + LLPointer result = new Response(); + result->mPipe = pipe; + return result; +} + +// virtual +LLHTTPPipe::Response::~Response() +{ +} + +void LLHTTPPipe::Response::nullPipe() +{ + mPipe = NULL; +} + +// virtual +void LLHTTPPipe::Response::result(const LLSD& r) +{ + if(! mPipe) + { + llwarns << "LLHTTPPipe::Response::result: NULL pipe" << llendl; + return; + } + + mPipe->mStatusCode = 200; + mPipe->mStatusMessage = "OK"; + mPipe->mGoodResult = r; + mPipe->mState = STATE_GOOD_RESULT; + mPipe->unlockChain(); +} + +// virtual +void LLHTTPPipe::Response::status(S32 code, const std::string& message) +{ + if(! mPipe) + { + llwarns << "LLHTTPPipe::Response::status: NULL pipe" << llendl; + return; + } + + mPipe->mStatusCode = code; + mPipe->mStatusMessage = message; + mPipe->mState = STATE_STATUS_RESULT; + mPipe->unlockChain(); +} + +void LLHTTPPipe::lockChain(LLPumpIO* pump) +{ + if (mChainLock != 0) { return; } + + mLockedPump = pump; + mChainLock = pump->setLock(); +} + +void LLHTTPPipe::unlockChain() +{ + if (mChainLock == 0) { return; } + + mLockedPump->clearLock(mChainLock); + mLockedPump = NULL; + mChainLock = 0; +} + + + +/** + * @class LLHTTPResponseHeader + * @brief Class which correctly builds HTTP headers on a pipe + * @see LLIOPipe + * + * An instance of this class can be placed in a chain where it will + * wait for an end of stream. Once it gets that, it will count the + * bytes on CHANNEL_OUT (or the size of the buffer in io pipe versions + * prior to 2) prepend that data to the request in an HTTP format, and + * supply all normal HTTP response headers. + */ +class LLHTTPResponseHeader : public LLIOPipe +{ +public: + LLHTTPResponseHeader() {} + virtual ~LLHTTPResponseHeader() {} + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data in buffer + */ + EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: + S32 mCode; +}; + + +/** + * LLHTTPResponseHeader + */ +// virtual +LLIOPipe::EStatus LLHTTPResponseHeader::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER); + if(eos) + { + PUMP_DEBUG; + //mGotEOS = true; + std::ostringstream ostr; + std::string message = context["response"]["statusMessage"]; + + int code = context["response"]["statusCode"]; + if (code < 200) + { + code = 200; + message = "OK"; + } + + ostr << HTTP_VERSION_STR << " " << code << " " << message << "\r\n"; + + std::string type = context["response"]["contentType"].asString(); + if (!type.empty()) + { + ostr << "Content-Type: " << type << "\r\n"; + } + S32 content_length = buffer->countAfter(channels.in(), NULL); + if(0 < content_length) + { + ostr << "Content-Length: " << content_length << "\r\n"; + } + ostr << "\r\n"; + + LLChangeChannel change(channels.in(), channels.out()); + std::for_each(buffer->beginSegment(), buffer->endSegment(), change); + std::string header = ostr.str(); + buffer->prepend(channels.out(), (U8*)header.c_str(), header.size()); + PUMP_DEBUG; + return STATUS_DONE; + } + PUMP_DEBUG; + return STATUS_OK; +} + + + +/** + * @class LLHTTPResponder + * @brief This class + * @see LLIOPipe + * + * NOTE: You should not need to create or use one of these, the + * details are handled by the HTTPResponseFactory. + */ +class LLHTTPResponder : public LLIOPipe +{ +public: + LLHTTPResponder(const LLHTTPNode& tree); + ~LLHTTPResponder(); + +protected: + /** + * @brief Read data off of CHANNEL_IN keeping track of last read position. + * + * This is a quick little hack to read headers. It is not IO + * optimal, but it makes it easier for me to implement the header + * parsing. Plus, there should never be more than a few headers. + * This method will tend to read more than necessary, find the + * newline, make the front part of dest look like a c string, and + * move the read head back to where the newline was found. Thus, + * the next read will pick up on the next line. + * @param channel The channel to read in the buffer + * @param buffer The heap array of processed data + * @param dest Destination for the data to be read + * @param[in,out] len in The size of the buffer. out how + * much was read. This value is not useful for determining where to + * seek orfor string assignment. + * @returns Returns true if a line was found. + */ + bool readLine( + const LLChannelDescriptors& channels, + buffer_ptr_t buffer, + U8* dest, + S32& len); + + /** + * @brief Mark the request as bad, and handle appropriately + * + * @param channels The channels to use in the buffer. + * @param buffer The heap array of processed data. + */ + void markBad(const LLChannelDescriptors& channels, buffer_ptr_t buffer); + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data in buffer + */ + EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: + enum EState + { + STATE_NOTHING, + STATE_READING_HEADERS, + STATE_LOOKING_FOR_EOS, + STATE_DONE, + STATE_SHORT_CIRCUIT + }; + + EState mState; + U8* mLastRead; + std::string mVerb; + std::string mAbsPathAndQuery; + std::string mPath; + std::string mQuery; + std::string mVersion; + S32 mContentLength; + + // handle the urls + const LLHTTPNode& mRootNode; +}; + +LLHTTPResponder::LLHTTPResponder(const LLHTTPNode& tree) : + mState(STATE_NOTHING), + mLastRead(NULL), + mContentLength(0), + mRootNode(tree) +{ + LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER); +} + +// virtual +LLHTTPResponder::~LLHTTPResponder() +{ + LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER); + //lldebugs << "destroying LLHTTPResponder" << llendl; +} + +bool LLHTTPResponder::readLine( + const LLChannelDescriptors& channels, + buffer_ptr_t buffer, + U8* dest, + S32& len) +{ + LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER); + --len; + U8* last = buffer->readAfter(channels.in(), mLastRead, dest, len); + dest[len] = '\0'; + U8* newline = (U8*)strchr((char*)dest, '\n'); + if(!newline) + { + if(len) + { + lldebugs << "readLine failed - too long maybe?" << llendl; + markBad(channels, buffer); + } + return false; + } + S32 offset = -((len - 1) - (newline - dest)); + ++newline; + *newline = '\0'; + mLastRead = buffer->seek(channels.in(), last, offset); + return true; +} + +void LLHTTPResponder::markBad( + const LLChannelDescriptors& channels, + buffer_ptr_t buffer) +{ + LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER); + mState = STATE_SHORT_CIRCUIT; + LLBufferStream out(channels, buffer.get()); + out << HTTP_VERSION_STR << " 400 Bad Request\r\n\r\n\n" + << "Bad Request\n\nBad Request.\n" + << "\n\n"; +} + +// virtual +LLIOPipe::EStatus LLHTTPResponder::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER); + LLIOPipe::EStatus status = STATUS_OK; + + // parsing headers + if((STATE_NOTHING == mState) || (STATE_READING_HEADERS == mState)) + { + PUMP_DEBUG; + status = STATUS_BREAK; + mState = STATE_READING_HEADERS; + const S32 HEADER_BUFFER_SIZE = 1024; + char buf[HEADER_BUFFER_SIZE + 1]; /*Flawfinder: ignore*/ + S32 len = HEADER_BUFFER_SIZE; + +#if 0 + if(true) + { + LLBufferArray::segment_iterator_t seg_iter = buffer->beginSegment(); + char buf[1024]; /*Flawfinder: ignore*/ + while(seg_iter != buffer->endSegment()) + { + memcpy(buf, (*seg_iter).data(), (*seg_iter).size()); /*Flawfinder: ignore*/ + buf[(*seg_iter).size()] = '\0'; + llinfos << (*seg_iter).getChannel() << ": " << buf + << llendl; + ++seg_iter; + } + } +#endif + + PUMP_DEBUG; + if(readLine(channels, buffer, (U8*)buf, len)) + { + bool read_next_line = false; + bool parse_all = true; + if(mVerb.empty()) + { + read_next_line = true; + LLMemoryStream header((U8*)buf, len); + header >> mVerb; + + if((HTTP_VERB_GET == mVerb) + || (HTTP_VERB_POST == mVerb) + || (HTTP_VERB_PUT == mVerb) + || (HTTP_VERB_DELETE == mVerb)) + { + header >> mAbsPathAndQuery; + header >> mVersion; + + lldebugs << "http request: " + << mVerb + << " " << mAbsPathAndQuery + << " " << mVersion << llendl; + + std::string::size_type delimiter + = mAbsPathAndQuery.find('?'); + if (delimiter == std::string::npos) + { + mPath = mAbsPathAndQuery; + mQuery = ""; + } + else + { + mPath = mAbsPathAndQuery.substr(0, delimiter); + mQuery = mAbsPathAndQuery.substr(delimiter+1); + } + + if(!mAbsPathAndQuery.empty()) + { + if(mVersion.empty()) + { + // simple request. + parse_all = false; + mState = STATE_DONE; + mVersion.assign("HTTP/1.0"); + } + } + } + else + { + read_next_line = false; + parse_all = false; + lldebugs << "unknown http verb: " << mVerb << llendl; + markBad(channels, buffer); + } + } + if(parse_all) + { + bool keep_parsing = true; + while(keep_parsing) + { + if(read_next_line) + { + len = HEADER_BUFFER_SIZE; + readLine(channels, buffer, (U8*)buf, len); + } + if(0 == len) + { + return status; + } + if(buf[0] == '\r' && buf[1] == '\n') + { + // end-o-headers + keep_parsing = false; + mState = STATE_LOOKING_FOR_EOS; + break; + } + char* pos_colon = strchr(buf, ':'); + if(NULL == pos_colon) + { + keep_parsing = false; + lldebugs << "bad header: " << buf << llendl; + markBad(channels, buffer); + break; + } + // we've found a header + read_next_line = true; + std::string name(buf, pos_colon - buf); + std::string value(pos_colon + 2); + LLString::toLower(name); + if("content-length" == name) + { + lldebugs << "Content-Length: " << value << llendl; + mContentLength = atoi(value.c_str()); + } + } + } + } + } + + PUMP_DEBUG; + // look for the end of stream based on + if(STATE_LOOKING_FOR_EOS == mState) + { + if(0 == mContentLength) + { + mState = STATE_DONE; + } + else if(buffer->countAfter(channels.in(), mLastRead) >= mContentLength) + { + mState = STATE_DONE; + } + // else more bytes should be coming. + } + + PUMP_DEBUG; + if(STATE_DONE == mState) + { + // hey, hey, we should have everything now, so we pass it to + // a content handler. + context[CONTEXT_REQUEST]["verb"] = mVerb; + const LLHTTPNode* node = mRootNode.traverse(mPath, context); + if(node) + { + llinfos << "LLHTTPResponder::process_impl found node for " + << mAbsPathAndQuery << llendl; + + // Copy everything after mLast read to the out. + LLBufferArray::segment_iterator_t seg_iter; + seg_iter = buffer->splitAfter(mLastRead); + if(seg_iter != buffer->endSegment()) + { + LLChangeChannel change(channels.in(), channels.out()); + ++seg_iter; + std::for_each(seg_iter, buffer->endSegment(), change); + +#if 0 + seg_iter = buffer->beginSegment(); + char buf[1024]; /*Flawfinder: ignore*/ + while(seg_iter != buffer->endSegment()) + { + memcpy(buf, (*seg_iter).data(), (*seg_iter).size()); /*Flawfinder: ignore*/ + buf[(*seg_iter).size()] = '\0'; + llinfos << (*seg_iter).getChannel() << ": " << buf + << llendl; + ++seg_iter; + } +#endif + } + + // + // *FIX: get rid of extra bytes off the end + // + + // Set up a chain which will prepend a content length and + // HTTP headers. + LLPumpIO::chain_t chain; + chain.push_back(LLIOPipe::ptr_t(new LLIOFlush)); + context[CONTEXT_REQUEST]["path"] = mPath; + context[CONTEXT_REQUEST]["query-string"] = mQuery; + + const LLChainIOFactory* protocolHandler + = node->getProtocolHandler(); + if (protocolHandler) + { + protocolHandler->build(chain, context); + } + else + { + // this is a simple LLHTTPNode, so use LLHTTPPipe + chain.push_back(LLIOPipe::ptr_t(new LLHTTPPipe(*node))); + } + + // Add the header - which needs to have the same + // channel information as the link before it since it + // is part of the response. + LLIOPipe* header = new LLHTTPResponseHeader; + chain.push_back(LLIOPipe::ptr_t(header)); + + // We need to copy all of the pipes _after_ this so + // that the response goes out correctly. + LLPumpIO::links_t current_links; + pump->copyCurrentLinkInfo(current_links); + LLPumpIO::links_t::iterator link_iter = current_links.begin(); + LLPumpIO::links_t::iterator links_end = current_links.end(); + bool after_this = false; + for(; link_iter < links_end; ++link_iter) + { + if(after_this) + { + chain.push_back((*link_iter).mPipe); + } + else if(this == (*link_iter).mPipe.get()) + { + after_this = true; + } + } + + // Do the final build of the chain, and send it on + // it's way. + LLChannelDescriptors chnl = channels; + LLPumpIO::LLLinkInfo link; + LLPumpIO::links_t links; + LLPumpIO::chain_t::iterator it = chain.begin(); + LLPumpIO::chain_t::iterator end = chain.end(); + while(it != end) + { + link.mPipe = *it; + link.mChannels = chnl; + links.push_back(link); + chnl = LLBufferArray::makeChannelConsumer(chnl); + ++it; + } + pump->addChain( + links, + buffer, + context, + DEFAULT_CHAIN_EXPIRY_SECS); + + status = STATUS_STOP; + } + else + { + llinfos << "LLHTTPResponder::process_impl didn't find a node for " + << mAbsPathAndQuery << llendl; + LLBufferStream str(channels, buffer.get()); + mState = STATE_SHORT_CIRCUIT; + str << HTTP_VERSION_STR << " 404 Not Found\r\n\r\n\n" + << "Not Found\n\nNode '" << mAbsPathAndQuery + << "' not found.\n\n\n"; + } + } + + if(STATE_SHORT_CIRCUIT == mState) + { + //status = mNext->process(buffer, true, pump, context); + status = STATUS_DONE; + } + PUMP_DEBUG; + return status; +} + + + +void LLCreateHTTPPipe(LLPumpIO::chain_t& chain, const LLHTTPNode& root) +{ + chain.push_back(LLIOPipe::ptr_t(new LLHTTPResponder(root))); +} + + +class LLHTTPResponseFactory : public LLChainIOFactory +{ +public: + bool build(LLPumpIO::chain_t& chain, LLSD ctx) const + { + LLCreateHTTPPipe(chain, mTree); + return true; + } + + LLHTTPNode& getRootNode() { return mTree; } + +private: + LLHTTPNode mTree; +}; + + +LLHTTPNode& LLCreateHTTPServer( + apr_pool_t* pool, LLPumpIO& pump, U16 port) +{ + LLSocket::ptr_t socket = LLSocket::create( + pool, + LLSocket::STREAM_TCP, + port); + if(!socket) + { + llerrs << "Unable to initialize socket" << llendl; + } + + LLHTTPResponseFactory* factory = new LLHTTPResponseFactory; + boost::shared_ptr factory_ptr(factory); + + LLIOServerSocket* server = new LLIOServerSocket(pool, socket, factory_ptr); + + LLPumpIO::chain_t chain; + chain.push_back(LLIOPipe::ptr_t(server)); + pump.addChain(chain, NEVER_CHAIN_EXPIRY_SECS); + + return factory->getRootNode(); +} + diff --git a/indra/llmessage/lliohttpserver.h b/indra/llmessage/lliohttpserver.h new file mode 100644 index 0000000000..05dfdc4bf7 --- /dev/null +++ b/indra/llmessage/lliohttpserver.h @@ -0,0 +1,95 @@ +/** + * @file lliohttpserver.h + * @brief Declaration of function for creating an HTTP wire server + * @see LLIOServerSocket, LLPumpIO + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLIOHTTPSERVER_H +#define LL_LLIOHTTPSERVER_H + +#include "llapr.h" +#include "llchainio.h" +#include "llhttpnode.h" + +class LLPumpIO; + +LLHTTPNode& LLCreateHTTPServer(apr_pool_t* pool, LLPumpIO& pump, U16 port); + /**< Creates an HTTP wire server on the pump for the given TCP port. + * + * Returns the root node of the new server. Add LLHTTPNode instances + * to this root. + * + * Nodes that return NULL for getProtocolHandler(), will use the + * default handler that interprets HTTP on the wire and converts + * it into calls to get(), put(), post(), del() with appropriate + * LLSD arguments and results. + * + * To have nodes that implement some other wire protocol (XML-RPC + * for example), use the helper templates below. + */ + +void LLCreateHTTPPipe(LLPumpIO::chain_t& chain, const LLHTTPNode& root); + /**< Create a pipe on the chain that handles HTTP requests. + * The requests are served by the node tree given at root. + * + * This is primarily useful for unit testing. + */ + +/* @name Helper Templates + * + * These templates make it easy to create nodes that use thier own protocol + * handlers rather than the default. Typically, you subclass LLIOPipe to + * implement the protocol, and then add a node using the templates: + * + * rootNode->addNode("thing", new LLHTTPNodeForPipe); + * + * The templates are: + * + * LLChainIOFactoryForPipe + * - a simple factory that builds instances of a pipe + * + * LLHTTPNodeForFacotry + * - a HTTP node that uses a factory as the protocol handler + * + * LLHTTPNodeForPipe + * - a HTTP node that uses a simple factory based on a pipe + */ +//@{ + +template +class LLChainIOFactoryForPipe : public LLChainIOFactory +{ +public: + virtual bool build(LLPumpIO::chain_t& chain, LLSD context) const + { + chain.push_back(LLIOPipe::ptr_t(new Pipe)); + return true; + } +}; + +template +class LLHTTPNodeForFactory : public LLHTTPNode +{ +public: + const LLChainIOFactory* getProtocolHandler() const + { return &mProtocolHandler; } + +private: + Factory mProtocolHandler; +}; + +//@} + + +template +class LLHTTPNodeForPipe : public LLHTTPNodeForFactory< + LLChainIOFactoryForPipe > +{ +}; + + +#endif // LL_LLIOHTTPSERVER_H + diff --git a/indra/llmessage/lliopipe.cpp b/indra/llmessage/lliopipe.cpp new file mode 100644 index 0000000000..eac1a8b68a --- /dev/null +++ b/indra/llmessage/lliopipe.cpp @@ -0,0 +1,93 @@ +/** + * @file lliopipe.cpp + * @author Phoenix + * @date 2004-11-19 + * @brief Implementation of the LLIOPipe class + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "lliopipe.h" + +#include "llpumpio.h" + +static const std::string STATUS_SUCCESS_NAMES[LLIOPipe::STATUS_SUCCESS_COUNT] = +{ + std::string("STATUS_OK"), + std::string("STATUS_STOP"), + std::string("STATUS_DONE"), + std::string("STATUS_BREAK"), + std::string("STATUS_NEED_PROCESS"), +}; + +static const std::string STATUS_ERROR_NAMES[LLIOPipe::STATUS_ERROR_COUNT] = +{ + std::string("STATUS_ERROR"), + std::string("STATUS_NOT_IMPLEMENTED"), + std::string("STATUS_PRECONDITION_NOT_MET"), + std::string("STATUS_NO_CONNECTION"), + std::string("STATUS_EXPIRED"), +}; + +// Debugging schmutz for deadlock +const char *gPumpFile = ""; +S32 gPumpLine = 0; + +void pump_debug(const char *file, S32 line) +{ + gPumpFile = file; + gPumpLine = line; +} + +/** + * LLIOPipe + */ +LLIOPipe::LLIOPipe() : + mReferenceCount(0) +{ +} + +LLIOPipe::~LLIOPipe() +{ + //lldebugs << "destroying LLIOPipe" << llendl; +} + +// static +std::string LLIOPipe::lookupStatusString(EStatus status) +{ + if((status >= 0) && (status < STATUS_SUCCESS_COUNT)) + { + return STATUS_SUCCESS_NAMES[status]; + } + else + { + S32 error_code = ((S32)status * -1) - 1; + if(error_code < STATUS_ERROR_COUNT) + { + return STATUS_ERROR_NAMES[error_code]; + } + } + std::string rv; + return rv; +} + +LLIOPipe::EStatus LLIOPipe::process( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + return process_impl(channels, buffer, eos, context, pump); +} + +// virtual +LLIOPipe::EStatus LLIOPipe::handleError( + LLIOPipe::EStatus status, + LLPumpIO* pump) +{ + // by default, the error is not handled. + return status; +} diff --git a/indra/llmessage/lliopipe.h b/indra/llmessage/lliopipe.h new file mode 100644 index 0000000000..5cbe3d8743 --- /dev/null +++ b/indra/llmessage/lliopipe.h @@ -0,0 +1,291 @@ +/** + * @file lliopipe.h + * @author Phoenix + * @date 2004-11-18 + * @brief Declaration of base IO class + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLIOPIPE_H +#define LL_LLIOPIPE_H + +#include +#include +#include "apr-1/apr_poll.h" + +#include "llsd.h" + +class LLIOPipe; +class LLPumpIO; +class LLBufferArray; +class LLChannelDescriptors; + +// Debugging schmutz for deadlocks +#define LL_DEBUG_PUMPS +#ifdef LL_DEBUG_PUMPS +void pump_debug(const char *file, S32 line); +#define PUMP_DEBUG pump_debug(__FILE__, __LINE__); +#define END_PUMP_DEBUG pump_debug("none", 0); +#endif + + +/** + * intrusive pointer support + */ +namespace boost +{ + void intrusive_ptr_add_ref(LLIOPipe* p); + void intrusive_ptr_release(LLIOPipe* p); +}; + +/** + * @class LLIOPipe + * @brief This class is an abstract base class for data processing units + * @see LLPumpIO + * + * The LLIOPipe is a base class for implementing the basic non-blocking + * processing of data subsystem in our system. + * + * Implementations of this class should behave like a stateful or + * stateless signal processor. Each call to process() + * hands the pipe implementation a buffer and a set of channels in the + * buffer to process, and the pipe returns the status of the + * operation. This is an abstract base class and developer created + * concrete implementations provide block or stream based processing + * of data to implement a particular protocol. + */ +class LLIOPipe +{ +public: + /** + * @brief I have decided that IO objects should have a reference + * count. In general, you can pass bald LLIOPipe pointers around + * as you need, but if you need to maintain a reference to one, + * you need to hold a ptr_t. + */ + typedef boost::intrusive_ptr ptr_t; + + /** + * @brief Scattered memory container. + */ + typedef boost::shared_ptr buffer_ptr_t; + + /** + * @brief Enumeration for IO return codes + * + * A status code a positive integer value is considered a success, + * but may indicate special handling for future calls, for + * example, issuing a STATUS_STOP to an LLIOSocketReader instance + * will tell the instance to stop reading the socket. A status + * code with a negative value means that a problem has been + * encountered which will require further action on the caller or + * a developer to correct. Some mechanisms, such as the LLPumpIO + * may depend on this definition of success and failure. + */ + enum EStatus + { + // Processing occurred normally, future calls will be accepted. + STATUS_OK = 0, + + // Processing occured normally, but stop unsolicited calls to + // process. + STATUS_STOP = 1, + + // This pipe is done with the processing. Future calls to + // process will be accepted as long as new data is available. + STATUS_DONE = 2, + + // This pipe is requesting that it become the head in a process. + STATUS_BREAK = 3, + + // This pipe is requesting that it become the head in a process. + STATUS_NEED_PROCESS = 4, + + // Keep track of the highest number of success codes here. + STATUS_SUCCESS_COUNT = 5, + + // A generic error code. + STATUS_ERROR = -1, + + // This method has not yet been implemented. This usually + // indicates the programmer working on the pipe is not yet + // done. + STATUS_NOT_IMPLEMENTED = -2, + + // This indicates that a pipe precondition was not met. For + // example, many pipes require an element to appear after them + // in a chain (ie, mNext is not null) and will return this in + // response to method calls. To recover from this, it will + // require the caller to adjust the pipe state or may require + // a dev to adjust the code to satisfy the preconditions. + STATUS_PRECONDITION_NOT_MET = -3, + + // This means we could not connect to a remote host. + STATUS_NO_CONNECTION = -4, + + // This means we could not connect to a remote host. + STATUS_EXPIRED = -5, + + // Keep track of the count of codes here. + STATUS_ERROR_COUNT = 5, + }; + + /** + * @brief Helper function to check status. + * + * When writing code to check status codes, if you do not + * specifically check a particular value, use this method for + * checking an error condition. + * @param status The status to check. + * @return Returns true if the code indicates an error occurred. + */ + inline static bool isError(EStatus status) + { + return ((S32)status < 0); + } + + /** + * @brief Helper function to check status. + * + * When writing code to check status codes, if you do not + * specifically check a particular value, use this method for + * checking an error condition. + * @param status The status to check. + * @return Returns true if the code indicates no error was generated. + */ + inline static bool isSuccess(EStatus status) + { + return ((S32)status >= 0); + } + + /** + * @brief Helper function to turn status into a string. + * + * @param status The status to check. + * @return Returns the name of the status code or empty string on failure. + */ + static std::string lookupStatusString(EStatus status); + + /** + * @brief Process the data in buffer. + * + * @param data The data processed + * @param eos True if this function call is the last because end of stream. + * @param pump The pump which is calling process. May be NULL. + * @param context Shared meta-data for the process. + * @return Returns a status code from the operation. + */ + EStatus process( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + + /** + * @brief Give this pipe a chance to handle a generated error + * + * If this pipe is in a chain being processed by a pump, and one + * of the pipes generates an error, the pump will rewind through + * the chain to see if any of the links can handle the error. For + * example, if a connection is refused in a socket connection, the + * socket client can try to find a new destination host. Return an + * error code if this pipe does not handle the error passed in. + * @param status The status code for the error + * @param pump The pump which was calling process before the error + * was generated. + * @return Returns a status code from the operation. Returns an + * error code if the error passed in was not handled. Returns + * STATUS_OK to indicate the error has been handled. + */ + virtual EStatus handleError(EStatus status, LLPumpIO* pump); + + /** + * @brief Base Destructor - do not call delete directly. + */ + virtual ~LLIOPipe(); + +protected: + /** + * @brief Base Constructor. + */ + LLIOPipe(); + + /** + * @brief Process the data in buffer + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) = 0; + +private: + friend void boost::intrusive_ptr_add_ref(LLIOPipe* p); + friend void boost::intrusive_ptr_release(LLIOPipe* p); + U32 mReferenceCount; +}; + +namespace boost +{ + inline void intrusive_ptr_add_ref(LLIOPipe* p) + { + ++p->mReferenceCount; + } + inline void intrusive_ptr_release(LLIOPipe* p) + { + if(0 == --p->mReferenceCount) + { + delete p; + } + } +}; + + +#if 0 +/** + * @class LLIOBoiler + * @brief This class helps construct new LLIOPipe specializations + * @see LLIOPipe + * + * THOROUGH_DESCRIPTION + */ +class LLIOBoiler : public LLIOPipe +{ +public: + LLIOBoiler(); + virtual ~LLIOBoiler(); + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data in buffer + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} +}; + +// virtual +LLIOPipe::EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + return STATUS_NOT_IMPLEMENTED; +} + +#endif // #if 0 - use this block as a boilerplate + +#endif // LL_LLIOPIPE_H diff --git a/indra/llmessage/lliosocket.cpp b/indra/llmessage/lliosocket.cpp new file mode 100644 index 0000000000..7649fef0cf --- /dev/null +++ b/indra/llmessage/lliosocket.cpp @@ -0,0 +1,596 @@ +/** + * @file lliosocket.cpp + * @author Phoenix + * @date 2005-07-31 + * @brief Sockets declarations for use with the io pipes + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "lliosocket.h" + +#include "llapr.h" + +#include "llbuffer.h" +#include "llhost.h" +#include "llmemtype.h" +#include "llpumpio.h" + +// +// constants +// + +static const S32 LL_DEFAULT_LISTEN_BACKLOG = 10; +static const S32 LL_SEND_BUFFER_SIZE = 40000; +static const S32 LL_RECV_BUFFER_SIZE = 40000; +//static const U16 LL_PORT_DISCOVERY_RANGE_MIN = 13000; +//static const U16 LL_PORT_DISCOVERY_RANGE_MAX = 13050; + +// +// local methods +// + +bool is_addr_in_use(apr_status_t status) +{ +#if LL_WINDOWS + return (WSAEADDRINUSE == APR_TO_OS_ERROR(status)); +#else + return (EADDRINUSE == APR_TO_OS_ERROR(status)); +#endif +} + +/// +/// LLSocket +/// + +// static +LLSocket::ptr_t LLSocket::create(apr_pool_t* pool, EType type, U16 port) +{ + LLMemType m1(LLMemType::MTYPE_IO_TCP); + LLSocket::ptr_t rv; + apr_socket_t* socket = NULL; + apr_pool_t* new_pool = NULL; + apr_status_t status = APR_EGENERAL; + + // create a pool for the socket + status = apr_pool_create(&new_pool, pool); + if(ll_apr_warn_status(status)) + { + if(new_pool) apr_pool_destroy(new_pool); + return rv; + } + + if(STREAM_TCP == type) + { + status = apr_socket_create( + &socket, + APR_INET, + SOCK_STREAM, + APR_PROTO_TCP, + new_pool); + } + else if(DATAGRAM_UDP == type) + { + status = apr_socket_create( + &socket, + APR_INET, + SOCK_DGRAM, + APR_PROTO_UDP, + new_pool); + } + else + { + if(new_pool) apr_pool_destroy(new_pool); + return rv; + } + if(ll_apr_warn_status(status)) + { + if(new_pool) apr_pool_destroy(new_pool); + return rv; + } + rv = ptr_t(new LLSocket(socket, new_pool)); + if(port > 0) + { + apr_sockaddr_t* sa = NULL; + status = apr_sockaddr_info_get( + &sa, + APR_ANYADDR, + APR_UNSPEC, + port, + 0, + new_pool); + if(ll_apr_warn_status(status)) + { + rv.reset(); + return rv; + } + // This allows us to reuse the address on quick down/up. This + // is unlikely to create problems. + ll_apr_warn_status(apr_socket_opt_set(socket, APR_SO_REUSEADDR, 1)); + status = apr_socket_bind(socket, sa); + if(ll_apr_warn_status(status)) + { + rv.reset(); + return rv; + } + lldebugs << "Bound " << ((DATAGRAM_UDP == type) ? "udp" : "tcp") + << " socket to port: " << sa->port << llendl; + if(STREAM_TCP == type) + { + // If it's a stream based socket, we need to tell the OS + // to keep a queue of incoming connections for ACCEPT. + lldebugs << "Setting listen state for socket." << llendl; + status = apr_socket_listen( + socket, + LL_DEFAULT_LISTEN_BACKLOG); + if(ll_apr_warn_status(status)) + { + rv.reset(); + return rv; + } + } + } + else + { + // we need to indicate that we have an ephemeral port if the + // previous calls were successful. It will + port = PORT_EPHEMERAL; + } + rv->mPort = port; + rv->setOptions(); + return rv; +} + +// static +LLSocket::ptr_t LLSocket::create(apr_socket_t* socket, apr_pool_t* pool) +{ + LLMemType m1(LLMemType::MTYPE_IO_TCP); + LLSocket::ptr_t rv; + if(!socket) + { + return rv; + } + rv = ptr_t(new LLSocket(socket, pool)); + rv->mPort = PORT_EPHEMERAL; + rv->setOptions(); + return rv; +} + + +bool LLSocket::blockingConnect(const LLHost& host) +{ + if(!mSocket) return false; + apr_sockaddr_t* sa = NULL; + char ip_address[MAXADDRSTR]; /*Flawfinder: ignore*/ + host.getIPString(ip_address, MAXADDRSTR); + if(ll_apr_warn_status(apr_sockaddr_info_get( + &sa, + ip_address, + APR_UNSPEC, + host.getPort(), + 0, + mPool))) + { + return false; + } + apr_socket_timeout_set(mSocket, 1000); + if(ll_apr_warn_status(apr_socket_connect(mSocket, sa))) return false; + setOptions(); + return true; +} + +LLSocket::LLSocket(apr_socket_t* socket, apr_pool_t* pool) : + mSocket(socket), + mPool(pool), + mPort(PORT_INVALID) +{ + LLMemType m1(LLMemType::MTYPE_IO_TCP); +} + +LLSocket::~LLSocket() +{ + LLMemType m1(LLMemType::MTYPE_IO_TCP); + // *FIX: clean up memory we are holding. + //lldebugs << "Destroying LLSocket" << llendl; + if(mSocket) + { + apr_socket_close(mSocket); + } + if(mPool) + { + apr_pool_destroy(mPool); + } +} + +void LLSocket::setOptions() +{ + LLMemType m1(LLMemType::MTYPE_IO_TCP); + // set up the socket options + ll_apr_warn_status(apr_socket_timeout_set(mSocket, 0)); + ll_apr_warn_status(apr_socket_opt_set(mSocket, APR_SO_SNDBUF, LL_SEND_BUFFER_SIZE)); + ll_apr_warn_status(apr_socket_opt_set(mSocket, APR_SO_RCVBUF, LL_RECV_BUFFER_SIZE)); + +} + +/// +/// LLIOSocketReader +/// + +LLIOSocketReader::LLIOSocketReader(LLSocket::ptr_t socket) : + mSource(socket), + mInitialized(false) +{ + LLMemType m1(LLMemType::MTYPE_IO_TCP); +} + +LLIOSocketReader::~LLIOSocketReader() +{ + LLMemType m1(LLMemType::MTYPE_IO_TCP); + //lldebugs << "Destroying LLIOSocketReader" << llendl; +} + +// virtual +LLIOPipe::EStatus LLIOSocketReader::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + LLMemType m1(LLMemType::MTYPE_IO_TCP); + if(!mSource) return STATUS_PRECONDITION_NOT_MET; + if(!mInitialized) + { + PUMP_DEBUG; + // Since the read will not block, it's ok to initialize and + // attempt to read off the descriptor immediately. + mInitialized = true; + if(pump) + { + PUMP_DEBUG; + lldebugs << "Initializing poll descriptor for LLIOSocketReader." + << llendl; + apr_pollfd_t poll_fd; + poll_fd.p = NULL; + poll_fd.desc_type = APR_POLL_SOCKET; + poll_fd.reqevents = APR_POLLIN; + poll_fd.rtnevents = 0x0; + poll_fd.desc.s = mSource->getSocket(); + poll_fd.client_data = NULL; + pump->setConditional(this, &poll_fd); + } + } + //if(!buffer) + //{ + // buffer = new LLBufferArray; + //} + PUMP_DEBUG; + const apr_size_t READ_BUFFER_SIZE = 1024; + char read_buf[READ_BUFFER_SIZE]; /*Flawfinder: ignore*/ + apr_size_t len; + apr_status_t status = APR_SUCCESS; + do + { + PUMP_DEBUG; + len = READ_BUFFER_SIZE; + status = apr_socket_recv(mSource->getSocket(), read_buf, &len); + buffer->append(channels.out(), (U8*)read_buf, len); + } while((APR_SUCCESS == status) && (READ_BUFFER_SIZE == len)); + lldebugs << "socket read status: " << status << llendl; + LLIOPipe::EStatus rv = STATUS_OK; + + PUMP_DEBUG; + // *FIX: Also need to check for broken pipe + if(APR_STATUS_IS_EOF(status)) + { + // *FIX: Should we shut down the socket read? + if(pump) + { + pump->setConditional(this, NULL); + } + rv = STATUS_DONE; + eos = true; + } + else if(APR_STATUS_IS_EAGAIN(status)) + { + // everything is fine, but we can terminate this process pump. + rv = STATUS_BREAK; + } + else + { + if(ll_apr_warn_status(status)) + { + rv = STATUS_ERROR; + } + } + PUMP_DEBUG; + return rv; +} + +/// +/// LLIOSocketWriter +/// + +LLIOSocketWriter::LLIOSocketWriter(LLSocket::ptr_t socket) : + mDestination(socket), + mLastWritten(NULL), + mInitialized(false) +{ + LLMemType m1(LLMemType::MTYPE_IO_TCP); +} + +LLIOSocketWriter::~LLIOSocketWriter() +{ + LLMemType m1(LLMemType::MTYPE_IO_TCP); + //lldebugs << "Destroying LLIOSocketWriter" << llendl; +} + +// virtual +LLIOPipe::EStatus LLIOSocketWriter::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + LLMemType m1(LLMemType::MTYPE_IO_TCP); + if(!mDestination) return STATUS_PRECONDITION_NOT_MET; + if(!mInitialized) + { + PUMP_DEBUG; + // Since the write will not block, it's ok to initialize and + // attempt to write immediately. + mInitialized = true; + if(pump) + { + PUMP_DEBUG; + lldebugs << "Initializing poll descriptor for LLIOSocketWriter." + << llendl; + apr_pollfd_t poll_fd; + poll_fd.p = NULL; + poll_fd.desc_type = APR_POLL_SOCKET; + poll_fd.reqevents = APR_POLLOUT; + poll_fd.rtnevents = 0x0; + poll_fd.desc.s = mDestination->getSocket(); + poll_fd.client_data = NULL; + pump->setConditional(this, &poll_fd); + } + } + + PUMP_DEBUG; + // *FIX: Some sort of writev implementation would be much more + // efficient - not only because writev() is better, but also + // because we won't have to do as much work to find the start + // address. + LLBufferArray::segment_iterator_t it; + LLBufferArray::segment_iterator_t end = buffer->endSegment(); + LLSegment segment; + it = buffer->constructSegmentAfter(mLastWritten, segment); + /* + if(NULL == mLastWritten) + { + it = buffer->beginSegment(); + segment = (*it); + } + else + { + it = buffer->getSegment(mLastWritten); + segment = (*it); + S32 size = segment.size(); + U8* data = segment.data(); + if((data + size) == mLastWritten) + { + ++it; + segment = (*it); + } + else + { + // *FIX: check the math on this one + segment = LLSegment( + (*it).getChannelMask(), + mLastWritten + 1, + size - (mLastWritten - data)); + } + } + */ + + PUMP_DEBUG; + apr_size_t len; + bool done = false; + while(it != end) + { + PUMP_DEBUG; + if((*it).isOnChannel(channels.in())) + { + PUMP_DEBUG; + // *FIX: check return code - sockets will fail (broken, etc.) + len = (apr_size_t)segment.size(); + apr_socket_send( + mDestination->getSocket(), + (const char*)segment.data(), + &len); + mLastWritten = segment.data() + len - 1; + PUMP_DEBUG; + if((S32)len < segment.size()) + { + break; + } + } + ++it; + if(it != end) + { + segment = (*it); + } + else + { + done = true; + } + } + PUMP_DEBUG; + if(done && eos) + { + return STATUS_DONE; + } + return STATUS_OK; +} + + +/// +/// LLIOServerSocket +/// + +LLIOServerSocket::LLIOServerSocket( + apr_pool_t* pool, + LLIOServerSocket::socket_t listener, + factory_t factory) : + mPool(pool), + mListenSocket(listener), + mReactor(factory), + mInitialized(false), + mResponseTimeout(DEFAULT_CHAIN_EXPIRY_SECS) +{ + LLMemType m1(LLMemType::MTYPE_IO_TCP); +} + +LLIOServerSocket::~LLIOServerSocket() +{ + LLMemType m1(LLMemType::MTYPE_IO_TCP); + //lldebugs << "Destroying LLIOServerSocket" << llendl; +} + +void LLIOServerSocket::setResponseTimeout(F32 timeout_secs) +{ + mResponseTimeout = timeout_secs; +} + +// virtual +LLIOPipe::EStatus LLIOServerSocket::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + LLMemType m1(LLMemType::MTYPE_IO_TCP); + if(!pump) + { + llwarns << "Need a pump for server socket." << llendl; + return STATUS_ERROR; + } + if(!mInitialized) + { + PUMP_DEBUG; + // This segment sets up the pump so that we do not call + // process again until we have an incoming read, aka connect() + // from a remote host. + lldebugs << "Initializing poll descriptor for LLIOServerSocket." + << llendl; + apr_pollfd_t poll_fd; + poll_fd.p = NULL; + poll_fd.desc_type = APR_POLL_SOCKET; + poll_fd.reqevents = APR_POLLIN; + poll_fd.rtnevents = 0x0; + poll_fd.desc.s = mListenSocket->getSocket(); + poll_fd.client_data = NULL; + pump->setConditional(this, &poll_fd); + mInitialized = true; + return STATUS_OK; + } + + // we are initialized, and told to process, so we must have a + // socket waiting for a connection. + lldebugs << "accepting socket" << llendl; + + PUMP_DEBUG; + apr_pool_t* new_pool = NULL; + apr_status_t status = apr_pool_create(&new_pool, mPool); + apr_socket_t* socket = NULL; + status = apr_socket_accept( + &socket, + mListenSocket->getSocket(), + new_pool); + LLSocket::ptr_t llsocket(LLSocket::create(socket, new_pool)); + //EStatus rv = STATUS_ERROR; + if(llsocket) + { + PUMP_DEBUG; + LLPumpIO::chain_t chain; + chain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(llsocket))); + if(mReactor->build(chain, LLSD())) + { + chain.push_back(LLIOPipe::ptr_t(new LLIOSocketWriter(llsocket))); + pump->addChain(chain, mResponseTimeout); + status = STATUS_OK; + } + else + { + llwarns << "Unable to build reactor to socket." << llendl; + } + } + else + { + llwarns << "Unable to create linden socket." << llendl; + } + + PUMP_DEBUG; + // This needs to always return success, lest it get removed from + // the pump. + return STATUS_OK; +} + + +#if 0 +LLIODataSocket::LLIODataSocket( + U16 suggested_port, + U16 start_discovery_port, + apr_pool_t* pool) : + mSocket(NULL) +{ + if(!pool || (PORT_INVALID == suggested_port)) return; + if(ll_apr_warn_status(apr_socket_create(&mSocket, APR_INET, SOCK_DGRAM, APR_PROTO_UDP, pool))) return; + apr_sockaddr_t* sa = NULL; + if(ll_apr_warn_status(apr_sockaddr_info_get(&sa, APR_ANYADDR, APR_UNSPEC, suggested_port, 0, pool))) return; + apr_status_t status = apr_socket_bind(mSocket, sa); + if((start_discovery_port > 0) && is_addr_in_use(status)) + { + const U16 MAX_ATTEMPT_PORTS = 50; + for(U16 attempt_port = start_discovery_port; + attempt_port < (start_discovery_port + MAX_ATTEMPT_PORTS); + ++attempt_port) + { + sa->port = attempt_port; + sa->sa.sin.sin_port = htons(attempt_port); + status = apr_socket_bind(mSocket, sa); + if(APR_SUCCESS == status) break; + if(is_addr_in_use(status)) continue; + (void)ll_apr_warn_status(status); + } + } + if(ll_apr_warn_status(status)) return; + if(sa->port) + { + lldebugs << "Bound datagram socket to port: " << sa->port << llendl; + mPort = sa->port; + } + else + { + mPort = LLIOSocket::PORT_EPHEMERAL; + } + + // set up the socket options options + ll_apr_warn_status(apr_socket_timeout_set(mSocket, 0)); + ll_apr_warn_status(apr_socket_opt_set(mSocket, APR_SO_SNDBUF, LL_SEND_BUFFER_SIZE)); + ll_apr_warn_status(apr_socket_opt_set(mSocket, APR_SO_RCVBUF, LL_RECV_BUFFER_SIZE)); +} + +LLIODataSocket::~LLIODataSocket() +{ +} + + +#endif diff --git a/indra/llmessage/lliosocket.h b/indra/llmessage/lliosocket.h new file mode 100644 index 0000000000..fd15949b69 --- /dev/null +++ b/indra/llmessage/lliosocket.h @@ -0,0 +1,355 @@ +/** + * @file lliosocket.h + * @author Phoenix + * @date 2005-07-31 + * @brief Declaration of files used for handling sockets and associated pipes + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLIOSOCKET_H +#define LL_LLIOSOCKET_H + +/** + * The socket interface provided here is a simple wraper around apr + * sockets, with a pipe source and sink to read and write off of the + * socket. Every socket only performs non-blocking operations except + * the server socket which only performs blocking operations when an + * OS poll indicates it will not block. + */ + +#include "lliopipe.h" +#include "apr-1/apr_pools.h" +#include "apr-1/apr_network_io.h" +#include "llchainio.h" + +class LLHost; + +/** + * @class LLSocket + * @brief Implementation of a wrapper around a socket. + * + * An instance of this class represents a single socket over it's + * entire life - from uninitialized, to connected, to a listening + * socket depending on it's purpose. This class simplifies our access + * into the socket interface by only providing stream/tcp and + * datagram/udp sockets - the only types we are interested in, since + * those are the only properly supported by all of our platforms. + */ +class LLSocket +{ +public: + /** + * @brief Reference counted shared pointers to sockets. + */ + typedef boost::shared_ptr ptr_t; + + /** + * @brief Type of socket to create. + */ + enum EType + { + STREAM_TCP, + DATAGRAM_UDP, + }; + + /** + * @brief Anonymous enumeration to help identify ports + */ + enum + { + PORT_INVALID = (U16)-1, + PORT_EPHEMERAL = 0, + }; + + /** + * @brief Create a socket. + * + * This is the call you would use if you intend to create a listen + * socket. If you intend the socket to be known to external + * clients without prior port notification, do not use + * PORT_EPHEMERAL. + * @param pool The apr pool to use. A child pool will be created + * and associated with the socket. + * @param type The type of socket to create + * @param port The port for the socket + * @return A valid socket shared pointer if the call worked. + */ + static ptr_t create( + apr_pool_t* pool, + EType type, + U16 port = PORT_EPHEMERAL); + + /** + * @brief Create a LLSocket when you already have an apr socket. + * + * This method assumes an ephemeral port. This is typically used + * by calls which spawn a socket such as a call to + * accept() as in the server socket. This call should + * not fail if you have a valid apr socket. + * Because of the nature of how accept() works, you are expected + * to create a new pool for the socket, use that pool for the + * accept, and pass it in here where it will be bound with the + * socket and destroyed at the same time. + * @param socket The apr socket to use + * @param pool The pool used to create the socket. *NOTE: The pool + * passed in will be DESTROYED. + * @return A valid socket shared pointer if the call worked. + */ + static ptr_t create(apr_socket_t* socket, apr_pool_t* pool); + + /** + * @brief Perform a blocking connect to a host. Do not use in production. + * + * @param host The host to connect this socket to. + * @return Returns true if the connect was successful. + */ + bool blockingConnect(const LLHost& host); + + /** + * @brief Get the type of socket + */ + //EType getType() const { return mType; } + + /** + * @brief Get the port. + * + * This will return PORT_EPHEMERAL if bind was never called. + * @return Returns the port associated with this socket. + */ + U16 getPort() const { return mPort; } + + /** + * @brief Get the apr socket implementation. + * + * @return Returns the raw apr socket. + */ + apr_socket_t* getSocket() const { return mSocket; } + +protected: + /** + * @brief Protected constructor since should only make sockets + * with one of the two create() calls. + */ + LLSocket(apr_socket_t* socket, apr_pool_t* pool); + + /** + * @brief Set default socket options. + */ + void setOptions(); + +public: + /** + * @brief Do not call this directly. + */ + ~LLSocket(); + +protected: + // The apr socket. + apr_socket_t* mSocket; + + // our memory pool + apr_pool_t* mPool; + + // The port if we know it. + U16 mPort; + + //EType mType; +}; + +/** + * @class LLIOSocketReader + * @brief An LLIOPipe implementation which reads from a socket. + * @see LLIOPipe + * + * An instance of a socket reader wraps around an LLSocket and + * performs non-blocking reads and passes it to the next pipe in the + * chain. + */ +class LLIOSocketReader : public LLIOPipe +{ +public: + LLIOSocketReader(LLSocket::ptr_t socket); + ~LLIOSocketReader(); + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data coming in the socket. + * + * Since the socket and next pipe must exist for process to make + * any sense, this method will return STATUS_PRECONDITION_NOT_MET + * unless if they are not known. + * If a STATUS_STOP returned by the next link in the chain, this + * reader will turn of the socket polling. + * @param buffer Pointer to a buffer which needs processing. Probably NULL. + * @param bytes Number of bytes to in buffer to process. Probably 0. + * @param eos True if this function is the last. Almost always false. + * @param read Number of bytes actually processed. + * @param pump The pump which is calling process. May be NULL. + * @param context A data structure to pass structured data + * @return STATUS_OK unless the preconditions are not met. + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: + LLSocket::ptr_t mSource; + std::vector mBuffer; + bool mInitialized; +}; + +/** + * @class LLIOSocketWriter + * @brief An LLIOPipe implementation which writes to a socket + * @see LLIOPipe + * + * An instance of a socket writer wraps around an LLSocket and + * performs non-blocking writes of the data passed in. + */ +class LLIOSocketWriter : public LLIOPipe +{ +public: + LLIOSocketWriter(LLSocket::ptr_t socket); + ~LLIOSocketWriter(); + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Write the data in buffer to the socket. + * + * Since the socket pipe must exist for process to make any sense, + * this method will return STATUS_PRECONDITION_NOT_MET if it is + * not known. + * @param buffer Pointer to a buffer which needs processing. + * @param bytes Number of bytes to in buffer to process. + * @param eos True if this function is the last. + * @param read Number of bytes actually processed. + * @param pump The pump which is calling process. May be NULL. + * @param context A data structure to pass structured data + * @return A return code for the write. + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: + LLSocket::ptr_t mDestination; + U8* mLastWritten; + bool mInitialized; +}; + +/** + * @class LLIOServerSocket + * @brief An IOPipe implementation which listens and spawns connected + * sockets. + * @see LLIOPipe, LLChainIOFactory + * + * Each server socket instance coordinates with a pump to ensure it + * only processes waiting connections. It uses the provided socket, + * and assumes it is correctly initialized. When the connection is + * established, the server will call the chain factory to build a + * chain, and attach a socket reader and the front and a socket writer + * at the end. It is up to the chain factory to create something which + * correctly handles the established connection using the reader as a + * source, and the writer as the final sink. + * The newly added chain timeout is in DEFAULT_CHAIN_EXPIRY_SECS unless + * adjusted with a call to setResponseTimeout(). + */ +class LLIOServerSocket : public LLIOPipe +{ +public: + typedef LLSocket::ptr_t socket_t; + typedef boost::shared_ptr factory_t; + LLIOServerSocket(apr_pool_t* pool, socket_t listener, factory_t reactor); + virtual ~LLIOServerSocket(); + + /** + * @brief Set the timeout for the generated chains. + * + * This value is passed directly to the LLPumpIO::addChain() + * method. The default on construction is set to + * DEFAULT_CHAIN_EXPIRY_SECS which is a reasonable value for most + * applications based on this library. Avoid passing in + * NEVER_CHAIN_EXPIRY_SECS unless you have another method of + * harvesting chains. + * @param timeout_secs The seconds before timeout for the response chain. + */ + void setResponseTimeout(F32 timeout_secs); + + /* @name LLIOPipe virtual implementations + */ + //@{ +protected: + /** + * @brief Process the data in buffer + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: + apr_pool_t* mPool; + socket_t mListenSocket; + factory_t mReactor; + bool mInitialized; + F32 mResponseTimeout; +}; + +#if 0 +/** + * @class LLIODataSocket + * @brief BRIEF_DESC + * + * THOROUGH_DESCRIPTION + */ +class LLIODataSocket : public LLIOSocket +{ +public: + /** + * @brief Construct a datagram socket. + * + * If you pass in LLIOSocket::PORT_EPHEMERAL as the suggested + * port, The socket will not be in a 'listen' mode, but can still + * read data sent back to it's port. When suggested_port is not + * ephemeral or invalid and bind fails, the port discovery + * algorithm will search through a limited set of ports to + * try to find an open port. If that process fails, getPort() will + * return LLIOSocket::PORT_INVALID + * @param suggested_port The port you would like to bind. Use + * LLIOSocket::PORT_EPHEMERAL for an unspecified port. + * @param start_discovery_port The start range for + * @param pool The pool to use for allocation. + */ + LLIODataSocket( + U16 suggested_port, + U16 start_discovery_port, + apr_pool_t* pool); + virtual ~LLIODataSocket(); + +protected: + +private: + apr_socket_t* mSocket; +}; +#endif + +#endif // LL_LLIOSOCKET_H diff --git a/indra/llmessage/llioutil.cpp b/indra/llmessage/llioutil.cpp new file mode 100644 index 0000000000..b0369439e6 --- /dev/null +++ b/indra/llmessage/llioutil.cpp @@ -0,0 +1,76 @@ +/** + * @file llioutil.cpp + * @author Phoenix + * @date 2005-10-05 + * @brief Utility functionality for the io pipes. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llioutil.h" + +/** + * LLIOFlush + */ +LLIOPipe::EStatus LLIOFlush::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + eos = true; + return STATUS_OK; +} + +/** + * @class LLIOSleep + */ +LLIOPipe::EStatus LLIOSleep::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + if(mSeconds > 0.0) + { + if(pump) pump->sleepChain(mSeconds); + mSeconds = 0.0; + return STATUS_BREAK; + } + return STATUS_DONE; +} + +/** + * @class LLIOAddChain + */ +LLIOPipe::EStatus LLIOAddChain::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + pump->addChain(mChain, mTimeout); + return STATUS_DONE; +} + +/** + * LLChangeChannel + */ +LLChangeChannel::LLChangeChannel(S32 is, S32 becomes) : + mIs(is), + mBecomes(becomes) +{ +} + +void LLChangeChannel::operator()(LLSegment& segment) +{ + if(segment.isOnChannel(mIs)) + { + segment.setChannel(mBecomes); + } +} diff --git a/indra/llmessage/llioutil.h b/indra/llmessage/llioutil.h new file mode 100644 index 0000000000..3b8452918e --- /dev/null +++ b/indra/llmessage/llioutil.h @@ -0,0 +1,154 @@ +/** + * @file llioutil.h + * @author Phoenix + * @date 2005-10-05 + * @brief Helper classes for dealing with IOPipes + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLIOUTIL_H +#define LL_LLIOUTIL_H + +#include "llbuffer.h" +#include "lliopipe.h" +#include "llpumpio.h" + +/** + * @class LLIOFlush + * @brief This class is used as a mini chain head which drains the buffer. + * @see LLIOPipe + * + * An instance of this class acts as a useful chain head when all data + * is known, and you simply want to get the chain moving. + */ +class LLIOFlush : public LLIOPipe +{ +public: + LLIOFlush() {} + virtual ~LLIOFlush() {} + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data in buffer + */ + EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} +protected: +}; + +/** + * @class LLIOSleep + * @brief This is a simple helper class which will hold a chain and + * process it later using pump mechanisms + * @see LLIOPipe + */ +class LLIOSleep : public LLIOPipe +{ +public: + LLIOSleep(F64 sleep_seconds) : mSeconds(sleep_seconds) {} + virtual ~LLIOSleep() {} + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data in buffer + */ + EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} +protected: + F64 mSeconds; +}; + +/** + * @class LLIOAddChain + * @brief Simple pipe that just adds a chain to a pump. + * @see LLIOPipe + */ +class LLIOAddChain : public LLIOPipe +{ +public: + LLIOAddChain(const LLPumpIO::chain_t& chain, F32 timeout) : + mChain(chain), + mTimeout(timeout) + {} + virtual ~LLIOAddChain() {} + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data in buffer + */ + EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: + LLPumpIO::chain_t mChain; + F32 mTimeout; +}; + +/** + * @class LLChangeChannel + * @brief This class changes the channel of segments in the buffer + * @see LLBufferArray + * + * This class is useful for iterating over the segments in a buffer + * array and changing each channel that matches to a different + * channel. + * Example: + * + * set_in_to_out(LLChannelDescriptors channels, LLBufferArray* buf) + * { + * std::for_each( + * buf->beginSegment(), + * buf->endSegment(), + * LLChangeChannel(channels.in(), channels.out())); + * } + * + */ +class LLChangeChannel //: public unary_function +{ +public: + /** + * @brief Constructor for iterating over a segment range to change channel. + * + * @param is The channel to match when looking at a segment. + * @param becomes The channel to set the segment when a match is found. + */ + LLChangeChannel(S32 is, S32 becomes); + + /** + * @brief Do the work of changing the channel + */ + void operator()(LLSegment& segment); + +protected: + S32 mIs; + S32 mBecomes; +}; + + +#endif // LL_LLIOUTIL_H diff --git a/indra/llmessage/llloginflags.h b/indra/llmessage/llloginflags.h new file mode 100644 index 0000000000..b21088a61d --- /dev/null +++ b/indra/llmessage/llloginflags.h @@ -0,0 +1,37 @@ +/** + * @file llloginflags.h + * @brief Login flag constants. + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLLOGINFLAGS_H +#define LL_LLLOGINFLAGS_H + +// Is this your first login to Second Life? +const U32 LOGIN_FLAG_FIRST_LOGIN = (1 << 0); + +// Is this a trial account? +const U32 LOGIN_FLAG_TRIAL = (1 << 1); + +// Stipend paid since last login? +const U32 LOGIN_FLAG_STIPEND_SINCE_LOGIN = (1 << 2); + +// Is your account enabled? +const U32 LOGIN_FLAG_ENABLED = (1 << 3); + +// Is the Pacific Time zone (aka the server time zone) +// currently observing daylight savings time? +const U32 LOGIN_FLAG_PACIFIC_DAYLIGHT_TIME = (1 << 4); + +// Does the avatar have wearables or not +const U32 LOGIN_FLAG_GENDERED = (1 << 5); + +// Kick flags +const U32 LOGIN_KICK_OK = 0x0; +const U32 LOGIN_KICK_NO_AGENT = (1 << 0); +const U32 LOGIN_KICK_SESSION_MISMATCH = (1 << 1); +const U32 LOGIN_KICK_NO_SIMULATOR = (1 << 2); + +#endif diff --git a/indra/llmessage/llmail.cpp b/indra/llmessage/llmail.cpp new file mode 100644 index 0000000000..9fe8e89b20 --- /dev/null +++ b/indra/llmessage/llmail.cpp @@ -0,0 +1,285 @@ +/** + * @file llmail.cpp + * @brief smtp helper functions. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include +#include +#include + +#include "llmail.h" + +#include "apr-1/apr_pools.h" +#include "apr-1/apr_network_io.h" + +#include "llapr.h" +#include "llerror.h" +#include "llhost.h" +#include "net.h" + +// +// constants +// +const size_t LL_MAX_KNOWN_GOOD_MAIL_SIZE = 4096; + +static bool gMailEnabled = true; +static apr_pool_t* gMailPool; +static apr_sockaddr_t* gSockAddr; +static apr_socket_t* gMailSocket; + +// According to RFC2822 +static const boost::regex valid_subject_chars("[\\x1-\\x9\\xb\\xc\\xe-\\x7f]+"); +bool connect_smtp(); +void disconnect_smtp(); + +//#if LL_WINDOWS +//SOCKADDR_IN gMailDstAddr, gMailSrcAddr, gMailLclAddr; +//#else +//struct sockaddr_in gMailDstAddr, gMailSrcAddr, gMailLclAddr; +//#endif + +// Define this for a super-spammy mail mode. +//#define LL_LOG_ENTIRE_MAIL_MESSAGE_ON_SEND 1 + +bool connect_smtp() +{ + // Prepare an soket to talk smtp + apr_status_t status; + status = apr_socket_create( + &gMailSocket, + gSockAddr->sa.sin.sin_family, + SOCK_STREAM, + APR_PROTO_TCP, + gMailPool); + if(ll_apr_warn_status(status)) return false; + status = apr_socket_connect(gMailSocket, gSockAddr); + if(ll_apr_warn_status(status)) + { + status = apr_socket_close(gMailSocket); + ll_apr_warn_status(status); + return false; + } + return true; +} + +void disconnect_smtp() +{ + if(gMailSocket) + { + apr_status_t status = apr_socket_close(gMailSocket); + ll_apr_warn_status(status); + gMailSocket = NULL; + } +} + +// Returns TRUE on success. +// message should NOT be SMTP escaped. +BOOL send_mail(const char* from_name, const char* from_address, + const char* to_name, const char* to_address, + const char* subject, const char* message) +{ + std::string header = build_smtp_transaction( + from_name, + from_address, + to_name, + to_address, + subject); + if(header.empty()) + { + return FALSE; + } + + std::string message_str; + if(message) + { + message_str = message; + } + bool rv = send_mail(header, message_str, to_address, from_address); + if(rv) return TRUE; + return FALSE; +} + +void init_mail(const std::string& hostname, apr_pool_t* pool) +{ + gMailSocket = NULL; + if(hostname.empty() || !pool) + { + gMailPool = NULL; + gSockAddr = NULL; + } + else + { + gMailPool = pool; + + // collect all the information into a socaddr sturcture. the + // documentation is a bit unclear, but I either have to + // specify APR_UNSPEC or not specify any flags. I am not sure + // which option is better. + apr_status_t status = apr_sockaddr_info_get( + &gSockAddr, + hostname.c_str(), + APR_UNSPEC, + 25, + APR_IPV4_ADDR_OK, + gMailPool); + ll_apr_warn_status(status); + } +} + +void enable_mail(bool mail_enabled) +{ + gMailEnabled = mail_enabled; +} + +std::string build_smtp_transaction( + const char* from_name, + const char* from_address, + const char* to_name, + const char* to_address, + const char* subject) +{ + if(!from_address || !to_address) + { + llinfos << "send_mail build_smtp_transaction reject: missing to and/or" + << " from address." << llendl; + return std::string(); + } + if(! boost::regex_match(subject, valid_subject_chars)) + { + llinfos << "send_mail build_smtp_transaction reject: bad subject header: " + << "to=<" << to_address + << ">, from=<" << from_address << ">" + << llendl; + return std::string(); + } + std::ostringstream from_fmt; + if(from_name && from_name[0]) + { + // "My Name" + from_fmt << "\"" << from_name << "\" <" << from_address << ">"; + } + else + { + // + from_fmt << "<" << from_address << ">"; + } + std::ostringstream to_fmt; + if(to_name && to_name[0]) + { + to_fmt << "\"" << to_name << "\" <" << to_address << ">"; + } + else + { + to_fmt << "<" << to_address << ">"; + } + std::ostringstream header; + header + << "HELO lindenlab.com\r\n" + << "MAIL FROM:<" << from_address << ">\r\n" + << "RCPT TO:<" << to_address << ">\r\n" + << "DATA\r\n" + << "From: " << from_fmt.str() << "\r\n" + << "To: " << to_fmt.str() << "\r\n" + << "Subject: " << subject << "\r\n" + << "\r\n"; + return header.str(); +} + +bool send_mail( + const std::string& header, + const std::string& message, + const char* from_address, + const char* to_address) +{ + if(!from_address || !to_address) + { + llinfos << "send_mail reject: missing to and/or from address." + << llendl; + return false; + } + + // *FIX: this translation doesn't deal with a single period on a + // line by itself. + std::ostringstream rfc2822_msg; + for(U32 i = 0; i < message.size(); ++i) + { + switch(message[i]) + { + case '\0': + break; + case '\n': + // *NOTE: this is kinda busted if we're fed \r\n + rfc2822_msg << "\r\n"; + break; + default: + rfc2822_msg << message[i]; + break; + } + } + + if(!gMailEnabled) + { + llinfos << "send_mail reject: mail system is disabled: to=<" + << to_address << ">, from=<" << from_address + << ">" << llendl; + // Any future interface to SMTP should return this as an + // error. --mark + return true; + } + if(!gSockAddr) + { + llwarns << "send_mail reject: mail system not initialized: to=<" + << to_address << ">, from=<" << from_address + << ">" << llendl; + return false; + } + + if(!connect_smtp()) + { + llwarns << "send_mail reject: SMTP connect failure: to=<" + << to_address << ">, from=<" << from_address + << ">" << llendl; + return false; + } + + std::ostringstream smtp_fmt; + smtp_fmt << header << rfc2822_msg.str() << "\r\n" << ".\r\n" << "QUIT\r\n"; + std::string smtp_transaction = smtp_fmt.str(); + size_t original_size = smtp_transaction.size(); + apr_size_t send_size = original_size; + apr_status_t status = apr_socket_send( + gMailSocket, + smtp_transaction.c_str(), + (apr_size_t*)&send_size); + disconnect_smtp(); + if(ll_apr_warn_status(status)) + { + llwarns << "send_mail socket failure: unable to write " + << "to=<" << to_address + << ">, from=<" << from_address << ">" + << ", bytes=" << original_size + << ", sent=" << send_size << llendl; + return false; + } + if(send_size >= LL_MAX_KNOWN_GOOD_MAIL_SIZE) + { + llwarns << "send_mail message has been shown to fail in testing " + << "when sending messages larger than " << LL_MAX_KNOWN_GOOD_MAIL_SIZE + << " bytes. The next log about success is potentially a lie." << llendl; + } + llinfos << "send_mail success: " + << "to=<" << to_address + << ">, from=<" << from_address << ">" + << ", bytes=" << original_size + << ", sent=" << send_size << llendl; + +#if LL_LOG_ENTIRE_MAIL_MESSAGE_ON_SEND + llinfos << rfc2822_msg.str() << llendl; +#endif + return true; +} diff --git a/indra/llmessage/llmail.h b/indra/llmessage/llmail.h new file mode 100644 index 0000000000..e34b827f5f --- /dev/null +++ b/indra/llmessage/llmail.h @@ -0,0 +1,66 @@ +/** + * @file llmail.h + * @brief smtp helper functions. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMAIL_H +#define LL_LLMAIL_H + +#include "apr-1/apr_pools.h" + +// if hostname is NULL, then the host is resolved as 'mail' +void init_mail(const std::string& hostname, apr_pool_t* pool); + +// Allow all email transmission to be disabled/enabled. +void enable_mail(bool mail_enabled); + +// returns TRUE if the call succeeds, FALSE otherwise. +// +// Results in: +// From: "from_name" +// To: "to_name" +// Subject: subject +// message +BOOL send_mail(const char* from_name, const char* from_address, + const char* to_name, const char* to_address, + const char* subject, const char* message); + +/** + * @brief build the complete smtp transaction & header for use in an + * mail. + * + * @param from_name The name of the email sender + * @param from_address The email address for the sender + * @param to_name The name of the email recipient + * @param to_name The email recipient address + * @param subject The subject of the email + * @return Returns the complete SMTP transaction mail header. + */ +std::string build_smtp_transaction( + const char* from_name, + const char* from_address, + const char* to_name, + const char* to_address, + const char* subject); + +/** + * @brief send an email with header and body. + * + * @param header The email header. Use build_mail_header(). + * @param message The unescaped email message. + * @param from_address Used for debugging + * @param to_address Used for debugging + * @return Returns true if the message could be sent. + */ +bool send_mail( + const std::string& header, + const std::string& message, + const char* from_address, + const char* to_address); + +extern const size_t LL_MAX_KNOWN_GOOD_MAIL_SIZE; + +#endif diff --git a/indra/llmessage/llmessagethrottle.cpp b/indra/llmessage/llmessagethrottle.cpp new file mode 100644 index 0000000000..0cfaac3276 --- /dev/null +++ b/indra/llmessage/llmessagethrottle.cpp @@ -0,0 +1,135 @@ +/** + * @file llmessagethrottle.cpp + * @brief LLMessageThrottle class used for throttling messages. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "llhash.h" + +#include "llmessagethrottle.h" +#include "llframetimer.h" + +// This is used for the stl search_n function. +bool eq_message_throttle_entry(LLMessageThrottleEntry a, LLMessageThrottleEntry b) + { return a.getHash() == b.getHash(); } + +const U64 SEC_TO_USEC = 1000000; + +// How long (in microseconds) each type of message stays in its throttle list. +const U64 MAX_MESSAGE_AGE[MTC_EOF] = +{ + 10 * SEC_TO_USEC, // MTC_VIEWER_ALERT + 10 * SEC_TO_USEC // MTC_AGENT_ALERT +}; + +LLMessageThrottle::LLMessageThrottle() +{ +} + +LLMessageThrottle::~LLMessageThrottle() +{ +} + +void LLMessageThrottle::pruneEntries() +{ + // Go through each message category, and prune entries older than max age. + S32 cat; + for (cat = 0; cat < MTC_EOF; cat++) + { + message_list_t* message_list = &(mMessageList[cat]); + + // Use a reverse iterator, since entries on the back will be the oldest. + message_list_reverse_iterator_t r_iterator = message_list->rbegin(); + message_list_reverse_iterator_t r_last = message_list->rend(); + + // Look for the first entry younger than the maximum age. + F32 max_age = (F32)MAX_MESSAGE_AGE[cat]; + BOOL found = FALSE; + while (r_iterator != r_last && !found) + { + if ( LLFrameTimer::getTotalTime() - (*r_iterator).getEntryTime() < max_age ) + { + // We found a young enough entry. + found = TRUE; + + // Did we find at least one entry to remove? + if (r_iterator != message_list->rbegin()) + { + // Yes, remove it. + message_list->erase(r_iterator.base(), message_list->end()); + } + } + else + { + r_iterator++; + } + } + + // If we didn't find any entries young enough to keep, remove them all. + if (!found) + { + message_list->clear(); + } + } +} + +BOOL LLMessageThrottle::addViewerAlert(const LLUUID& to, const char* mesg) +{ + message_list_t* message_list = &(mMessageList[MTC_VIEWER_ALERT]); + + // Concatenate from,to,mesg into one string. + std::ostringstream full_mesg; + full_mesg << to << mesg; + + // Create an entry for this message. + size_t hash = llhash (full_mesg.str().c_str()); + LLMessageThrottleEntry entry(hash, LLFrameTimer::getTotalTime()); + + // Check if this message is already in the list. + message_list_iterator_t found = std::search_n(message_list->begin(), message_list->end(), + 1, entry, eq_message_throttle_entry); + + if (found == message_list->end()) + { + // This message was not found. Add it to the list. + message_list->push_front(entry); + return TRUE; + } + else + { + // This message was already in the list. + return FALSE; + } +} + +BOOL LLMessageThrottle::addAgentAlert(const LLUUID& agent, const LLUUID& task, const char* mesg) +{ + message_list_t* message_list = &(mMessageList[MTC_AGENT_ALERT]); + + // Concatenate from,to,mesg into one string. + std::ostringstream full_mesg; + full_mesg << agent << task << mesg; + + // Create an entry for this message. + size_t hash = llhash (full_mesg.str().c_str()); + LLMessageThrottleEntry entry(hash, LLFrameTimer::getTotalTime()); + + // Check if this message is already in the list. + message_list_iterator_t found = std::search_n(message_list->begin(), message_list->end(), + 1, entry, eq_message_throttle_entry); + + if (found == message_list->end()) + { + // This message was not found. Add it to the list. + message_list->push_front(entry); + return TRUE; + } + else + { + // This message was already in the list. + return FALSE; + } +} + diff --git a/indra/llmessage/llmessagethrottle.h b/indra/llmessage/llmessagethrottle.h new file mode 100644 index 0000000000..4c3c01bab9 --- /dev/null +++ b/indra/llmessage/llmessagethrottle.h @@ -0,0 +1,62 @@ +/** + * @file llmessagethrottle.h + * @brief LLMessageThrottle class used for throttling messages. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMESSAGETHROTTLE_H +#define LL_LLMESSAGETHROTTLE_H + +#include + +#include "linden_common.h" +#include "lluuid.h" + +typedef enum e_message_throttle_categories +{ + MTC_VIEWER_ALERT, + MTC_AGENT_ALERT, + MTC_EOF +} EMessageThrottleCats; + +class LLMessageThrottleEntry +{ +public: + LLMessageThrottleEntry(const size_t hash, const U64 entry_time) + : mHash(hash), mEntryTime(entry_time) {} + + size_t getHash() { return mHash; } + U64 getEntryTime() { return mEntryTime; } +protected: + size_t mHash; + U64 mEntryTime; +}; + + +class LLMessageThrottle +{ +public: + LLMessageThrottle(); + ~LLMessageThrottle(); + + BOOL addViewerAlert (const LLUUID& to, const char* mesg); + BOOL addAgentAlert (const LLUUID& agent, const LLUUID& task, const char* mesg); + + void pruneEntries(); + +protected: + typedef std::deque message_list_t; + typedef std::deque::iterator message_list_iterator_t; + typedef std::deque::reverse_iterator message_list_reverse_iterator_t; + typedef std::deque::const_iterator message_list_const_iterator_t; + typedef std::deque::const_reverse_iterator message_list_const_reverse_iterator_t; + message_list_t mMessageList[MTC_EOF]; +}; + +extern LLMessageThrottle gMessageThrottle; + +#endif + + diff --git a/indra/llmessage/llmime.cpp b/indra/llmessage/llmime.cpp new file mode 100644 index 0000000000..9df9cdf3a7 --- /dev/null +++ b/indra/llmessage/llmime.cpp @@ -0,0 +1,613 @@ +/** + * @file llmime.cpp + * @author Phoenix + * @date 2006-12-20 + * @brief Implementation of mime tools. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llmime.h" + +#include + +#include "llmemorystream.h" + +/** + * Useful constants. + */ +// Headers specified in rfc-2045 will be canonicalized below. +static const std::string CONTENT_LENGTH("Content-Length"); +static const std::string CONTENT_TYPE("Content-Type"); +static const S32 KNOWN_HEADER_COUNT = 6; +static const std::string KNOWN_HEADER[KNOWN_HEADER_COUNT] = +{ + CONTENT_LENGTH, + CONTENT_TYPE, + std::string("MIME-Version"), + std::string("Content-Transfer-Encoding"), + std::string("Content-ID"), + std::string("Content-Description"), +}; + +// parser helpers +static const std::string MULTIPART("multipart"); +static const std::string BOUNDARY("boundary"); +static const std::string END_OF_CONTENT_PARAMETER("\r\n ;\t"); +static const std::string SEPARATOR_PREFIX("--"); +//static const std::string SEPARATOR_SUFFIX("\r\n"); + +/* +Content-Type: multipart/mixed; boundary="segment" +Content-Length: 24832 + +--segment +Content-Type: image/j2c +Content-Length: 23715 + + + +--segment +Content-Type: text/xml; charset=UTF-8 + + +EOF + +*/ + +/** + * LLMimeIndex + */ + +/** + * @class LLMimeIndex::Impl + * @brief Implementation details of the mime index class. + * @see LLMimeIndex + */ +class LLMimeIndex::Impl +{ +public: + Impl() : mOffset(-1), mUseCount(1) + {} + Impl(LLSD headers, S32 offset) : + mHeaders(headers), mOffset(offset), mUseCount(1) + {} +public: + LLSD mHeaders; + S32 mOffset; + S32 mUseCount; + + typedef std::vector sub_part_t; + sub_part_t mAttachments; +}; + +LLSD LLMimeIndex::headers() const +{ + return mImpl->mHeaders; +} + +S32 LLMimeIndex::offset() const +{ + return mImpl->mOffset; +} + +S32 LLMimeIndex::contentLength() const +{ + // Find the content length in the headers. + S32 length = -1; + LLSD content_length = mImpl->mHeaders[CONTENT_LENGTH]; + if(content_length.isDefined()) + { + length = content_length.asInteger(); + } + return length; +} + +std::string LLMimeIndex::contentType() const +{ + std::string type; + LLSD content_type = mImpl->mHeaders[CONTENT_TYPE]; + if(content_type.isDefined()) + { + type = content_type.asString(); + } + return type; +} + +bool LLMimeIndex::isMultipart() const +{ + bool multipart = false; + LLSD content_type = mImpl->mHeaders[CONTENT_TYPE]; + if(content_type.isDefined()) + { + std::string type = content_type.asString(); + int comp = type.compare(0, MULTIPART.size(), MULTIPART); + if(0 == comp) + { + multipart = true; + } + } + return multipart; +} + +S32 LLMimeIndex::subPartCount() const +{ + return mImpl->mAttachments.size(); +} + +LLMimeIndex LLMimeIndex::subPart(S32 index) const +{ + LLMimeIndex part; + if((index >= 0) && (index < (S32)mImpl->mAttachments.size())) + { + part = mImpl->mAttachments[index]; + } + return part; +} + +LLMimeIndex::LLMimeIndex() : mImpl(new LLMimeIndex::Impl) +{ +} + +LLMimeIndex::LLMimeIndex(LLSD headers, S32 content_offset) : + mImpl(new LLMimeIndex::Impl(headers, content_offset)) +{ +} + +LLMimeIndex::LLMimeIndex(const LLMimeIndex& mime) : + mImpl(mime.mImpl) +{ + ++mImpl->mUseCount; +} + +LLMimeIndex::~LLMimeIndex() +{ + if(0 == --mImpl->mUseCount) + { + delete mImpl; + } +} + +LLMimeIndex& LLMimeIndex::operator=(const LLMimeIndex& mime) +{ + // Increment use count first so that we handle self assignment + // automatically. + ++mime.mImpl->mUseCount; + if(0 == --mImpl->mUseCount) + { + delete mImpl; + } + mImpl = mime.mImpl; + return *this; +} + +bool LLMimeIndex::attachSubPart(LLMimeIndex sub_part) +{ + // *FIX: Should we check for multi-part? + if(mImpl->mAttachments.size() < S32_MAX) + { + mImpl->mAttachments.push_back(sub_part); + return true; + } + return false; +} + +/** + * LLMimeParser + */ +/** + * @class LLMimeParser::Impl + * @brief Implementation details of the mime parser class. + * @see LLMimeParser + */ +class LLMimeParser::Impl +{ +public: + // @brief Constructor. + Impl(); + + // @brief Reset this for a new parse. + void reset(); + + /** + * @brief Parse a mime entity to find the index information. + * + * This method will scan the istr until a single complete mime + * entity is read, an EOF, or limit bytes have been scanned. The + * istr will be modified by this parsing, so pass in a temporary + * stream or rewind/reset the stream after this call. + * @param istr An istream which contains a mime entity. + * @param limit The maximum number of bytes to scan. + * @param separator The multipart separator if it is known. + * @param is_subpart Set true if parsing a multipart sub part. + * @param index[out] The parsed output. + * @return Returns true if an index was parsed and no errors occurred. + */ + bool parseIndex( + std::istream& istr, + S32 limit, + const std::string& separator, + bool is_subpart, + LLMimeIndex& index); + +protected: + /** + * @brief parse the headers. + * + * At the end of a successful parse, mScanCount will be at the + * start of the content. + * @param istr The input stream. + * @param limit maximum number of bytes to process + * @param headers[out] A map of the headers found. + * @return Returns true if the parse was successful. + */ + bool parseHeaders(std::istream& istr, S32 limit, LLSD& headers); + + /** + * @brief Figure out the separator string from a content type header. + * + * @param multipart_content_type The content type value from the headers. + * @return Returns the separator string. + */ + std::string findSeparator(std::string multipart_content_type); + + /** + * @brief Scan through istr past the separator. + * + * @param istr The input stream. + * @param limit Maximum number of bytes to scan. + * @param separator The multipart separator. + */ + void scanPastSeparator( + std::istream& istr, + S32 limit, + const std::string& separator); + + /** + * @brief Scan through istr past the content of the current mime part. + * + * @param istr The input stream. + * @param limit Maximum number of bytes to scan. + * @param headers The headers for this mime part. + * @param separator The multipart separator if known. + */ + void scanPastContent( + std::istream& istr, + S32 limit, + LLSD headers, + const std::string separator); + + /** + * @brief Eat CRLF. + * + * This method has no concept of the limit, so ensure you have at + * least 2 characters left to eat before hitting the limit. This + * method will increment mScanCount as it goes. + * @param istr The input stream. + * @return Returns true if CRLF was found and consumed off of istr. + */ + bool eatCRLF(std::istream& istr); + + // @brief Returns true if parsing should continue. + bool continueParse() const { return (!mError && mContinue); } + + // @brief anonymous enumeration for parse buffer size. + enum + { + LINE_BUFFER_LENGTH = 1024 + }; + +protected: + S32 mScanCount; + bool mContinue; + bool mError; + char mBuffer[LINE_BUFFER_LENGTH]; +}; + +LLMimeParser::Impl::Impl() +{ + reset(); +} + +void LLMimeParser::Impl::reset() +{ + mScanCount = 0; + mContinue = true; + mError = false; + mBuffer[0] = '\0'; +} + +bool LLMimeParser::Impl::parseIndex( + std::istream& istr, + S32 limit, + const std::string& separator, + bool is_subpart, + LLMimeIndex& index) +{ + LLSD headers; + bool parsed_something = false; + if(parseHeaders(istr, limit, headers)) + { + parsed_something = true; + LLMimeIndex mime(headers, mScanCount); + index = mime; + if(index.isMultipart()) + { + // Figure out the separator, scan past it, and recurse. + std::string ct = headers[CONTENT_TYPE].asString(); + std::string sep = findSeparator(ct); + scanPastSeparator(istr, limit, sep); + while(continueParse() && parseIndex(istr, limit, sep, true, mime)) + { + index.attachSubPart(mime); + } + } + else + { + // Scan to the end of content. + scanPastContent(istr, limit, headers, separator); + if(is_subpart) + { + scanPastSeparator(istr, limit, separator); + } + } + } + if(mError) return false; + return parsed_something; +} + +bool LLMimeParser::Impl::parseHeaders( + std::istream& istr, + S32 limit, + LLSD& headers) +{ + while(continueParse()) + { + // Get the next line. + // We subtract 1 from the limit so that we make sure + // not to read past limit when we get() the newline. + S32 max_get = llmin((S32)LINE_BUFFER_LENGTH, limit - mScanCount - 1); + istr.getline(mBuffer, max_get, '\r'); + mScanCount += istr.gcount(); + int c = istr.get(); + if(EOF == c) + { + mContinue = false; + return false; + } + ++mScanCount; + if(c != '\n') + { + mError = true; + return false; + } + if(mScanCount >= limit) + { + mContinue = false; + } + + // Check if that's the end of headers. + if('\0' == mBuffer[0]) + { + break; + } + + // Split out the name and value. + // *NOTE: The use of strchr() here is safe since mBuffer is + // guaranteed to be NULL terminated from the call to getline() + // above. + char* colon = strchr(mBuffer, ':'); + if(!colon) + { + mError = true; + return false; + } + + // Cononicalize the name part, and store the name: value in + // the headers structure. We do this by iterating through + // 'known' headers and replacing the value found with the + // correct one. + // *NOTE: Not so efficient, but iterating through a small + // subset should not be too much of an issue. + std::string name(mBuffer, colon++ - mBuffer); + while(isspace(*colon)) ++colon; + std::string value(colon); + for(S32 ii = 0; ii < KNOWN_HEADER_COUNT; ++ii) + { + if(0 == LLString::compareInsensitive( + name.c_str(), + KNOWN_HEADER[ii].c_str())) + { + name = KNOWN_HEADER[ii]; + break; + } + } + headers[name] = value; + } + if(headers.isUndefined()) return false; + return true; +} + +std::string LLMimeParser::Impl::findSeparator(std::string header) +{ + // 01234567890 + //Content-Type: multipart/mixed; boundary="segment" + std::string separator; + std::string::size_type pos = header.find(BOUNDARY); + if(std::string::npos == pos) return separator; + pos += BOUNDARY.size() + 1; + std::string::size_type end; + if(header[pos] == '"') + { + // the boundary is quoted, find the end from pos, and take the + // substring. + end = header.find('"', ++pos); + if(std::string::npos == end) + { + // poorly formed boundary. + mError = true; + } + } + else + { + // otherwise, it's every character until a whitespace, end of + // line, or another parameter begins. + end = header.find_first_of(END_OF_CONTENT_PARAMETER, pos); + if(std::string::npos == end) + { + // it goes to the end of the string. + end = header.size(); + } + } + if(!mError) separator = header.substr(pos, end - pos); + return separator; +} + +void LLMimeParser::Impl::scanPastSeparator( + std::istream& istr, + S32 limit, + const std::string& sep) +{ + std::ostringstream ostr; + ostr << SEPARATOR_PREFIX << sep; + std::string separator = ostr.str(); + bool found_separator = false; + while(!found_separator && continueParse()) + { + // Subtract 1 from the limit so that we make sure not to read + // past limit when we get() the newline. + S32 max_get = llmin((S32)LINE_BUFFER_LENGTH, limit - mScanCount - 1); + istr.getline(mBuffer, max_get, '\r'); + mScanCount += istr.gcount(); + if(istr.gcount() >= LINE_BUFFER_LENGTH - 1) + { + // that's way too long to be a separator, so ignore it. + continue; + } + int c = istr.get(); + if(EOF == c) + { + mContinue = false; + return; + } + ++mScanCount; + if(c != '\n') + { + mError = true; + return; + } + if(mScanCount >= limit) + { + mContinue = false; + } + if(0 == LLString::compareStrings(mBuffer, separator.c_str())) + { + found_separator = true; + } + } +} + +void LLMimeParser::Impl::scanPastContent( + std::istream& istr, + S32 limit, + LLSD headers, + const std::string separator) +{ + if(headers.has(CONTENT_LENGTH)) + { + S32 content_length = headers[CONTENT_LENGTH].asInteger(); + // Subtract 2 here for the \r\n after the content. + S32 max_skip = llmin(content_length, limit - mScanCount - 2); + istr.ignore(max_skip); + mScanCount += max_skip; + + // *NOTE: Check for hitting the limit and eof here before + // checking for the trailing EOF, because our mime parser has + // to gracefully handle incomplete mime entites. + if((mScanCount >= limit) || istr.eof()) + { + mContinue = false; + } + else if(!eatCRLF(istr)) + { + mError = true; + return; + } + } +} + +bool LLMimeParser::Impl::eatCRLF(std::istream& istr) +{ + int c = istr.get(); + ++mScanCount; + if(c != '\r') + { + return false; + } + c = istr.get(); + ++mScanCount; + if(c != '\n') + { + return false; + } + return true; +} + + +LLMimeParser::LLMimeParser() : mImpl(* new LLMimeParser::Impl) +{ +} + +LLMimeParser::~LLMimeParser() +{ + delete & mImpl; +} + +void LLMimeParser::reset() +{ + mImpl.reset(); +} + +bool LLMimeParser::parseIndex(std::istream& istr, LLMimeIndex& index) +{ + std::string separator; + return mImpl.parseIndex(istr, S32_MAX, separator, false, index); +} + +bool LLMimeParser::parseIndex( + const std::vector& buffer, + LLMimeIndex& index) +{ + LLMemoryStream mstr(&buffer[0], buffer.size()); + return parseIndex(mstr, buffer.size() + 1, index); +} + +bool LLMimeParser::parseIndex( + std::istream& istr, + S32 limit, + LLMimeIndex& index) +{ + std::string separator; + return mImpl.parseIndex(istr, limit, separator, false, index); +} + +bool LLMimeParser::parseIndex(const U8* buffer, S32 length, LLMimeIndex& index) +{ + LLMemoryStream mstr(buffer, length); + return parseIndex(mstr, length + 1, index); +} + +/* +bool LLMimeParser::verify(std::istream& isr, LLMimeIndex& index) const +{ + return false; +} + +bool LLMimeParser::verify(U8* buffer, S32 length, LLMimeIndex& index) const +{ + LLMemoryStream mstr(buffer, length); + return verify(mstr, index); +} +*/ diff --git a/indra/llmessage/llmime.h b/indra/llmessage/llmime.h new file mode 100644 index 0000000000..62e1204b88 --- /dev/null +++ b/indra/llmessage/llmime.h @@ -0,0 +1,274 @@ +/** + * @file llmime.h + * @author Phoenix + * @date 2006-12-20 + * @brief Declaration of mime tools. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMIME_H +#define LL_LLMIME_H + +#include +#include "llsd.h" + +/** + * This file declares various tools for parsing and creating MIME + * objects as described in RFCs 2045, 2046, 2047, 2048, and 2049. + */ + +/** + * @class LLMimeIndex + * @brief Skeletal information useful for handling mime packages. + * @see LLMimeParser + * + * An instance of this class is the parsed output from a LLMimeParser + * which then allows for easy access into a data stream to find and + * get what you want out of it. + * + * This class meant as a tool to quickly find what you seek in a + * parsed mime entity. As such, it does not have useful support for + * modification of a mime entity and specializes the interface toward + * querying data from a fixed mime entity. Modifying an instance of + * LLMimeIndx does not alter a mime entity and changes to a mime + * entity itself are not propogated into an instance of a LLMimeIndex. + * + * Usage:
+ * LLMimeIndex mime_index;
+ * std::ifstream fstr("package.mime", ios::binary);
+ * LLMimeParser parser;
+ * if(parser.parseIndex(fstr, mime_index))
+ * {
+ * std::vector content;
+ * content.resize(mime_index.contentLength());
+ * fstr.seekg(mime_index.offset(), ios::beg);
+ * // ...do work on fstr and content
+ * }
+ */ +class LLMimeIndex +{ +public: + /* @name Client interface. + */ + //@{ + /** + * @brief Get the full parsed headers for this. + * + * If there are any headers, it will be a map of header name to + * the value found on the line. The name is everything before the + * colon, and the value is the string found after the colon to the + * end of the line after trimming leading whitespace. So, for + * example: + * Content-Type: text/plain + * would become an entry in the headers of: + * headers["Content-Type"] == "text/plain" + * + * If this instance of an index was generated by the + * LLMimeParser::parseIndex() call, all header names in rfc2045 + * will be capitalized as in rfc, eg Content-Length and + * MIME-Version, not content-length and mime-version. + * @return Returns an LLSD map of header name to value. Returns + * undef if there are no headers. + */ + LLSD headers() const; + + /** + * @brief Get the content offset. + * + * @return Returns the number of bytes to the start of the data + * segment from the start of serialized mime entity. Returns -1 if + * offset is not known. + */ + S32 offset() const; + + /** + * @brief Get the length of the data segment for this mime part. + * + * @return Returns the content length in bytes. Returns -1 if + * length is not known. + */ + S32 contentLength() const; + + /** + * @brief Get the mime type associated with this node. + * + * @return Returns the mimetype. + */ + std::string contentType() const; + + /** + * @brief Helper method which simplifies parsing the return from type() + * + * @return Returns true if this is a multipart mime, and therefore + * getting subparts will succeed. + */ + bool isMultipart() const; + + /** + * @brief Get the number of atachments. + * + * @return Returns the number of sub-parts for this. + */ + S32 subPartCount() const; + + /** + * @brief Get the indicated attachment. + * + * @param index Value from 0 to (subPartCount() - 1). + * @return Returns the indicated sub-part, or an invalid mime + * index on failure. + */ + LLMimeIndex subPart(S32 index) const; + //@} + + /* @name Interface for building, testing, and helpers for typical use. + */ + //@{ + /** + * @brief Default constructor - creates a useless LLMimeIndex. + */ + LLMimeIndex(); + + /** + * @brief Full constructor. + * + * @param headers The complete headers. + * @param content_offset The number of bytes to the start of the + * data segment of this mime entity from the start of the stream + * or buffer. + */ + LLMimeIndex(LLSD headers, S32 content_offset); + + /** + * @brief Copy constructor. + * + * @param mime The other mime object. + */ + LLMimeIndex(const LLMimeIndex& mime); + + // @brief Destructor. + ~LLMimeIndex(); + + /* + * @breif Assignment operator. + * + * @param mime The other mime object. + * @return Returns this after assignment. + */ + LLMimeIndex& operator=(const LLMimeIndex& mime); + + /** + * @brief Add attachment information as a sub-part to a multipart mime. + * + * @param sub_part the part to attach. + * @return Returns true on success, false on failure. + */ + bool attachSubPart(LLMimeIndex sub_part); + //@} + +protected: + // Implementation. + class Impl; + Impl* mImpl; +}; + + +/** + * @class LLMimeParser + * @brief This class implements a MIME parser and verifier. + * + * THOROUGH_DESCRIPTION + */ +class LLMimeParser +{ +public: + // @brief Make a new mime parser. + LLMimeParser(); + + // @brief Mime parser Destructor. + ~LLMimeParser(); + + // @brief Reset internal state of this parser. + void reset(); + + + /* @name Index generation interface. + */ + //@{ + /** + * @brief Parse a stream to find the mime index information. + * + * This method will scan the istr until a single complete mime + * entity is read or EOF. The istr will be modified by this + * parsing, so pass in a temporary stream or rewind/reset the + * stream after this call. + * @param istr An istream which contains a mime entity. + * @param index[out] The parsed output. + * @return Returns true if an index was parsed and no errors occurred. + */ + bool parseIndex(std::istream& istr, LLMimeIndex& index); + + /** + * @brief Parse a vector to find the mime index information. + * + * @param buffer A vector with data to parse. + * @param index[out] The parsed output. + * @return Returns true if an index was parsed and no errors occurred. + */ + bool parseIndex(const std::vector& buffer, LLMimeIndex& index); + + /** + * @brief Parse a stream to find the mime index information. + * + * This method will scan the istr until a single complete mime + * entity is read, an EOF, or limit bytes have been scanned. The + * istr will be modified by this parsing, so pass in a temporary + * stream or rewind/reset the stream after this call. + * @param istr An istream which contains a mime entity. + * @param limit The maximum number of bytes to scan. + * @param index[out] The parsed output. + * @return Returns true if an index was parsed and no errors occurred. + */ + bool parseIndex(std::istream& istr, S32 limit, LLMimeIndex& index); + + /** + * @brief Parse a memory bufffer to find the mime index information. + * + * @param buffer The start of the buffer to parse. + * @param buffer_length The length of the buffer. + * @param index[out] The parsed output. + * @return Returns true if an index was parsed and no errors occurred. + */ + bool parseIndex(const U8* buffer, S32 buffer_length, LLMimeIndex& index); + //@} + + /** + * @brief + * + * @return + */ + //bool verify(std::istream& istr, LLMimeIndex& index) const; + + /** + * @brief + * + * @return + */ + //bool verify(U8* buffer, S32 buffer_length, LLMimeIndex& index) const; + +protected: + // Implementation. + class Impl; + Impl& mImpl; + +private: + // @brief Not implemneted to prevent copy consturction. + LLMimeParser(const LLMimeParser& parser); + + // @brief Not implemneted to prevent assignment. + LLMimeParser& operator=(const LLMimeParser& mime); +}; + +#endif // LL_LLMIME_H diff --git a/indra/llmessage/llnamevalue.cpp b/indra/llmessage/llnamevalue.cpp new file mode 100644 index 0000000000..02ddec1bf5 --- /dev/null +++ b/indra/llmessage/llnamevalue.cpp @@ -0,0 +1,2141 @@ +/** + * @file llnamevalue.cpp + * @brief class for defining name value pairs. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Examples: +// AvatarCharacter STRING RW DSV male1 + +#include "linden_common.h" + +#include + +#include "llnamevalue.h" +#include "u64.h" +#include "llstring.h" +#include "llcamera.h" + +// Anonymous enumeration to provide constants in this file. +// *NOTE: These values may be used in sscanf statements below as their +// value-1, so search for '2047' if you cange NV_BUFFER_LEN or '63' if +// you change U64_BUFFER_LEN. +enum +{ + NV_BUFFER_LEN = 2048, + U64_BUFFER_LEN = 64 +}; + +struct user_callback_t +{ + user_callback_t() {}; + user_callback_t(TNameValueCallback cb, void** data) : m_Callback(cb), m_Data(data) {} + TNameValueCallback m_Callback; + void ** m_Data; +}; +typedef std::map user_callback_map_t; +user_callback_map_t gUserCallbackMap; + +LLStringTable gNVNameTable(16384); + +char NameValueTypeStrings[NVT_EOF][NAME_VALUE_TYPE_STRING_LENGTH] = +{ + "NULL", + "STRING", + "F32", + "S32", + "VEC3", + "U32", + "CAMERA", // Deprecated, but leaving in case removing completely would cause problems + "ASSET", + "U64" +}; /*Flawfinder: Ignore*/ + +char NameValueClassStrings[NVC_EOF][NAME_VALUE_CLASS_STRING_LENGTH] = +{ + "NULL", + "R", // read only + "RW", // read write + "CB" // callback +}; /*Flawfinder: Ignore*/ + +char NameValueSendtoStrings[NVS_EOF][NAME_VALUE_SENDTO_STRING_LENGTH] = +{ + "NULL", + "S", // "Sim", formerly SIM + "DS", // "Data Sim" formerly SIM_SPACE + "SV", // "Sim Viewer" formerly SIM_VIEWER + "DSV" // "Data Sim Viewer", formerly SIM_SPACE_VIEWER +}; /*Flawfinder: Ignore*/ + + +void add_use_callback(char *name, TNameValueCallback ucb, void **user_data) +{ + char *temp = gNVNameTable.addString(name); + gUserCallbackMap[temp] = user_callback_t(ucb,user_data); +} + + +// +// Class +// + +LLNameValue::LLNameValue() +{ + baseInit(); +} + +void LLNameValue::baseInit() +{ + mNVNameTable = &gNVNameTable; + + mName = NULL; + mNameValueReference.string = NULL; + + mType = NVT_NULL; + mStringType = NameValueTypeStrings[NVT_NULL]; + + mClass = NVC_NULL; + mStringClass = NameValueClassStrings[NVC_NULL]; + + mSendto = NVS_NULL; + mStringSendto = NameValueSendtoStrings[NVS_NULL]; +} + +void LLNameValue::init(const char *name, const char *data, const char *type, const char *nvclass, const char *nvsendto, TNameValueCallback nvcb, void **user_data) +{ + mNVNameTable = &gNVNameTable; + + mName = mNVNameTable->addString(name); + + // Nota Bene: Whatever global structure manages this should have these in the name table already! + mStringType = mNVNameTable->addString(type); + if (!strcmp(mStringType, "STRING")) + { + S32 string_length = (S32)strlen(data); /*Flawfinder: Ignore*/ + mType = NVT_STRING; + + delete[] mNameValueReference.string; + + // two options here. . . data can either look like foo or "foo" + // WRONG! - this is a poorly implemented and incomplete escape + // mechanism. For example, using this scheme, there is no way + // to tell an intentional double quotes from a zero length + // string. This needs to excised. Phoenix + //if (strchr(data, '\"')) + //{ + // string_length -= 2; + // mNameValueReference.string = new char[string_length + 1];; + // strncpy(mNameValueReference.string, data + 1, string_length); + //} + //else + //{ + mNameValueReference.string = new char[string_length + 1];; + strncpy(mNameValueReference.string, data, string_length); /*Flawfinder: Ignore*/ + //} + mNameValueReference.string[string_length] = 0; + } + else if (!strcmp(mStringType, "F32")) + { + mType = NVT_F32; + mNameValueReference.f32 = new F32((F32)atof(data)); + } + else if (!strcmp(mStringType, "S32")) + { + mType = NVT_S32; + mNameValueReference.s32 = new S32(atoi(data)); + } + else if (!strcmp(mStringType, "U64")) + { + mType = NVT_U64; + mNameValueReference.u64 = new U64(str_to_U64(data)); + } + else if (!strcmp(mStringType, "VEC3")) + { + mType = NVT_VEC3; + F32 t1, t2, t3; + + // two options here. . . data can either look like 0, 1, 2 or <0, 1, 2> + + if (strchr(data, '<')) + { + sscanf(data, "<%f, %f, %f>", &t1, &t2, &t3); + } + else + { + sscanf(data, "%f, %f, %f", &t1, &t2, &t3); + } + + // finite checks + if (!llfinite(t1) || !llfinite(t2) || !llfinite(t3)) + { + t1 = 0.f; + t2 = 0.f; + t3 = 0.f; + } + + mNameValueReference.vec3 = new LLVector3(t1, t2, t3); + } + else if (!strcmp(mStringType, "U32")) + { + mType = NVT_U32; + mNameValueReference.u32 = new U32(atoi(data)); + } + else if(!strcmp(mStringType, (const char*)NameValueTypeStrings[NVT_ASSET])) + { + // assets are treated like strings, except that the name has + // meaning to an LLAssetInfo object + S32 string_length = (S32)strlen(data); /*Flawfinder: Ignore*/ + mType = NVT_ASSET; + + // two options here. . . data can either look like foo or "foo" + // WRONG! - this is a poorly implemented and incomplete escape + // mechanism. For example, using this scheme, there is no way + // to tell an intentional double quotes from a zero length + // string. This needs to excised. Phoenix + //if (strchr(data, '\"')) + //{ + // string_length -= 2; + // mNameValueReference.string = new char[string_length + 1];; + // strncpy(mNameValueReference.string, data + 1, string_length); + //} + //else + //{ + mNameValueReference.string = new char[string_length + 1];; + strncpy(mNameValueReference.string, data, string_length); /*Flawfinder: Ignore*/ + //} + mNameValueReference.string[string_length] = 0; + } + else + { + llwarns << "Unknown name value type string " << mStringType << " for " << mName << llendl; + mType = NVT_NULL; + } + + + // Nota Bene: Whatever global structure manages this should have these in the name table already! + if (!strcmp(nvclass, "R") || + !strcmp(nvclass, "READ_ONLY")) // legacy + { + mClass = NVC_READ_ONLY; + mStringClass = mNVNameTable->addString("R"); + } + else if (!strcmp(nvclass, "RW") || + !strcmp(nvclass, "READ_WRITE")) // legacy + { + mClass = NVC_READ_WRITE; + mStringClass = mNVNameTable->addString("RW"); + } + else if (!strcmp(nvclass, "CB") || + !strcmp(nvclass, "CALLBACK")) // legacy + { + mClass = NVC_CALLBACK; + mStringClass = mNVNameTable->addString("CB"); + mNameValueCB = nvcb; + mUserData = user_data; + } + else + { + // assume it's bad + mClass = NVC_NULL; + mStringClass = mNVNameTable->addString(nvclass); + mNameValueCB = NULL; + mUserData = NULL; + + // are we a user-defined call back? + for (user_callback_map_t::iterator iter = gUserCallbackMap.begin(); + iter != gUserCallbackMap.end(); iter++) + { + char* tname = iter->first; + if (tname == mStringClass) + { + mClass = NVC_CALLBACK; + mNameValueCB = (iter->second).m_Callback; + mUserData = (iter->second).m_Data; + } + } + + // Warn if we didn't find a callback + if (mClass == NVC_NULL) + { + llwarns << "Unknown user callback in name value init() for " << mName << llendl; + } + } + + // Initialize the sendto variable + if (!strcmp(nvsendto, "S") || + !strcmp(nvsendto, "SIM")) // legacy + { + mSendto = NVS_SIM; + mStringSendto = mNVNameTable->addString("S"); + } + else if (!strcmp(nvsendto, "DS") || + !strcmp(nvsendto, "SIM_SPACE")) // legacy + { + mSendto = NVS_DATA_SIM; + mStringSendto = mNVNameTable->addString("DS"); + } + else if (!strcmp(nvsendto, "SV") || + !strcmp(nvsendto, "SIM_VIEWER")) // legacy + { + mSendto = NVS_SIM_VIEWER; + mStringSendto = mNVNameTable->addString("SV"); + } + else if (!strcmp(nvsendto, "DSV") || + !strcmp(nvsendto, "SIM_SPACE_VIEWER")) // legacy + { + mSendto = NVS_DATA_SIM_VIEWER; + mStringSendto = mNVNameTable->addString("DSV"); + } + else + { + llwarns << "LLNameValue::init() - unknown sendto field " + << nvsendto << " for NV " << mName << llendl; + mSendto = NVS_NULL; + mStringSendto = mNVNameTable->addString("S"); + } + +} + + +LLNameValue::LLNameValue(const char *name, const char *data, const char *type, const char *nvclass, TNameValueCallback nvcb, void **user_data) +{ + baseInit(); + // if not specified, send to simulator only + init(name, data, type, nvclass, "SIM", nvcb, user_data); +} + + +LLNameValue::LLNameValue(const char *name, const char *data, const char *type, const char *nvclass, const char *nvsendto, TNameValueCallback nvcb, void **user_data) +{ + baseInit(); + init(name, data, type, nvclass, nvsendto, nvcb, user_data); +} + + + +// Initialize without any initial data. +LLNameValue::LLNameValue(const char *name, const char *type, const char *nvclass, TNameValueCallback nvcb, void **user_data) +{ + baseInit(); + mName = mNVNameTable->addString(name); + + // Nota Bene: Whatever global structure manages this should have these in the name table already! + mStringType = mNVNameTable->addString(type); + if (!strcmp(mStringType, "STRING")) + { + mType = NVT_STRING; + mNameValueReference.string = NULL; + } + else if (!strcmp(mStringType, "F32")) + { + mType = NVT_F32; + mNameValueReference.f32 = NULL; + } + else if (!strcmp(mStringType, "S32")) + { + mType = NVT_S32; + mNameValueReference.s32 = NULL; + } + else if (!strcmp(mStringType, "VEC3")) + { + mType = NVT_VEC3; + mNameValueReference.vec3 = NULL; + } + else if (!strcmp(mStringType, "U32")) + { + mType = NVT_U32; + mNameValueReference.u32 = NULL; + } + else if (!strcmp(mStringType, "U64")) + { + mType = NVT_U64; + mNameValueReference.u64 = NULL; + } + else if(!strcmp(mStringType, (const char*)NameValueTypeStrings[NVT_ASSET])) + { + mType = NVT_ASSET; + mNameValueReference.string = NULL; + } + else + { + mType = NVT_NULL; + llinfos << "Unknown name-value type " << mStringType << llendl; + } + + // Nota Bene: Whatever global structure manages this should have these in the name table already! + mStringClass = mNVNameTable->addString(nvclass); + if (!strcmp(mStringClass, "READ_ONLY")) + { + mClass = NVC_READ_ONLY; + } + else if (!strcmp(mStringClass, "READ_WRITE")) + { + mClass = NVC_READ_WRITE; + } + else if (!strcmp(mStringClass, "CALLBACK")) + { + mClass = NVC_READ_WRITE; + mNameValueCB = nvcb; + mUserData = user_data; + } + + // Initialize the sendto variable + mStringSendto = mNVNameTable->addString("SIM"); + mSendto = NVS_SIM; +} + + +// data is in the format: +// "NameValueName Type Class Data" +LLNameValue::LLNameValue(const char *data) +{ + baseInit(); + static char name[NV_BUFFER_LEN]; + static char type[NV_BUFFER_LEN]; + static char nvclass[NV_BUFFER_LEN]; + static char nvsendto[NV_BUFFER_LEN]; + static char nvdata[NV_BUFFER_LEN]; + + S32 i; + + S32 character_count = 0; + S32 length = 0; + + // go to first non-whitespace character + while (1) + { + if ( (*(data + character_count) == ' ') + ||(*(data + character_count) == '\n') + ||(*(data + character_count) == '\t') + ||(*(data + character_count) == '\r')) + { + character_count++; + } + else + { + break; + } + } + + // read in the name + sscanf((data + character_count), "%2047s", name); + + // bump past it and add null terminator + length = (S32)strlen(name); /* Flawfinder: ignore */ + name[length] = 0; + character_count += length; + + // go to the next non-whitespace character + while (1) + { + if ( (*(data + character_count) == ' ') + ||(*(data + character_count) == '\n') + ||(*(data + character_count) == '\t') + ||(*(data + character_count) == '\r')) + { + character_count++; + } + else + { + break; + } + } + + // read in the type + sscanf((data + character_count), "%2047s", type); + + // bump past it and add null terminator + length = (S32)strlen(type); /* Flawfinder: ignore */ + type[length] = 0; + character_count += length; + + // go to the next non-whitespace character + while (1) + { + if ( (*(data + character_count) == ' ') + ||(*(data + character_count) == '\n') + ||(*(data + character_count) == '\t') + ||(*(data + character_count) == '\r')) + { + character_count++; + } + else + { + break; + } + } + + // do we have a type argument? + for (i = NVC_READ_ONLY; i < NVC_EOF; i++) + { + if (!strncmp(NameValueClassStrings[i], data + character_count, strlen(NameValueClassStrings[i]))) /* Flawfinder: ignore */ + { + break; + } + } + + if (i != NVC_EOF) + { + // yes we do! + // read in the class + sscanf((data + character_count), "%2047s", nvclass); + + // bump past it and add null terminator + length = (S32)strlen(nvclass); /* Flawfinder: ignore */ + nvclass[length] = 0; + character_count += length; + + // go to the next non-whitespace character + while (1) + { + if ( (*(data + character_count) == ' ') + ||(*(data + character_count) == '\n') + ||(*(data + character_count) == '\t') + ||(*(data + character_count) == '\r')) + { + character_count++; + } + else + { + break; + } + } + } + else + { + // no type argument given, default to read-write + strncpy(nvclass, "READ_WRITE", sizeof(nvclass) -1); /* Flawfinder: ignore */ + nvclass[sizeof(nvclass) -1] = '\0'; + } + + // Do we have a sendto argument? + for (i = NVS_SIM; i < NVS_EOF; i++) + { + if (!strncmp(NameValueSendtoStrings[i], data + character_count, strlen(NameValueSendtoStrings[i]))) /* Flawfinder: ignore */ + { + break; + } + } + + if (i != NVS_EOF) + { + // found a sendto argument + sscanf((data + character_count), "%2047s", nvsendto); + + // add null terminator + length = (S32)strlen(nvsendto); /* Flawfinder: ignore */ + nvsendto[length] = 0; + character_count += length; + + // seek to next non-whitespace characer + while (1) + { + if ( (*(data + character_count) == ' ') + ||(*(data + character_count) == '\n') + ||(*(data + character_count) == '\t') + ||(*(data + character_count) == '\r')) + { + character_count++; + } + else + { + break; + } + } + } + else + { + // no sendto argument given, default to sim only + strncpy(nvsendto, "SIM", sizeof(nvsendto) -1); /* Flawfinder: ignore */ + nvsendto[sizeof(nvsendto) -1] ='\0'; + } + + + // copy the rest character by character into data + length = 0; + + while ( (*(nvdata + length++) = *(data + character_count++)) ) + ; + + init(name, nvdata, type, nvclass, nvsendto); +} + + +LLNameValue::~LLNameValue() +{ + mNVNameTable->removeString(mName); + mName = NULL; + + switch(mType) + { + case NVT_STRING: + case NVT_ASSET: + delete [] mNameValueReference.string; + mNameValueReference.string = NULL; + break; + case NVT_F32: + delete mNameValueReference.f32; + mNameValueReference.string = NULL; + break; + case NVT_S32: + delete mNameValueReference.s32; + mNameValueReference.string = NULL; + break; + case NVT_VEC3: + delete mNameValueReference.vec3; + mNameValueReference.string = NULL; + break; + case NVT_U32: + delete mNameValueReference.u32; + mNameValueReference.u32 = NULL; + break; + case NVT_U64: + delete mNameValueReference.u64; + mNameValueReference.u64 = NULL; + break; + default: + break; + } + + delete[] mNameValueReference.string; + mNameValueReference.string = NULL; +} + +char *LLNameValue::getString() +{ + if (mType == NVT_STRING) + { + return mNameValueReference.string; + } + else + { + llerrs << mName << " not a string!" << llendl; + return NULL; + } +} + +const char *LLNameValue::getAsset() const +{ + if (mType == NVT_ASSET) + { + return mNameValueReference.string; + } + else + { + llerrs << mName << " not an asset!" << llendl; + return NULL; + } +} + +F32 *LLNameValue::getF32() +{ + if (mType == NVT_F32) + { + return mNameValueReference.f32; + } + else + { + llerrs << mName << " not a F32!" << llendl; + return NULL; + } +} + +S32 *LLNameValue::getS32() +{ + if (mType == NVT_S32) + { + return mNameValueReference.s32; + } + else + { + llerrs << mName << " not a S32!" << llendl; + return NULL; + } +} + +U32 *LLNameValue::getU32() +{ + if (mType == NVT_U32) + { + return mNameValueReference.u32; + } + else + { + llerrs << mName << " not a U32!" << llendl; + return NULL; + } +} + +U64 *LLNameValue::getU64() +{ + if (mType == NVT_U64) + { + return mNameValueReference.u64; + } + else + { + llerrs << mName << " not a U64!" << llendl; + return NULL; + } +} + +void LLNameValue::getVec3(LLVector3 &vec) +{ + if (mType == NVT_VEC3) + { + vec = *mNameValueReference.vec3; + } + else + { + llerrs << mName << " not a Vec3!" << llendl; + } +} + +LLVector3 *LLNameValue::getVec3() +{ + if (mType == NVT_VEC3) + { + return (mNameValueReference.vec3); + } + else + { + llerrs << mName << " not a Vec3!" << llendl; + return NULL; + } +} + + +F32 LLNameValue::magnitude() +{ + switch(mType) + { + case NVT_STRING: + return (F32)(strlen(mNameValueReference.string)); /* Flawfinder: ignore */ + break; + case NVT_F32: + return (fabsf(*mNameValueReference.f32)); + break; + case NVT_S32: + return (fabsf((F32)(*mNameValueReference.s32))); + break; + case NVT_VEC3: + return (mNameValueReference.vec3->magVec()); + break; + case NVT_U32: + return (F32)(*mNameValueReference.u32); + break; + default: + llerrs << "No magnitude operation for NV type " << mStringType << llendl; + break; + } + return 0.f; +} + + +void LLNameValue::callCallback() +{ + if (mNameValueCB) + { + (*mNameValueCB)(this, mUserData); + } + else + { + llinfos << mName << " has no callback!" << llendl; + } +} + + +BOOL LLNameValue::sendToData() const +{ + return (mSendto == NVS_DATA_SIM || mSendto == NVS_DATA_SIM_VIEWER); +} + + +BOOL LLNameValue::sendToViewer() const +{ + return (mSendto == NVS_SIM_VIEWER || mSendto == NVS_DATA_SIM_VIEWER); +} + + +LLNameValue &LLNameValue::operator=(const LLNameValue &a) +{ + if (mType != a.mType) + { + return *this; + } + if (mClass == NVC_READ_ONLY) + return *this; + + BOOL b_changed = FALSE; + if ( (mClass == NVC_CALLBACK) + &&(*this != a)) + { + b_changed = TRUE; + } + + switch(a.mType) + { + case NVT_STRING: + case NVT_ASSET: + if (mNameValueReference.string) + delete [] mNameValueReference.string; + + mNameValueReference.string = new char [strlen(a.mNameValueReference.string) + 1]; /* Flawfinder: ignore */ + if(mNameValueReference.string != NULL) + { + strcpy(mNameValueReference.string, a.mNameValueReference.string); /* Flawfinder: ignore */ + } + break; + case NVT_F32: + *mNameValueReference.f32 = *a.mNameValueReference.f32; + break; + case NVT_S32: + *mNameValueReference.s32 = *a.mNameValueReference.s32; + break; + case NVT_VEC3: + *mNameValueReference.vec3 = *a.mNameValueReference.vec3; + break; + case NVT_U32: + *mNameValueReference.u32 = *a.mNameValueReference.u32; + break; + case NVT_U64: + *mNameValueReference.u64 = *a.mNameValueReference.u64; + break; + default: + llerrs << "Unknown Name value type " << (U32)a.mType << llendl; + break; + } + + if (b_changed) + { + callCallback(); + } + + return *this; +} + +void LLNameValue::setString(const char *a) +{ + if (mClass == NVC_READ_ONLY) + return; + BOOL b_changed = FALSE; + + switch(mType) + { + case NVT_STRING: + if (a) + { + if ( (mClass == NVC_CALLBACK) + &&(strcmp(this->mNameValueReference.string,a))) + { + b_changed = TRUE; + } + + if (mNameValueReference.string) + { + delete [] mNameValueReference.string; + } + + mNameValueReference.string = new char [strlen(a) + 1]; /* Flawfinder: ignore */ + if(mNameValueReference.string != NULL) + { + strcpy(mNameValueReference.string, a); /* Flawfinder: ignore */ + } + + if (b_changed) + { + callCallback(); + } + } + else + { + if (mNameValueReference.string) + delete [] mNameValueReference.string; + + mNameValueReference.string = new char [1]; + mNameValueReference.string[0] = 0; + } + break; + default: + break; + } + + if (b_changed) + { + callCallback(); + } + + return; +} + + +void LLNameValue::setAsset(const char *a) +{ + if (mClass == NVC_READ_ONLY) + return; + BOOL b_changed = FALSE; + + switch(mType) + { + case NVT_ASSET: + if (a) + { + if ( (mClass == NVC_CALLBACK) + &&(strcmp(this->mNameValueReference.string,a))) + { + b_changed = TRUE; + } + + if (mNameValueReference.string) + { + delete [] mNameValueReference.string; + } + mNameValueReference.string = new char [strlen(a) + 1]; /* Flawfinder: ignore */ + if(mNameValueReference.string != NULL) + { + strcpy(mNameValueReference.string, a); /* Flawfinder: ignore */ + } + + if (b_changed) + { + callCallback(); + } + } + else + { + if (mNameValueReference.string) + delete [] mNameValueReference.string; + + mNameValueReference.string = new char [1]; + mNameValueReference.string[0] = 0; + } + break; + default: + break; + } + if (b_changed) + { + callCallback(); + } +} + + +void LLNameValue::setF32(const F32 a) +{ + if (mClass == NVC_READ_ONLY) + return; + BOOL b_changed = FALSE; + + switch(mType) + { + case NVT_F32: + if ( (mClass == NVC_CALLBACK) + &&(*this->mNameValueReference.f32 != a)) + { + b_changed = TRUE; + } + *mNameValueReference.f32 = a; + if (b_changed) + { + callCallback(); + } + break; + default: + break; + } + if (b_changed) + { + callCallback(); + } + + return; +} + + +void LLNameValue::setS32(const S32 a) +{ + if (mClass == NVC_READ_ONLY) + return; + BOOL b_changed = FALSE; + + switch(mType) + { + case NVT_S32: + if ( (mClass == NVC_CALLBACK) + &&(*this->mNameValueReference.s32 != a)) + { + b_changed = TRUE; + } + *mNameValueReference.s32 = a; + if (b_changed) + { + callCallback(); + } + break; + case NVT_U32: + if ( (mClass == NVC_CALLBACK) + && ((S32) (*this->mNameValueReference.u32) != a)) + { + b_changed = TRUE; + } + *mNameValueReference.u32 = a; + if (b_changed) + { + callCallback(); + } + break; + case NVT_F32: + if ( (mClass == NVC_CALLBACK) + &&(*this->mNameValueReference.f32 != a)) + { + b_changed = TRUE; + } + *mNameValueReference.f32 = (F32)a; + if (b_changed) + { + callCallback(); + } + break; + default: + break; + } + if (b_changed) + { + callCallback(); + } + + return; +} + + +void LLNameValue::setU32(const U32 a) +{ + if (mClass == NVC_READ_ONLY) + return; + BOOL b_changed = FALSE; + + switch(mType) + { + case NVT_S32: + if ( (mClass == NVC_CALLBACK) + &&(*this->mNameValueReference.s32 != (S32) a)) + { + b_changed = TRUE; + } + *mNameValueReference.s32 = a; + if (b_changed) + { + callCallback(); + } + break; + case NVT_U32: + if ( (mClass == NVC_CALLBACK) + &&(*this->mNameValueReference.u32 != a)) + { + b_changed = TRUE; + } + *mNameValueReference.u32 = a; + if (b_changed) + { + callCallback(); + } + break; + case NVT_F32: + if ( (mClass == NVC_CALLBACK) + &&(*this->mNameValueReference.f32 != a)) + { + b_changed = TRUE; + } + *mNameValueReference.f32 = (F32)a; + if (b_changed) + { + callCallback(); + } + break; + default: + llerrs << "NameValue: Trying to set U32 into a " << mStringType << ", unknown conversion" << llendl; + break; + } + return; +} + + +void LLNameValue::setVec3(const LLVector3 &a) +{ + if (mClass == NVC_READ_ONLY) + return; + BOOL b_changed = FALSE; + + switch(mType) + { + case NVT_VEC3: + if ( (mClass == NVC_CALLBACK) + &&(*this->mNameValueReference.vec3 != a)) + { + b_changed = TRUE; + } + *mNameValueReference.vec3 = a; + if (b_changed) + { + callCallback(); + } + break; + default: + llerrs << "NameValue: Trying to set LLVector3 into a " << mStringType << ", unknown conversion" << llendl; + break; + } + return; +} + + +BOOL LLNameValue::nonzero() +{ + switch(mType) + { + case NVT_STRING: + if (!mNameValueReference.string) + return 0; + return (mNameValueReference.string[0] != 0); + case NVT_F32: + return (*mNameValueReference.f32 != 0.f); + case NVT_S32: + return (*mNameValueReference.s32 != 0); + case NVT_U32: + return (*mNameValueReference.u32 != 0); + case NVT_VEC3: + return (mNameValueReference.vec3->magVecSquared() != 0.f); + default: + llerrs << "NameValue: Trying to call nonzero on a " << mStringType << ", unknown conversion" << llendl; + break; + } + return FALSE; +} + +std::string LLNameValue::printNameValue() +{ + std::string buffer; + buffer = llformat("%s %s %s %s ", mName, mStringType, mStringClass, mStringSendto); + buffer += printData(); +// llinfos << "Name Value Length: " << buffer.size() + 1 << llendl; + return buffer; +} + +std::string LLNameValue::printData() +{ + std::string buffer; + switch(mType) + { + case NVT_STRING: + case NVT_ASSET: + buffer = mNameValueReference.string; + break; + case NVT_F32: + buffer = llformat("%f", *mNameValueReference.f32); + break; + case NVT_S32: + buffer = llformat("%d", *mNameValueReference.s32); + break; + case NVT_U32: + buffer = llformat("%u", *mNameValueReference.u32); + break; + case NVT_U64: + { + char u64_string[U64_BUFFER_LEN]; /* Flawfinder: ignore */ + U64_to_str(*mNameValueReference.u64, u64_string, sizeof(u64_string)); + buffer = u64_string; + } + break; + case NVT_VEC3: + buffer = llformat( "%f, %f, %f", mNameValueReference.vec3->mV[VX], mNameValueReference.vec3->mV[VY], mNameValueReference.vec3->mV[VZ]); + break; + default: + llerrs << "Trying to print unknown NameValue type " << mStringType << llendl; + break; + } + return buffer; +} + +std::ostream& operator<<(std::ostream& s, const LLNameValue &a) +{ + switch(a.mType) + { + case NVT_STRING: + case NVT_ASSET: + s << a.mNameValueReference.string; + break; + case NVT_F32: + s << (*a.mNameValueReference.f32); + break; + case NVT_S32: + s << *(a.mNameValueReference.s32); + break; + case NVT_U32: + s << *(a.mNameValueReference.u32); + break; + case NVT_U64: + { + char u64_string[U64_BUFFER_LEN]; /* Flawfinder: ignore */ + U64_to_str(*a.mNameValueReference.u64, u64_string, sizeof(u64_string)); + s << u64_string; + } + case NVT_VEC3: + s << *(a.mNameValueReference.vec3); + break; + default: + llerrs << "Trying to print unknown NameValue type " << a.mStringType << llendl; + break; + } + return s; +} + + +// nota bene: return values aren't static for now to prevent memory leaks + +LLNameValue &operator+(const LLNameValue &a, const LLNameValue &b) +{ + static LLNameValue retval; + + switch(a.mType) + { + case NVT_STRING: + if (b.mType == NVT_STRING) + { + retval.mType = a.mType; + retval.mStringType = NameValueTypeStrings[a.mType]; + + S32 length1 = (S32)strlen(a.mNameValueReference.string); /* Flawfinder: Ignore */ + S32 length2 = (S32)strlen(b.mNameValueReference.string); /* Flawfinder: Ignore */ + delete [] retval.mNameValueReference.string; + retval.mNameValueReference.string = new char[length1 + length2 + 1]; + if(retval.mNameValueReference.string != NULL) + { + strcpy(retval.mNameValueReference.string, a.mNameValueReference.string); /* Flawfinder: Ignore */ + strcat(retval.mNameValueReference.string, b.mNameValueReference.string); /* Flawfinder: Ignore */ + } + } + break; + case NVT_F32: + if (b.mType == NVT_F32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.f32 + *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.f32 + *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.f32 + *b.mNameValueReference.u32); + } + break; + case NVT_S32: + if (b.mType == NVT_F32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.s32 + *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + retval.mType = NVT_S32; + retval.mStringType = NameValueTypeStrings[NVT_S32]; + delete retval.mNameValueReference.s32; + retval.mNameValueReference.s32 = new S32(*a.mNameValueReference.s32 + *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + retval.mType = NVT_S32; + retval.mStringType = NameValueTypeStrings[NVT_S32]; + delete retval.mNameValueReference.s32; + retval.mNameValueReference.s32 = new S32(*a.mNameValueReference.s32 + *b.mNameValueReference.u32); + } + break; + case NVT_U32: + if (b.mType == NVT_F32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.u32 + *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + retval.mType = NVT_S32; + retval.mStringType = NameValueTypeStrings[NVT_S32]; + delete retval.mNameValueReference.s32; + retval.mNameValueReference.s32 = new S32(*a.mNameValueReference.u32 + *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + retval.mType = NVT_U32; + retval.mStringType = NameValueTypeStrings[NVT_U32]; + delete retval.mNameValueReference.u32; + retval.mNameValueReference.u32 = new U32(*a.mNameValueReference.u32 + *b.mNameValueReference.u32); + } + break; + case NVT_VEC3: + if ( (a.mType == b.mType) + &&(a.mType == NVT_VEC3)) + { + retval.mType = a.mType; + retval.mStringType = NameValueTypeStrings[a.mType]; + delete retval.mNameValueReference.vec3; + retval.mNameValueReference.vec3 = new LLVector3(*a.mNameValueReference.vec3 + *b.mNameValueReference.vec3); + } + break; + default: + llerrs << "Unknown add of NV type " << a.mStringType << " to " << b.mStringType << llendl; + break; + } + return retval; +} + +LLNameValue &operator-(const LLNameValue &a, const LLNameValue &b) +{ + static LLNameValue retval; + + switch(a.mType) + { + case NVT_STRING: + break; + case NVT_F32: + if (b.mType == NVT_F32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.f32 - *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.f32 - *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.f32 - *b.mNameValueReference.u32); + } + break; + case NVT_S32: + if (b.mType == NVT_F32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.s32 - *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + retval.mType = NVT_S32; + retval.mStringType = NameValueTypeStrings[NVT_S32]; + delete retval.mNameValueReference.s32; + retval.mNameValueReference.s32 = new S32(*a.mNameValueReference.s32 - *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + retval.mType = NVT_S32; + retval.mStringType = NameValueTypeStrings[NVT_S32]; + delete retval.mNameValueReference.s32; + retval.mNameValueReference.s32 = new S32(*a.mNameValueReference.s32 - *b.mNameValueReference.u32); + } + break; + case NVT_U32: + if (b.mType == NVT_F32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.u32 - *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + retval.mType = NVT_S32; + retval.mStringType = NameValueTypeStrings[NVT_S32]; + delete retval.mNameValueReference.s32; + retval.mNameValueReference.s32 = new S32(*a.mNameValueReference.u32 - *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + retval.mType = NVT_U32; + retval.mStringType = NameValueTypeStrings[NVT_U32]; + delete retval.mNameValueReference.u32; + retval.mNameValueReference.u32 = new U32(*a.mNameValueReference.u32 - *b.mNameValueReference.u32); + } + break; + case NVT_VEC3: + if ( (a.mType == b.mType) + &&(a.mType == NVT_VEC3)) + { + retval.mType = a.mType; + retval.mStringType = NameValueTypeStrings[a.mType]; + delete retval.mNameValueReference.vec3; + retval.mNameValueReference.vec3 = new LLVector3(*a.mNameValueReference.vec3 - *b.mNameValueReference.vec3); + } + break; + default: + llerrs << "Unknown subtract of NV type " << a.mStringType << " to " << b.mStringType << llendl; + break; + } + return retval; +} + +LLNameValue &operator*(const LLNameValue &a, const LLNameValue &b) +{ + static LLNameValue retval; + + switch(a.mType) + { + case NVT_STRING: + break; + case NVT_F32: + if (b.mType == NVT_F32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.f32 * *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.f32 * *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.f32 * *b.mNameValueReference.u32); + } + break; + case NVT_S32: + if (b.mType == NVT_F32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.s32 * *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + retval.mType = NVT_S32; + retval.mStringType = NameValueTypeStrings[NVT_S32]; + delete retval.mNameValueReference.s32; + retval.mNameValueReference.s32 = new S32(*a.mNameValueReference.s32 * *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + retval.mType = NVT_S32; + retval.mStringType = NameValueTypeStrings[NVT_S32]; + delete retval.mNameValueReference.s32; + retval.mNameValueReference.s32 = new S32(*a.mNameValueReference.s32 * *b.mNameValueReference.u32); + } + break; + case NVT_U32: + if (b.mType == NVT_F32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.u32 * *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + retval.mType = NVT_S32; + retval.mStringType = NameValueTypeStrings[NVT_S32]; + delete retval.mNameValueReference.s32; + retval.mNameValueReference.s32 = new S32(*a.mNameValueReference.u32 * *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + retval.mType = NVT_U32; + retval.mStringType = NameValueTypeStrings[NVT_U32]; + delete retval.mNameValueReference.u32; + retval.mNameValueReference.u32 = new U32(*a.mNameValueReference.u32 * *b.mNameValueReference.u32); + } + break; + case NVT_VEC3: + if ( (a.mType == b.mType) + &&(a.mType == NVT_VEC3)) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[a.mType]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32((*a.mNameValueReference.vec3) * (*b.mNameValueReference.vec3)); + } + break; + default: + llerrs << "Unknown multiply of NV type " << a.mStringType << " to " << b.mStringType << llendl; + break; + } + return retval; +} + +LLNameValue &operator/(const LLNameValue &a, const LLNameValue &b) +{ + static LLNameValue retval; + + switch(a.mType) + { + case NVT_STRING: + break; + case NVT_F32: + if (b.mType == NVT_F32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.f32 / *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.f32 / *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.f32 / *b.mNameValueReference.u32); + } + break; + case NVT_S32: + if (b.mType == NVT_F32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.s32 / *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + retval.mType = NVT_S32; + retval.mStringType = NameValueTypeStrings[NVT_S32]; + delete retval.mNameValueReference.s32; + retval.mNameValueReference.s32 = new S32(*a.mNameValueReference.s32 / *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + retval.mType = NVT_S32; + retval.mStringType = NameValueTypeStrings[NVT_S32]; + delete retval.mNameValueReference.s32; + retval.mNameValueReference.s32 = new S32(*a.mNameValueReference.s32 / *b.mNameValueReference.u32); + } + break; + case NVT_U32: + if (b.mType == NVT_F32) + { + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.u32 / *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + retval.mType = NVT_S32; + retval.mStringType = NameValueTypeStrings[NVT_S32]; + delete retval.mNameValueReference.s32; + retval.mNameValueReference.s32 = new S32(*a.mNameValueReference.u32 / *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + retval.mType = NVT_U32; + retval.mStringType = NameValueTypeStrings[NVT_U32]; + delete retval.mNameValueReference.u32; + retval.mNameValueReference.u32 = new U32(*a.mNameValueReference.u32 / *b.mNameValueReference.u32); + } + break; + default: + llerrs << "Unknown divide of NV type " << a.mStringType << " to " << b.mStringType << llendl; + break; + } + return retval; +} + +LLNameValue &operator%(const LLNameValue &a, const LLNameValue &b) +{ + static LLNameValue retval; + + switch(a.mType) + { + case NVT_STRING: + break; + case NVT_F32: + break; + case NVT_S32: + if (b.mType == NVT_S32) + { + retval.mType = NVT_S32; + retval.mStringType = NameValueTypeStrings[NVT_S32]; + delete retval.mNameValueReference.s32; + retval.mNameValueReference.s32 = new S32(*a.mNameValueReference.s32 % *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + retval.mType = NVT_S32; + retval.mStringType = NameValueTypeStrings[NVT_S32]; + delete retval.mNameValueReference.s32; + retval.mNameValueReference.s32 = new S32(*a.mNameValueReference.s32 % *b.mNameValueReference.u32); + } + break; + case NVT_U32: + if (b.mType == NVT_S32) + { + retval.mType = NVT_S32; + retval.mStringType = NameValueTypeStrings[NVT_S32]; + delete retval.mNameValueReference.s32; + retval.mNameValueReference.s32 = new S32(*a.mNameValueReference.u32 % *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + retval.mType = NVT_U32; + retval.mStringType = NameValueTypeStrings[NVT_U32]; + delete retval.mNameValueReference.u32; + retval.mNameValueReference.u32 = new U32(*a.mNameValueReference.u32 % *b.mNameValueReference.u32); + } + break; + case NVT_VEC3: + if ( (a.mType == b.mType) + &&(a.mType == NVT_VEC3)) + { + retval.mType = a.mType; + retval.mStringType = NameValueTypeStrings[a.mType]; + delete retval.mNameValueReference.vec3; + retval.mNameValueReference.vec3 = new LLVector3(*a.mNameValueReference.vec3 % *b.mNameValueReference.vec3); + } + break; + default: + llerrs << "Unknown % of NV type " << a.mStringType << " to " << b.mStringType << llendl; + break; + } + return retval; +} + + +// Multiplying anything times a float gives you some floats +LLNameValue &operator*(const LLNameValue &a, F32 k) +{ + static LLNameValue retval; + + switch(a.mType) + { + case NVT_STRING: + break; + case NVT_F32: + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.f32 * k); + break; + case NVT_S32: + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.s32 * k); + break; + case NVT_U32: + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.u32 * k); + break; + case NVT_VEC3: + retval.mType = a.mType; + retval.mStringType = NameValueTypeStrings[a.mType]; + delete retval.mNameValueReference.vec3; + retval.mNameValueReference.vec3 = new LLVector3(*a.mNameValueReference.vec3 * k); + break; + default: + llerrs << "Unknown multiply of NV type " << a.mStringType << " with F32" << llendl; + break; + } + return retval; +} + + +LLNameValue &operator*(F32 k, const LLNameValue &a) +{ + static LLNameValue retval; + + switch(a.mType) + { + case NVT_STRING: + break; + case NVT_F32: + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.f32 * k); + break; + case NVT_S32: + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.s32 * k); + break; + case NVT_U32: + retval.mType = NVT_F32; + retval.mStringType = NameValueTypeStrings[NVT_F32]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(*a.mNameValueReference.u32 * k); + break; + case NVT_VEC3: + retval.mType = a.mType; + retval.mStringType = NameValueTypeStrings[a.mType]; + delete retval.mNameValueReference.vec3; + retval.mNameValueReference.vec3 = new LLVector3(*a.mNameValueReference.vec3 * k); + break; + default: + llerrs << "Unknown multiply of NV type " << a.mStringType << " with F32" << llendl; + break; + } + return retval; +} + + +bool operator==(const LLNameValue &a, const LLNameValue &b) +{ + switch(a.mType) + { + case NVT_STRING: + if (b.mType == NVT_STRING) + { + if (!a.mNameValueReference.string) + return FALSE; + if (!b.mNameValueReference.string) + return FALSE; + return (!strcmp(a.mNameValueReference.string, b.mNameValueReference.string)); + } + break; + case NVT_F32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.f32 == *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return (*a.mNameValueReference.f32 == *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.f32 == *b.mNameValueReference.u32); + } + break; + case NVT_S32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.s32 == *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return (*a.mNameValueReference.s32 == *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.s32 == (S32) *b.mNameValueReference.u32); + } + break; + case NVT_U32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.u32 == *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return ((S32) *a.mNameValueReference.u32 == *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.u32 == *b.mNameValueReference.u32); + } + break; + case NVT_VEC3: + if ( (a.mType == b.mType) + &&(a.mType == NVT_VEC3)) + { + return (*a.mNameValueReference.vec3 == *b.mNameValueReference.vec3); + } + break; + default: + llerrs << "Unknown == NV type " << a.mStringType << " with " << b.mStringType << llendl; + break; + } + return FALSE; +} + +bool operator<=(const LLNameValue &a, const LLNameValue &b) +{ + switch(a.mType) + { + case NVT_STRING: + if (b.mType == NVT_STRING) + { + S32 retval = strcmp(a.mNameValueReference.string, b.mNameValueReference.string); + return (retval <= 0); + } + break; + case NVT_F32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.f32 <= *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return (*a.mNameValueReference.f32 <= *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.f32 <= *b.mNameValueReference.u32); + } + break; + case NVT_S32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.s32 <= *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return (*a.mNameValueReference.s32 <= *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.s32 <= (S32) *b.mNameValueReference.u32); + } + break; + case NVT_U32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.u32 <= *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return ((S32) *a.mNameValueReference.u32 <= *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.u32 <= *b.mNameValueReference.u32); + } + break; + default: + llerrs << "Unknown <= NV type " << a.mStringType << " with " << b.mStringType << llendl; + break; + } + return FALSE; +} + + +bool operator>=(const LLNameValue &a, const LLNameValue &b) +{ + switch(a.mType) + { + case NVT_STRING: + if ( (a.mType == b.mType) + &&(a.mType == NVT_STRING)) + { + S32 retval = strcmp(a.mNameValueReference.string, b.mNameValueReference.string); + return (retval >= 0); + } + break; + case NVT_F32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.f32 >= *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return (*a.mNameValueReference.f32 >= *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.f32 >= *b.mNameValueReference.u32); + } + break; + case NVT_S32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.s32 >= *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return (*a.mNameValueReference.s32 >= *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.s32 >= (S32) *b.mNameValueReference.u32); + } + break; + case NVT_U32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.u32 >= *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return ((S32) *a.mNameValueReference.u32 >= *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.u32 >= *b.mNameValueReference.u32); + } + break; + default: + llerrs << "Unknown >= NV type " << a.mStringType << " with " << b.mStringType << llendl; + break; + } + return FALSE; +} + + +bool operator<(const LLNameValue &a, const LLNameValue &b) +{ + switch(a.mType) + { + case NVT_STRING: + if ( (a.mType == b.mType) + &&(a.mType == NVT_STRING)) + { + S32 retval = strcmp(a.mNameValueReference.string, b.mNameValueReference.string); + return (retval < 0); + } + break; + case NVT_F32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.f32 < *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return (*a.mNameValueReference.f32 < *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.f32 < *b.mNameValueReference.u32); + } + break; + case NVT_S32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.s32 < *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return (*a.mNameValueReference.s32 < *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.s32 < (S32) *b.mNameValueReference.u32); + } + break; + case NVT_U32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.u32 < *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return ((S32) *a.mNameValueReference.u32 < *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.u32 < *b.mNameValueReference.u32); + } + break; + default: + llerrs << "Unknown < NV type " << a.mStringType << " with " << b.mStringType << llendl; + break; + } + return FALSE; +} + + +bool operator>(const LLNameValue &a, const LLNameValue &b) +{ + switch(a.mType) + { + case NVT_STRING: + if ( (a.mType == b.mType) + &&(a.mType == NVT_STRING)) + { + S32 retval = strcmp(a.mNameValueReference.string, b.mNameValueReference.string); + return (retval > 0); + } + break; + case NVT_F32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.f32 > *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return (*a.mNameValueReference.f32 > *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.f32 > *b.mNameValueReference.u32); + } + break; + case NVT_S32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.s32 > *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return (*a.mNameValueReference.s32 > *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.s32 > (S32) *b.mNameValueReference.u32); + } + break; + case NVT_U32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.u32 > *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return ((S32) *a.mNameValueReference.u32 > *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.u32 > *b.mNameValueReference.u32); + } + break; + default: + llerrs << "Unknown > NV type " << a.mStringType << " with " << b.mStringType << llendl; + break; + } + return FALSE; +} + +bool operator!=(const LLNameValue &a, const LLNameValue &b) +{ + switch(a.mType) + { + case NVT_STRING: + if ( (a.mType == b.mType) + &&(a.mType == NVT_STRING)) + { + return (strcmp(a.mNameValueReference.string, b.mNameValueReference.string)) ? true : false; + } + break; + case NVT_F32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.f32 != *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return (*a.mNameValueReference.f32 != *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.f32 != *b.mNameValueReference.u32); + } + break; + case NVT_S32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.s32 != *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return (*a.mNameValueReference.s32 != *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.s32 != (S32) *b.mNameValueReference.u32); + } + break; + case NVT_U32: + if (b.mType == NVT_F32) + { + return (*a.mNameValueReference.u32 != *b.mNameValueReference.f32); + } + else if (b.mType == NVT_S32) + { + return ((S32) *a.mNameValueReference.u32 != *b.mNameValueReference.s32); + } + else if (b.mType == NVT_U32) + { + return (*a.mNameValueReference.u32 != *b.mNameValueReference.u32); + } + break; + case NVT_VEC3: + if ( (a.mType == b.mType) + &&(a.mType == NVT_VEC3)) + { + return (*a.mNameValueReference.vec3 != *b.mNameValueReference.vec3); + } + break; + default: + llerrs << "Unknown != NV type " << a.mStringType << " with " << b.mStringType << llendl; + break; + } + return FALSE; +} + + +LLNameValue &operator-(const LLNameValue &a) +{ + static LLNameValue retval; + + switch(a.mType) + { + case NVT_STRING: + break; + case NVT_F32: + retval.mType = a.mType; + retval.mStringType = NameValueTypeStrings[a.mType]; + delete retval.mNameValueReference.f32; + retval.mNameValueReference.f32 = new F32(-*a.mNameValueReference.f32); + break; + case NVT_S32: + retval.mType = a.mType; + retval.mStringType = NameValueTypeStrings[a.mType]; + delete retval.mNameValueReference.s32; + retval.mNameValueReference.s32 = new S32(-*a.mNameValueReference.s32); + break; + case NVT_U32: + retval.mType = NVT_S32; + retval.mStringType = NameValueTypeStrings[NVT_S32]; + delete retval.mNameValueReference.s32; + // Can't do unary minus on U32, doesn't work. + retval.mNameValueReference.s32 = new S32(-S32(*a.mNameValueReference.u32)); + break; + case NVT_VEC3: + retval.mType = a.mType; + retval.mStringType = NameValueTypeStrings[a.mType]; + delete retval.mNameValueReference.vec3; + retval.mNameValueReference.vec3 = new LLVector3(-*a.mNameValueReference.vec3); + break; + default: + llerrs << "Unknown - NV type " << a.mStringType << llendl; + break; + } + return retval; +} diff --git a/indra/llmessage/llnamevalue.h b/indra/llmessage/llnamevalue.h new file mode 100644 index 0000000000..27355277ca --- /dev/null +++ b/indra/llmessage/llnamevalue.h @@ -0,0 +1,186 @@ +/** + * @file llnamevalue.h + * @brief class for defining name value pairs. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLNAMEVALUE_H +#define LL_LLNAMEVALUE_H + +#include +#include + +#include "string_table.h" +#include "llskipmap.h" +#include "llmath.h" +//#include "vmath.h" +#include "v3math.h" +#include "lldbstrings.h" + +class LLNameValue; +typedef void (*TNameValueCallback)(LLNameValue *changed, void **user_data); + +void add_use_callback(char *name, TNameValueCallback ucb, void **user_data); + +typedef enum e_name_value_types +{ + NVT_NULL, + NVT_STRING, + NVT_F32, + NVT_S32, + NVT_VEC3, + NVT_U32, + NVT_CAMERA, // Deprecated, but leaving in case removing this will cause problems + NVT_ASSET, + NVT_U64, + NVT_EOF +} ENameValueType; + +typedef enum e_name_value_class +{ + NVC_NULL, + NVC_READ_ONLY, + NVC_READ_WRITE, + NVC_CALLBACK, + NVC_EOF +} ENameValueClass; + +typedef enum e_name_value_sento +{ + NVS_NULL, + NVS_SIM, + NVS_DATA_SIM, + NVS_SIM_VIEWER, + NVS_DATA_SIM_VIEWER, + NVS_EOF +} ENameValueSendto; + + +// NameValues can always be "printed" into a buffer of this length. +const U32 NAME_VALUE_BUF_SIZE = 1024; + + +const U32 NAME_VALUE_TYPE_STRING_LENGTH = 8; +const U32 NAME_VALUE_CLASS_STRING_LENGTH = 16; +const U32 NAME_VALUE_SENDTO_STRING_LENGTH = 18; +const U32 NAME_VALUE_DATA_SIZE = + NAME_VALUE_BUF_SIZE - + ( DB_NV_NAME_BUF_SIZE + + NAME_VALUE_TYPE_STRING_LENGTH + + NAME_VALUE_CLASS_STRING_LENGTH + + NAME_VALUE_SENDTO_STRING_LENGTH ); + + +extern char NameValueTypeStrings[NVT_EOF][NAME_VALUE_TYPE_STRING_LENGTH]; /* Flawfinder: Ignore */ +extern char NameValueClassStrings[NVC_EOF][NAME_VALUE_CLASS_STRING_LENGTH]; /* Flawfinder: Ignore */ +extern char NameValueSendtoStrings[NVS_EOF][NAME_VALUE_SENDTO_STRING_LENGTH]; /* Flawfinder: Ignore */ + +typedef union u_name_value_reference +{ + char *string; + F32 *f32; + S32 *s32; + LLVector3 *vec3; + U32 *u32; + U64 *u64; +} UNameValueReference; + + +class LLNameValue +{ +public: + void baseInit(); + void init(const char *name, const char *data, const char *type, const char *nvclass, const char *nvsendto, + TNameValueCallback nvcb = NULL, void **user_data = NULL); + + LLNameValue(); + LLNameValue(const char *data); + LLNameValue(const char *name, const char *type, const char *nvclass, + TNameValueCallback nvcb = NULL, void **user_data = NULL); + LLNameValue(const char *name, const char *data, const char *type, const char *nvclass, + TNameValueCallback nvcb = NULL, void **user_data = NULL); + LLNameValue(const char *name, const char *data, const char *type, const char *nvclass, const char *nvsendto, + TNameValueCallback nvcb = NULL, void **user_data = NULL); + + ~LLNameValue(); + + char *getString(); + const char *getAsset() const; + F32 *getF32(); + S32 *getS32(); + void getVec3(LLVector3 &vec); + LLVector3 *getVec3(); + F32 magnitude(); + U32 *getU32(); + U64 *getU64(); + + const char *getType() const { return mStringType; } + const char *getClass() const { return mStringClass; } + const char *getSendto() const { return mStringSendto; } + + BOOL sendToData() const; + BOOL sendToViewer() const; + + void callCallback(); + std::string printNameValue(); + std::string printData(); + + ENameValueType getTypeEnum() const { return mType; } + ENameValueClass getClassEnum() const { return mClass; } + ENameValueSendto getSendtoEnum() const { return mSendto; } + + LLNameValue &operator=(const LLNameValue &a); + void setString(const char *a); + void setAsset(const char *a); + void setF32(const F32 a); + void setS32(const S32 a); + void setVec3(const LLVector3 &a); + void setU32(const U32 a); + + BOOL nonzero(); + + friend std::ostream& operator<<(std::ostream& s, const LLNameValue &a); + + friend LLNameValue &operator+(const LLNameValue &a, const LLNameValue &b); + friend LLNameValue &operator-(const LLNameValue &a, const LLNameValue &b); + friend LLNameValue &operator*(const LLNameValue &a, const LLNameValue &b); + friend LLNameValue &operator/(const LLNameValue &a, const LLNameValue &b); + friend LLNameValue &operator%(const LLNameValue &a, const LLNameValue &b); + friend LLNameValue &operator*(const LLNameValue &a, F32 k); + friend LLNameValue &operator*(F32 k, const LLNameValue &a); + + friend bool operator==(const LLNameValue &a, const LLNameValue &b); + friend bool operator<=(const LLNameValue &a, const LLNameValue &b); + friend bool operator>=(const LLNameValue &a, const LLNameValue &b); + friend bool operator<(const LLNameValue &a, const LLNameValue &b); + friend bool operator>(const LLNameValue &a, const LLNameValue &b); + friend bool operator!=(const LLNameValue &a, const LLNameValue &b); + + friend LLNameValue &operator-(const LLNameValue &a); + +private: + void printNameValue(std::ostream& s); + +public: + char *mName; + + char *mStringType; + ENameValueType mType; + char *mStringClass; + ENameValueClass mClass; + char *mStringSendto; + ENameValueSendto mSendto; + + UNameValueReference mNameValueReference; + S32 mNumberEntries; + LLStringTable *mNVNameTable; + TNameValueCallback mNameValueCB; + void **mUserData; +}; + +extern LLStringTable gNVNameTable; + + +#endif diff --git a/indra/llmessage/llnullcipher.cpp b/indra/llmessage/llnullcipher.cpp new file mode 100644 index 0000000000..53bb748415 --- /dev/null +++ b/indra/llmessage/llnullcipher.cpp @@ -0,0 +1,40 @@ +/** + * @file llnullcipher.cpp + * @brief Implementation of a cipher which does not encrypt. + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llcrypto.h" + +///---------------------------------------------------------------------------- +/// Class LLNullCipher +///---------------------------------------------------------------------------- + +BOOL LLNullCipher::encrypt(const U8* src, U32 src_len, U8* dst, U32 dst_len) +{ + if((src_len == dst_len) && src && dst) + { + memmove(dst, src, src_len); + return TRUE; + } + return FALSE; +} + +BOOL LLNullCipher::decrypt(const U8* src, U32 src_len, U8* dst, U32 dst_len) +{ + if((src_len == dst_len) && src && dst) + { + memmove(dst, src, src_len); + return TRUE; + } + return FALSE; +} + +U32 LLNullCipher::requiredEncryptionSpace(U32 len) +{ + return len; +} diff --git a/indra/llmessage/llpacketack.h b/indra/llmessage/llpacketack.h new file mode 100644 index 0000000000..1b62dc9415 --- /dev/null +++ b/indra/llmessage/llpacketack.h @@ -0,0 +1,143 @@ +/** + * @file llpacketack.h + * @brief Reliable UDP helpers for the message system. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLPACKETACK_H +#define LL_LLPACKETACK_H + +#include +#include + +#include "llerror.h" +#include "lltimer.h" +#include "llhost.h" + +//class LLPacketAck +//{ +//public: +// LLHost mHost; +// TPACKETID mPacketID; +//public: +// LLPacketAck(const LLHost &host, TPACKETID packet_id) +// { +// mHost = host; +// mPacketID = packet_id; +// }; +// ~LLPacketAck(){}; +//}; + +class LLReliablePacketParams +{ +public: + LLHost mHost; + S32 mRetries; + BOOL mPingBasedRetry; + F32 mTimeout; + void (*mCallback)(void **,S32); + void **mCallbackData; + char *mMessageName; + +public: + LLReliablePacketParams() + { + mRetries = 0; + mPingBasedRetry = TRUE; + mTimeout = 0.f; + mCallback = NULL; + mCallbackData = NULL; + mMessageName = NULL; + }; + + ~LLReliablePacketParams() { }; + + void set ( const LLHost &host, S32 retries, BOOL ping_based_retry, + F32 timeout, + void (*callback)(void **,S32), void **callback_data, char *name ) + { + mHost = host; + mRetries = retries; + mPingBasedRetry = ping_based_retry; + mTimeout = timeout; + mCallback = callback; + mCallbackData = callback_data; + mMessageName = name; + }; +}; + +class LLReliablePacket +{ +public: + LLReliablePacket(S32 socket, U8 *buf_ptr, S32 buf_len, LLReliablePacketParams *params) : + mBuffer(NULL), + mBufferLength(0) + { + if (params) + { + mHost = params->mHost; + mRetries = params->mRetries; + mPingBasedRetry = params->mPingBasedRetry; + mTimeout = params->mTimeout; + mCallback = params->mCallback; + mCallbackData = params->mCallbackData; + mMessageName = params->mMessageName; + } + else + { + mRetries = 0; + mPingBasedRetry = TRUE; + mTimeout = 0.f; + mCallback = NULL; + mCallbackData = NULL; + mMessageName = NULL; + } + + mExpirationTime = (F64)((S64)totalTime())/1000000.0 + mTimeout; + mPacketID = buf_ptr[1] + ((buf_ptr[0] & 0x0f ) * 256); + if (sizeof(TPACKETID) == 4) + { + mPacketID *= 256; + mPacketID += buf_ptr[2]; + mPacketID *= 256; + mPacketID += buf_ptr[3]; + } + + mSocket = socket; + if (mRetries) + { + mBuffer = new U8[buf_len]; + if (mBuffer != NULL) + { + memcpy(mBuffer,buf_ptr,buf_len); + mBufferLength = buf_len; + } + + } + }; + ~LLReliablePacket(){ delete [] mBuffer; }; + + friend class LLCircuitData; +protected: + S32 mSocket; + LLHost mHost; + S32 mRetries; + BOOL mPingBasedRetry; + F32 mTimeout; + void (*mCallback)(void **,S32); + void **mCallbackData; + char *mMessageName; + + U8 *mBuffer; + S32 mBufferLength; + + TPACKETID mPacketID; + + F64 mExpirationTime; + +}; + +#endif + diff --git a/indra/llmessage/llpacketbuffer.cpp b/indra/llmessage/llpacketbuffer.cpp new file mode 100644 index 0000000000..95c2217a69 --- /dev/null +++ b/indra/llmessage/llpacketbuffer.cpp @@ -0,0 +1,75 @@ +/** + * @file llpacketbuffer.cpp + * @brief implementation of LLPacketBuffer class for a packet. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llpacketbuffer.h" + +#include "net.h" +#include "timing.h" +#include "llhost.h" + +/////////////////////////////////////////////////////////// + +LLPacketBuffer::LLPacketBuffer(const LLHost &host, const char *datap, const S32 size) : mHost(host) +{ + if (size > NET_BUFFER_SIZE) + { + llerrs << "Sending packet > " << NET_BUFFER_SIZE << " of size " << size << llendl; + } + + if (datap != NULL) + { + memcpy(mData, datap, size); + mSize = size; + } + +} + +LLPacketBuffer::LLPacketBuffer (S32 hSocket) +{ + init(hSocket); +} + +/////////////////////////////////////////////////////////// + +LLPacketBuffer::~LLPacketBuffer () +{ + free(); +} + +/////////////////////////////////////////////////////////// + +void LLPacketBuffer::init (S32 hSocket) +{ + mSize = receive_packet(hSocket, mData); + mHost = ::get_sender(); +} + +/////////////////////////////////////////////////////////// + +void LLPacketBuffer::free () +{ +} + + + + + + + + + + + + + + + + + diff --git a/indra/llmessage/llpacketbuffer.h b/indra/llmessage/llpacketbuffer.h new file mode 100644 index 0000000000..13841aaa08 --- /dev/null +++ b/indra/llmessage/llpacketbuffer.h @@ -0,0 +1,37 @@ +/** + * @file llpacketbuffer.h + * @brief definition of LLPacketBuffer class for implementing a + * resend, drop, or delay in packet transmissions. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLPACKETBUFFER_H +#define LL_LLPACKETBUFFER_H + +#include "net.h" // for NET_BUFFER_SIZE +#include "llhost.h" + +class LLPacketBuffer +{ +public: + LLPacketBuffer(const LLHost &host, const char *datap, const S32 size); + LLPacketBuffer(S32 hSocket); // receive a packet + ~LLPacketBuffer(); + + S32 getSize() const { return mSize; } + const char *getData() const { return mData; } + LLHost getHost() const { return mHost; } + void init(S32 hSocket); + void free(); + +protected: + char mData[NET_BUFFER_SIZE]; // packet data /* Flawfinder : ignore */ + S32 mSize; // size of buffer in bytes + LLHost mHost; // source/dest IP and port +}; + +#endif + + diff --git a/indra/llmessage/llpacketring.cpp b/indra/llmessage/llpacketring.cpp new file mode 100644 index 0000000000..4f17d1ae5a --- /dev/null +++ b/indra/llmessage/llpacketring.cpp @@ -0,0 +1,293 @@ +/** + * @file llpacketring.cpp + * @brief implementation of LLPacketRing class for a packet. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llpacketring.h" + +// linden library includes +#include "llerror.h" +#include "lltimer.h" +#include "timing.h" +#include "llrand.h" +#include "u64.h" + +/////////////////////////////////////////////////////////// +LLPacketRing::LLPacketRing () : + mUseInThrottle(FALSE), + mUseOutThrottle(FALSE), + mInThrottle(256000.f), + mOutThrottle(64000.f), + mActualBitsIn(0), + mActualBitsOut(0), + mMaxBufferLength(64000), + mInBufferLength(0), + mOutBufferLength(0), + mDropPercentage(0.0f), + mPacketsToDrop(0x0) +{ +} + +/////////////////////////////////////////////////////////// +LLPacketRing::~LLPacketRing () +{ + free(); +} + +/////////////////////////////////////////////////////////// +void LLPacketRing::free () +{ + LLPacketBuffer *packetp; + + while (!mReceiveQueue.empty()) + { + packetp = mReceiveQueue.front(); + delete packetp; + mReceiveQueue.pop(); + } + + while (!mSendQueue.empty()) + { + packetp = mSendQueue.front(); + delete packetp; + mSendQueue.pop(); + } +} + +/////////////////////////////////////////////////////////// +void LLPacketRing::dropPackets (U32 num_to_drop) +{ + mPacketsToDrop += num_to_drop; +} + +/////////////////////////////////////////////////////////// +void LLPacketRing::setDropPercentage (F32 percent_to_drop) +{ + mDropPercentage = percent_to_drop; +} + +void LLPacketRing::setUseInThrottle(const BOOL use_throttle) +{ + mUseInThrottle = use_throttle; +} + +void LLPacketRing::setUseOutThrottle(const BOOL use_throttle) +{ + mUseOutThrottle = use_throttle; +} + +void LLPacketRing::setInBandwidth(const F32 bps) +{ + mInThrottle.setRate(bps); +} + +void LLPacketRing::setOutBandwidth(const F32 bps) +{ + mOutThrottle.setRate(bps); +} +/////////////////////////////////////////////////////////// +S32 LLPacketRing::receiveFromRing (S32 socket, char *datap) +{ + + if (mInThrottle.checkOverflow(0)) + { + // We don't have enough bandwidth, don't give them a packet. + return 0; + } + + LLPacketBuffer *packetp = NULL; + if (mReceiveQueue.empty()) + { + // No packets on the queue, don't give them any. + return 0; + } + + S32 packet_size = 0; + packetp = mReceiveQueue.front(); + mReceiveQueue.pop(); + packet_size = packetp->getSize(); + if (packetp->getData() != NULL) + { + memcpy(datap, packetp->getData(), packet_size); + } + // need to set sender IP/port!! + mLastSender = packetp->getHost(); + delete packetp; + + this->mInBufferLength -= packet_size; + + // Adjust the throttle + mInThrottle.throttleOverflow(packet_size * 8.f); + return packet_size; +} + +/////////////////////////////////////////////////////////// +S32 LLPacketRing::receivePacket (S32 socket, char *datap) +{ + S32 packet_size = 0; + + // If using the throttle, simulate a limited size input buffer. + if (mUseInThrottle) + { + BOOL done = FALSE; + + // push any current net packet (if any) onto delay ring + while (!done) + { + LLPacketBuffer *packetp; + packetp = new LLPacketBuffer(socket); + + if (packetp->getSize()) + { + mActualBitsIn += packetp->getSize() * 8; + + // Fake packet loss + if (mDropPercentage && (frand(100.f) < mDropPercentage)) + { + mPacketsToDrop++; + } + + if (mPacketsToDrop) + { + delete packetp; + packetp = NULL; + packet_size = 0; + mPacketsToDrop--; + } + } + + // If we faked packet loss, then we don't have a packet + // to use for buffer overflow testing + if (packetp) + { + if (mInBufferLength + packetp->getSize() > mMaxBufferLength) + { + // Toss it. + llwarns << "Throwing away packet, overflowing buffer" << llendl; + delete packetp; + packetp = NULL; + } + else if (packetp->getSize()) + { + mReceiveQueue.push(packetp); + mInBufferLength += packetp->getSize(); + } + else + { + delete packetp; + packetp = NULL; + done = true; + } + } + else + { + // No packetp, keep going? - no packetp == faked packet loss + } + } + + // Now, grab data off of the receive queue according to our + // throttled bandwidth settings. + packet_size = receiveFromRing(socket, datap); + } + else + { + // no delay, pull straight from net + packet_size = receive_packet(socket, datap); + mLastSender = ::get_sender(); + + if (packet_size) // did we actually get a packet? + { + if (mDropPercentage && (frand(100.f) < mDropPercentage)) + { + mPacketsToDrop++; + } + + if (mPacketsToDrop) + { + packet_size = 0; + mPacketsToDrop--; + } + } + } + + return packet_size; +} + +BOOL LLPacketRing::sendPacket(int h_socket, char * send_buffer, S32 buf_size, LLHost host) +{ + BOOL status = TRUE; + if (!mUseOutThrottle) + { + return send_packet(h_socket, send_buffer, buf_size, host.getAddress(), host.getPort() ); + } + else + { + mActualBitsOut += buf_size * 8; + LLPacketBuffer *packetp = NULL; + // See if we've got enough throttle to send a packet. + while (!mOutThrottle.checkOverflow(0.f)) + { + // While we have enough bandwidth, send a packet from the queue or the current packet + + S32 packet_size = 0; + if (!mSendQueue.empty()) + { + // Send a packet off of the queue + LLPacketBuffer *packetp = mSendQueue.front(); + mSendQueue.pop(); + + mOutBufferLength -= packetp->getSize(); + packet_size = packetp->getSize(); + + status = send_packet(h_socket, packetp->getData(), packet_size, packetp->getHost().getAddress(), packetp->getHost().getPort()); + + delete packetp; + // Update the throttle + mOutThrottle.throttleOverflow(packet_size * 8.f); + } + else + { + // If the queue's empty, we can just send this packet right away. + status = send_packet(h_socket, send_buffer, buf_size, host.getAddress(), host.getPort() ); + packet_size = buf_size; + + // Update the throttle + mOutThrottle.throttleOverflow(packet_size * 8.f); + + // This was the packet we're sending now, there are no other packets + // that we need to send + return status; + } + + } + + // We haven't sent the incoming packet, add it to the queue + if (mOutBufferLength + buf_size > mMaxBufferLength) + { + // Nuke this packet, we overflowed the buffer. + // Toss it. + llwarns << "Throwing away outbound packet, overflowing buffer" << llendl; + } + else + { + static LLTimer queue_timer; + if ((mOutBufferLength > 4192) && queue_timer.getElapsedTimeF32() > 1.f) + { + // Add it to the queue + llinfos << "Outbound packet queue " << mOutBufferLength << " bytes" << llendl; + queue_timer.reset(); + } + packetp = new LLPacketBuffer(host, send_buffer, buf_size); + + mOutBufferLength += packetp->getSize(); + mSendQueue.push(packetp); + } + } + + return status; +} diff --git a/indra/llmessage/llpacketring.h b/indra/llmessage/llpacketring.h new file mode 100644 index 0000000000..0327586d78 --- /dev/null +++ b/indra/llmessage/llpacketring.h @@ -0,0 +1,74 @@ +/** + * @file llpacketring.h + * @brief definition of LLPacketRing class for implementing a resend, + * drop, or delay in packet transmissions + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLPACKETRING_H +#define LL_LLPACKETRING_H + +#include + +#include "llpacketbuffer.h" +#include "linked_lists.h" +#include "llhost.h" +#include "net.h" +#include "llthrottle.h" + + +class LLPacketRing +{ +public: + LLPacketRing(); + ~LLPacketRing(); + + void free(); + + void dropPackets(U32); + void setDropPercentage (F32 percent_to_drop); + void setUseInThrottle(const BOOL use_throttle); + void setUseOutThrottle(const BOOL use_throttle); + void setInBandwidth(const F32 bps); + void setOutBandwidth(const F32 bps); + S32 receivePacket (S32 socket, char *datap); + S32 receiveFromRing (S32 socket, char *datap); + + BOOL sendPacket(int h_socket, char * send_buffer, S32 buf_size, LLHost host); + + inline LLHost getLastSender(); + + S32 getAndResetActualInBits() { S32 bits = mActualBitsIn; mActualBitsIn = 0; return bits;} + S32 getAndResetActualOutBits() { S32 bits = mActualBitsOut; mActualBitsOut = 0; return bits;} +protected: + BOOL mUseInThrottle; + BOOL mUseOutThrottle; + + // For simulating a lower-bandwidth connection - BPS + LLThrottle mInThrottle; + LLThrottle mOutThrottle; + + S32 mActualBitsIn; + S32 mActualBitsOut; + S32 mMaxBufferLength; // How much data can we queue up before dropping data. + S32 mInBufferLength; // Current incoming buffer length + S32 mOutBufferLength; // Current outgoing buffer length + + F32 mDropPercentage; // % of packets to drop + U32 mPacketsToDrop; // drop next n packets + + std::queue mReceiveQueue; + std::queue mSendQueue; + + LLHost mLastSender; +}; + + +inline LLHost LLPacketRing::getLastSender() +{ + return mLastSender; +} + +#endif diff --git a/indra/llmessage/llpartdata.cpp b/indra/llmessage/llpartdata.cpp new file mode 100644 index 0000000000..4ce7bc1fcd --- /dev/null +++ b/indra/llmessage/llpartdata.cpp @@ -0,0 +1,307 @@ +/** + * @file llpartdata.cpp + * @brief Particle system data packing + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llpartdata.h" +#include "message.h" + +#include "lldatapacker.h" +#include "v4coloru.h" + +#include "llsdutil.h" + + +const S32 PS_PART_DATA_BLOCK_SIZE = 4 + 2 + 4 + 4 + 2 + 2; // 18 +const S32 PS_DATA_BLOCK_SIZE = 68 + PS_PART_DATA_BLOCK_SIZE; // 68 + 18 = 86 + + +const F32 MAX_PART_SCALE = 4.f; + +BOOL LLPartData::pack(LLDataPacker &dp) +{ + LLColor4U coloru; + dp.packU32(mFlags, "pdflags"); + dp.packFixed(mMaxAge, "pdmaxage", FALSE, 8, 8); + coloru.setVec(mStartColor); + dp.packColor4U(coloru, "pdstartcolor"); + coloru.setVec(mEndColor); + dp.packColor4U(coloru, "pdendcolor"); + dp.packFixed(mStartScale.mV[0], "pdstartscalex", FALSE, 3, 5); + dp.packFixed(mStartScale.mV[1], "pdstartscaley", FALSE, 3, 5); + dp.packFixed(mEndScale.mV[0], "pdendscalex", FALSE, 3, 5); + dp.packFixed(mEndScale.mV[1], "pdendscaley", FALSE, 3, 5); + return TRUE; +} + +LLSD LLPartData::asLLSD() const +{ + LLSD sd = LLSD(); + sd["pdflags"] = ll_sd_from_U32(mFlags); + sd["pdmaxage"] = mMaxAge; + sd["pdstartcolor"] = ll_sd_from_color4(mStartColor); + sd["pdendcolor"] = ll_sd_from_color4(mEndColor); + sd["pdstartscale"] = ll_sd_from_vector2(mStartScale); + sd["pdendscale"] = ll_sd_from_vector2(mEndScale); + return sd; +} + +bool LLPartData::fromLLSD(LLSD& sd) +{ + mFlags = ll_U32_from_sd(sd["pdflags"]); + mMaxAge = (F32)sd["pdmaxage"].asReal(); + mStartColor = ll_color4_from_sd(sd["pdstartcolor"]); + mEndColor = ll_color4_from_sd(sd["pdendcolor"]); + mStartScale = ll_vector2_from_sd(sd["pdstartscale"]); + mEndScale = ll_vector2_from_sd(sd["pdendscale"]); + return true; +} + + +BOOL LLPartData::unpack(LLDataPacker &dp) +{ + LLColor4U coloru; + + dp.unpackU32(mFlags, "pdflags"); + dp.unpackFixed(mMaxAge, "pdmaxage", FALSE, 8, 8); + + dp.unpackColor4U(coloru, "pdstartcolor"); + mStartColor.setVec(coloru); + dp.unpackColor4U(coloru, "pdendcolor"); + mEndColor.setVec(coloru); + dp.unpackFixed(mStartScale.mV[0], "pdstartscalex", FALSE, 3, 5); + dp.unpackFixed(mStartScale.mV[1], "pdstartscaley", FALSE, 3, 5); + dp.unpackFixed(mEndScale.mV[0], "pdendscalex", FALSE, 3, 5); + dp.unpackFixed(mEndScale.mV[1], "pdendscaley", FALSE, 3, 5); + return TRUE; +} + + +void LLPartData::setFlags(const U32 flags) +{ + mFlags = flags; +} + + +void LLPartData::setMaxAge(const F32 max_age) +{ + mMaxAge = llclamp(max_age, 0.f, 30.f); +} + + +void LLPartData::setStartScale(const F32 xs, const F32 ys) +{ + mStartScale.mV[VX] = llmin(xs, MAX_PART_SCALE); + mStartScale.mV[VY] = llmin(ys, MAX_PART_SCALE); +} + + +void LLPartData::setEndScale(const F32 xs, const F32 ys) +{ + mEndScale.mV[VX] = llmin(xs, MAX_PART_SCALE); + mEndScale.mV[VY] = llmin(ys, MAX_PART_SCALE); +} + + +void LLPartData::setStartColor(const LLVector3 &rgb) +{ + mStartColor.setVec(rgb.mV[0], rgb.mV[1], rgb.mV[2]); +} + + +void LLPartData::setEndColor(const LLVector3 &rgb) +{ + mEndColor.setVec(rgb.mV[0], rgb.mV[1], rgb.mV[2]); +} + +void LLPartData::setStartAlpha(const F32 alpha) +{ + mStartColor.mV[3] = alpha; +} +void LLPartData::setEndAlpha(const F32 alpha) +{ + mEndColor.mV[3] = alpha; +} + + +LLPartSysData::LLPartSysData() +{ + mCRC = 0; + mPartData.mFlags = 0; + mPartData.mStartColor = LLColor4(1.f, 1.f, 1.f, 1.f); + mPartData.mEndColor = LLColor4(1.f, 1.f, 1.f, 1.f); + mPartData.mStartScale = LLVector2(1.f, 1.f); + mPartData.mEndScale = LLVector2(1.f, 1.f); + mPartData.mMaxAge = 10.0; + + mMaxAge = 0.0; + mStartAge = 0.0; + mPattern = LL_PART_SRC_PATTERN_DROP; // Pattern for particle velocity + mInnerAngle = 0.0; // Inner angle of PATTERN_ANGLE_* + mOuterAngle = 0.0; // Outer angle of PATTERN_ANGLE_* + mBurstRate = 0.1f; // How often to do a burst of particles + mBurstPartCount = 1; // How many particles in a burst + mBurstSpeedMin = 1.f; // Minimum particle velocity + mBurstSpeedMax = 1.f; // Maximum particle velocity + mBurstRadius = 0.f; +} + + +BOOL LLPartSysData::pack(LLDataPacker &dp) +{ + dp.packU32(mCRC, "pscrc"); + dp.packU32(mFlags, "psflags"); + dp.packU8(mPattern, "pspattern"); + dp.packFixed(mMaxAge, "psmaxage", FALSE, 8, 8); + dp.packFixed(mStartAge, "psstartage", FALSE, 8, 8); + dp.packFixed(mInnerAngle, "psinnerangle", FALSE, 3, 5); + dp.packFixed(mOuterAngle, "psouterangle", FALSE, 3, 5); + dp.packFixed(mBurstRate, "psburstrate", FALSE, 8, 8); + dp.packFixed(mBurstRadius, "psburstradius", FALSE, 8, 8); + dp.packFixed(mBurstSpeedMin, "psburstspeedmin", FALSE, 8, 8); + dp.packFixed(mBurstSpeedMax, "psburstspeedmax", FALSE, 8, 8); + dp.packU8(mBurstPartCount, "psburstpartcount"); + + dp.packFixed(mAngularVelocity.mV[0], "psangvelx", TRUE, 8, 7); + dp.packFixed(mAngularVelocity.mV[1], "psangvely", TRUE, 8, 7); + dp.packFixed(mAngularVelocity.mV[2], "psangvelz", TRUE, 8, 7); + + dp.packFixed(mPartAccel.mV[0], "psaccelx", TRUE, 8, 7); + dp.packFixed(mPartAccel.mV[1], "psaccely", TRUE, 8, 7); + dp.packFixed(mPartAccel.mV[2], "psaccelz", TRUE, 8, 7); + + dp.packUUID(mPartImageID, "psuuid"); + dp.packUUID(mTargetUUID, "pstargetuuid"); + mPartData.pack(dp); + return TRUE; +} + + +BOOL LLPartSysData::unpack(LLDataPacker &dp) +{ + dp.unpackU32(mCRC, "pscrc"); + dp.unpackU32(mFlags, "psflags"); + dp.unpackU8(mPattern, "pspattern"); + dp.unpackFixed(mMaxAge, "psmaxage", FALSE, 8, 8); + dp.unpackFixed(mStartAge, "psstartage", FALSE, 8, 8); + dp.unpackFixed(mInnerAngle, "psinnerangle", FALSE, 3, 5); + dp.unpackFixed(mOuterAngle, "psouterangle", FALSE, 3, 5); + dp.unpackFixed(mBurstRate, "psburstrate", FALSE, 8, 8); + mBurstRate = llmax(0.01f, mBurstRate); + dp.unpackFixed(mBurstRadius, "psburstradius", FALSE, 8, 8); + dp.unpackFixed(mBurstSpeedMin, "psburstspeedmin", FALSE, 8, 8); + dp.unpackFixed(mBurstSpeedMax, "psburstspeedmax", FALSE, 8, 8); + dp.unpackU8(mBurstPartCount, "psburstpartcount"); + + dp.unpackFixed(mAngularVelocity.mV[0], "psangvelx", TRUE, 8, 7); + dp.unpackFixed(mAngularVelocity.mV[1], "psangvely", TRUE, 8, 7); + dp.unpackFixed(mAngularVelocity.mV[2], "psangvelz", TRUE, 8, 7); + + dp.unpackFixed(mPartAccel.mV[0], "psaccelx", TRUE, 8, 7); + dp.unpackFixed(mPartAccel.mV[1], "psaccely", TRUE, 8, 7); + dp.unpackFixed(mPartAccel.mV[2], "psaccelz", TRUE, 8, 7); + + dp.unpackUUID(mPartImageID, "psuuid"); + dp.unpackUUID(mTargetUUID, "pstargetuuid"); + mPartData.unpack(dp); + return TRUE; +} + + +BOOL LLPartSysData::isNullPS(const S32 block_num) +{ + U8 ps_data_block[PS_DATA_BLOCK_SIZE]; + U32 crc; + + S32 size; + // Check size of block + size = gMessageSystem->getSize("ObjectData", block_num, "PSBlock"); + + if (!size) + { + return TRUE; + } + else if (size != PS_DATA_BLOCK_SIZE) + { + llwarns << "PSBlock is wrong size for particle system data - got " << size << ", expecting " << PS_DATA_BLOCK_SIZE << llendl; + return TRUE; + } + gMessageSystem->getBinaryData("ObjectData", "PSBlock", ps_data_block, PS_DATA_BLOCK_SIZE, block_num, PS_DATA_BLOCK_SIZE); + + LLDataPackerBinaryBuffer dp(ps_data_block, PS_DATA_BLOCK_SIZE); + dp.unpackU32(crc, "crc"); + + if (crc == 0) + { + return TRUE; + } + return FALSE; +} + + +//static +BOOL LLPartSysData::packNull() +{ + U8 ps_data_block[PS_DATA_BLOCK_SIZE]; + gMessageSystem->addBinaryData("PSBlock", ps_data_block, 0); + return TRUE; +} + + +BOOL LLPartSysData::packBlock() +{ + U8 ps_data_block[PS_DATA_BLOCK_SIZE]; + + LLDataPackerBinaryBuffer dp(ps_data_block, PS_DATA_BLOCK_SIZE); + pack(dp); + + // Add to message + gMessageSystem->addBinaryData("PSBlock", ps_data_block, PS_DATA_BLOCK_SIZE); + + return TRUE; +} + + +BOOL LLPartSysData::unpackBlock(const S32 block_num) +{ + U8 ps_data_block[PS_DATA_BLOCK_SIZE]; + + // Check size of block + S32 size = gMessageSystem->getSize("ObjectData", block_num, "PSBlock"); + + if (size != PS_DATA_BLOCK_SIZE) + { + llwarns << "PSBlock is wrong size for particle system data - got " << size << ", expecting " << PS_DATA_BLOCK_SIZE << llendl; + return FALSE; + } + + // Get from message + gMessageSystem->getBinaryData("ObjectData", "PSBlock", ps_data_block, PS_DATA_BLOCK_SIZE, block_num, PS_DATA_BLOCK_SIZE); + + LLDataPackerBinaryBuffer dp(ps_data_block, PS_DATA_BLOCK_SIZE); + unpack(dp); + + return TRUE; +} + +void LLPartSysData::clampSourceParticleRate() +{ + F32 particle_rate = 0; + particle_rate = mBurstPartCount/mBurstRate; + if (particle_rate > 256.f) + { + mBurstPartCount = llfloor(((F32)mBurstPartCount)*(256.f/particle_rate)); + } +} + +void LLPartSysData::setPartAccel(const LLVector3 &accel) +{ + mPartAccel.mV[VX] = llclamp(accel.mV[VX], -100.f, 100.f); + mPartAccel.mV[VY] = llclamp(accel.mV[VY], -100.f, 100.f); + mPartAccel.mV[VZ] = llclamp(accel.mV[VZ], -100.f, 100.f); +} diff --git a/indra/llmessage/llpartdata.h b/indra/llmessage/llpartdata.h new file mode 100644 index 0000000000..ba3bdaf9b6 --- /dev/null +++ b/indra/llmessage/llpartdata.h @@ -0,0 +1,217 @@ +/** + * @file llpartdata.h + * @brief Particle system data packing + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLPARTDATA_H +#define LL_LLPARTDATA_H + +#include + +#include "lluuid.h" +#include "v3math.h" +#include "v3dmath.h" +#include "v2math.h" +#include "v4color.h" + +class LLMessageSystem; +class LLDataPacker; + +const S32 PS_CUR_VERSION = 18; + +// +// These constants are used by the script code, not by the particle system itself +// + +enum LLPSScriptFlags +{ + // Flags for the different parameters of individual particles + LLPS_PART_FLAGS, + LLPS_PART_START_COLOR, + LLPS_PART_START_ALPHA, + LLPS_PART_END_COLOR, + LLPS_PART_END_ALPHA, + LLPS_PART_START_SCALE, + LLPS_PART_END_SCALE, + LLPS_PART_MAX_AGE, + + // Flags for the different parameters of the particle source + LLPS_SRC_ACCEL, + LLPS_SRC_PATTERN, + LLPS_SRC_INNERANGLE, + LLPS_SRC_OUTERANGLE, + LLPS_SRC_TEXTURE, + LLPS_SRC_BURST_RATE, + LLPS_SRC_BURST_DURATION, + LLPS_SRC_BURST_PART_COUNT, + LLPS_SRC_BURST_RADIUS, + LLPS_SRC_BURST_SPEED_MIN, + LLPS_SRC_BURST_SPEED_MAX, + LLPS_SRC_MAX_AGE, + LLPS_SRC_TARGET_UUID, + LLPS_SRC_OMEGA, + LLPS_SRC_ANGLE_BEGIN, + LLPS_SRC_ANGLE_END +}; + + +class LLPartData +{ +public: + LLPartData() : + mFlags(0), + mMaxAge(0) + { + } + BOOL unpack(LLDataPacker &dp); + BOOL pack(LLDataPacker &dp); + LLSD asLLSD() const; + operator LLSD() const {return asLLSD(); } + bool fromLLSD(LLSD& sd); + + // Masks for the different particle flags + enum + { + LL_PART_INTERP_COLOR_MASK = 0x01, + LL_PART_INTERP_SCALE_MASK = 0x02, + LL_PART_BOUNCE_MASK = 0x04, + LL_PART_WIND_MASK = 0x08, + LL_PART_FOLLOW_SRC_MASK = 0x10, // Follows source, no rotation following (expensive!) + LL_PART_FOLLOW_VELOCITY_MASK = 0x20, // Particles orient themselves with velocity + LL_PART_TARGET_POS_MASK = 0x40, + LL_PART_TARGET_LINEAR_MASK = 0x80, // Particle uses a direct linear interpolation + LL_PART_EMISSIVE_MASK = 0x100, // Particle is "emissive", instead of being lit + LL_PART_BEAM_MASK = 0x200, // Particle is a "beam" connecting source and target + + // Not implemented yet! + //LL_PART_RANDOM_ACCEL_MASK = 0x100, // Patricles have random accelearation + //LL_PART_RANDOM_VEL_MASK = 0x200, // Particles have random velocity shifts" + //LL_PART_TRAIL_MASK = 0x400, // Particles have historical "trails" + + // Viewer side use only! + LL_PART_DEAD_MASK = 0x80000000, + }; + + void setFlags(const U32 flags); + void setMaxAge(const F32 max_age); + void setStartScale(const F32 xs, F32 ys); + void setEndScale(const F32 xs, F32 ys); + void setStartColor(const LLVector3 &rgb); + void setEndColor(const LLVector3 &rgb); + void setStartAlpha(const F32 alpha); + void setEndAlpha(const F32 alpha); + + + friend class LLPartSysData; + friend class LLViewerPartSourceScript; + + // These are public because I'm really lazy... +public: + U32 mFlags; // Particle state/interpolators in effect + F32 mMaxAge; // Maximum age of the particle + LLColor4 mStartColor; // Start color + LLColor4 mEndColor; // End color + LLVector2 mStartScale; // Start scale + LLVector2 mEndScale; // End scale + + LLVector3 mPosOffset; // Offset from source if using FOLLOW_SOURCE + F32 mParameter; // A single floating point parameter +}; + + +class LLPartSysData +{ +public: + LLPartSysData(); + + BOOL unpack(LLDataPacker &dp); + BOOL pack(LLDataPacker &dp); + + + BOOL unpackBlock(const S32 block_num); + BOOL packBlock(); + + static BOOL packNull(); + static BOOL isNullPS(const S32 block_num); // Returns FALSE if this is a "NULL" particle system (i.e. no system) + + // Different masks for effects on the source + enum + { + LL_PART_SRC_OBJ_REL_MASK = 0x01, // Accel and velocity for particles relative object rotation + LL_PART_USE_NEW_ANGLE = 0x02, // Particles uses new 'correct' angle parameters. + }; + + // The different patterns for how particles are created + enum + { + LL_PART_SRC_PATTERN_DROP = 0x01, + LL_PART_SRC_PATTERN_EXPLODE = 0x02, + // Not implemented fully yet + LL_PART_SRC_PATTERN_ANGLE = 0x04, + LL_PART_SRC_PATTERN_ANGLE_CONE = 0x08, + LL_PART_SRC_PATTERN_ANGLE_CONE_EMPTY = 0x10, + }; + + + void setBurstSpeedMin(const F32 spd) { mBurstSpeedMin = llclamp(spd, -100.f, 100.f); } + void setBurstSpeedMax(const F32 spd) { mBurstSpeedMax = llclamp(spd, -100.f, 100.f); } + void setBurstRadius(const F32 rad) { mBurstRadius = llclamp(rad, 0.f, 50.f); } + void setPartAccel(const LLVector3 &accel); + void setUseNewAngle() { mFlags |= LL_PART_USE_NEW_ANGLE; } + void unsetUseNewAngle() { mFlags &= ~LL_PART_USE_NEW_ANGLE; } + + // Since the actual particle creation rate is + // a combination of multiple parameters, we + // need to clamp it using a separate method instead of an accessor. + void clampSourceParticleRate(); +public: + // Public because I'm lazy.... + + // + // There are two kinds of data for the particle system + // 1. Parameters which specify parameters of the source (mSource*) + // 2. Parameters which specify parameters of the particles generated by the source (mPart*) + // + + U32 mCRC; + U32 mFlags; + + U8 mPattern; // Pattern for particle velocity/output + F32 mInnerAngle; // Inner angle for PATTERN_ANGLE + F32 mOuterAngle; // Outer angle for PATTERN_ANGLE + LLVector3 mAngularVelocity; // Angular velocity for emission axis (for PATTERN_ANGLE) + + F32 mBurstRate; // How often to do a burst of particles + U8 mBurstPartCount; // How many particles in a burst + F32 mBurstRadius; + F32 mBurstSpeedMin; // Minimum particle velocity + F32 mBurstSpeedMax; // Maximum particle velocity + + F32 mMaxAge; // Maximum lifetime of this particle source + + LLUUID mTargetUUID; // Target UUID for the particle system + + F32 mStartAge; // Age at which to start the particle system (for an update after the + // particle system has started) + + + // + // These are actually particle properties, but can be mutated by the source, + // so are stored here instead + // + LLVector3 mPartAccel; + LLUUID mPartImageID; + + // + // The "template" partdata where we actually store the non-mutable particle parameters + // + LLPartData mPartData; + +protected: + S32 mNumParticles; // Number of particles generated +}; + +#endif // LL_LLPARTDATA_H diff --git a/indra/llmessage/llpumpio.cpp b/indra/llmessage/llpumpio.cpp new file mode 100644 index 0000000000..0c5bd1eaea --- /dev/null +++ b/indra/llmessage/llpumpio.cpp @@ -0,0 +1,1006 @@ +/** + * @file llpumpio.cpp + * @author Phoenix + * @date 2004-11-21 + * @brief Implementation of the i/o pump and related functions. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llpumpio.h" + +#include +#include "apr-1/apr_poll.h" + +#include "llapr.h" +#include "llmemtype.h" +#include "llstl.h" + +// This should not be in production, but it is intensely useful during +// development. +#if LL_LINUX +#define LL_DEBUG_PIPE_TYPE_IN_PUMP 0 +#endif + +#if LL_DEBUG_PIPE_TYPE_IN_PUMP +#include +#endif + +// constants for poll timeout. if we are threading, we want to have a +// longer poll timeout. +#if LL_THREADS_APR +static const S32 DEFAULT_POLL_TIMEOUT = 1000; +#else +static const S32 DEFAULT_POLL_TIMEOUT = 0; +#endif + +// The default (and fallback) expiration time for chains +const F32 DEFAULT_CHAIN_EXPIRY_SECS = 30.0f; +extern const F32 SHORT_CHAIN_EXPIRY_SECS = 1.0f; +extern const F32 NEVER_CHAIN_EXPIRY_SECS = 0.0f; + +// sorta spammy debug modes. +//#define LL_DEBUG_SPEW_BUFFER_CHANNEL_IN_ON_ERROR 1 +//#define LL_DEBUG_PROCESS_LINK 1 +//#define LL_DEBUG_PROCESS_RETURN_VALUE 1 + +// Super spammy debug mode. +//#define LL_DEBUG_SPEW_BUFFER_CHANNEL_IN 1 +//#define LL_DEBUG_SPEW_BUFFER_CHANNEL_OUT 1 + +/** + * @class + */ +class LLChainSleeper : public LLRunnable +{ +public: + static LLRunner::run_ptr_t build(LLPumpIO* pump, S32 key) + { + return LLRunner::run_ptr_t(new LLChainSleeper(pump, key)); + } + + virtual void run(LLRunner* runner, S64 handle) + { + mPump->clearLock(mKey); + } + +protected: + LLChainSleeper(LLPumpIO* pump, S32 key) : mPump(pump), mKey(key) {} + LLPumpIO* mPump; + S32 mKey; +}; + + +/** + * @struct ll_delete_apr_pollset_fd_client_data + * @brief This is a simple helper class to clean up our client data. + */ +struct ll_delete_apr_pollset_fd_client_data +{ + typedef std::pair pipe_conditional_t; + void operator()(const pipe_conditional_t& conditional) + { + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + S32* client_id = (S32*)conditional.second.client_data; + delete client_id; + } +}; + +/** + * LLPumpIO + */ +LLPumpIO::LLPumpIO(apr_pool_t* pool) : + mState(LLPumpIO::NORMAL), + mRebuildPollset(false), + mPollset(NULL), + mPollsetClientID(0), + mNextLock(0), + mPool(NULL), + mCurrentPool(NULL), + mCurrentPoolReallocCount(0), + mChainsMutex(NULL), + mCallbackMutex(NULL) +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + initialize(pool); +} + +LLPumpIO::~LLPumpIO() +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + cleanup(); +} + +bool LLPumpIO::prime(apr_pool_t* pool) +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + cleanup(); + initialize(pool); + return ((pool == NULL) ? false : true); +} + +bool LLPumpIO::addChain(const chain_t& chain, F32 timeout) +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + if(chain.empty()) return false; + +#if LL_THREADS_APR + LLScopedLock lock(mChainsMutex); +#endif + LLChainInfo info; + info.setTimeoutSeconds(timeout); + info.mData = LLIOPipe::buffer_ptr_t(new LLBufferArray); + LLLinkInfo link; +#if LL_DEBUG_PIPE_TYPE_IN_PUMP + lldebugs << "LLPumpIO::addChain() " << chain[0] << " '" + << typeid(*(chain[0])).name() << "'" << llendl; +#else + lldebugs << "LLPumpIO::addChain() " << chain[0] <nextChannel(); + info.mChainLinks.push_back(link); + } + mPendingChains.push_back(info); + return true; +} + +bool LLPumpIO::addChain( + const LLPumpIO::links_t& links, + LLIOPipe::buffer_ptr_t data, + LLSD context, + F32 timeout) +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + + // remember that if the caller is providing a full link + // description, we need to have that description matched to a + // particular buffer. + if(!data) return false; + if(links.empty()) return false; + +#if LL_THREADS_APR + LLScopedLock lock(mChainsMutex); +#endif +#if LL_DEBUG_PIPE_TYPE_IN_PUMP + lldebugs << "LLPumpIO::addChain() " << links[0].mPipe << " '" + << typeid(*(links[0].mPipe)).name() << "'" << llendl; +#else + lldebugs << "LLPumpIO::addChain() " << links[0].mPipe << llendl; +#endif + LLChainInfo info; + info.setTimeoutSeconds(timeout); + info.mChainLinks = links; + info.mData = data; + info.mContext = context; + mPendingChains.push_back(info); + return true; +} + +bool LLPumpIO::setTimeoutSeconds(F32 timeout) +{ + // If no chain is running, return failure. + if(current_chain_t() == mCurrentChain) + { + return false; + } + (*mCurrentChain).setTimeoutSeconds(timeout); + return true; +} + +bool LLPumpIO::setConditional(LLIOPipe* pipe, const apr_pollfd_t* poll) +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + //lldebugs << "LLPumpIO::setConditional" << llendl; + if(pipe) + { + // remove any matching poll file descriptors for this pipe. + LLIOPipe::ptr_t pipe_ptr(pipe); + + LLChainInfo::conditionals_t::iterator it = (*mCurrentChain).mDescriptors.begin(); + LLChainInfo::conditionals_t::iterator end = (*mCurrentChain).mDescriptors.end(); + while (it != end) + { + LLChainInfo::pipe_conditional_t& value = (*it); + if ( pipe_ptr == value.first ) + { + ll_delete_apr_pollset_fd_client_data()(value); + (*mCurrentChain).mDescriptors.erase(it++); + mRebuildPollset = true; + } + else + { + ++it; + } + } + + if(poll) + { + LLChainInfo::pipe_conditional_t value; + value.first = pipe_ptr; + value.second = *poll; + if(!poll->p) + { + // each fd needs a pool to work with, so if one was + // not specified, use this pool. + // *FIX: Should it always be this pool? + value.second.p = mPool; + } + value.second.client_data = new S32(++mPollsetClientID); + (*mCurrentChain).mDescriptors.push_back(value); + mRebuildPollset = true; + } + return true; + } + return false; +} + +S32 LLPumpIO::setLock() +{ + // *NOTE: I do not think it is necessary to acquire a mutex here + // since this should only be called during the pump(), and should + // only change the running chain. Any other use of this method is + // incorrect usage. If it becomes necessary to acquire a lock + // here, be sure to lock here and call a protected method to get + // the lock, and sleepChain() should probably acquire the same + // lock while and calling the same protected implementation to + // lock the runner at the same time. + + // If no chain is running, return failure. + if(current_chain_t() == mCurrentChain) + { + return 0; + } + + // deal with wrap. + if(++mNextLock <= 0) + { + mNextLock = 1; + } + + // set the lock + (*mCurrentChain).mLock = mNextLock; + return mNextLock; +} + +void LLPumpIO::clearLock(S32 key) +{ + // We need to lock it here since we do not want to be iterating + // over the chains twice. We can safely call process() while this + // is happening since we should not be erasing a locked pipe, and + // therefore won't be treading into deleted memory. I think we can + // also clear the lock on the chain safely since the pump only + // reads that value. +#if LL_THREADS_APR + LLScopedLock lock(mChainsMutex); +#endif + mClearLocks.insert(key); +} + +bool LLPumpIO::sleepChain(F64 seconds) +{ + // Much like the call to setLock(), this should only be called + // from one chain during processing, so there is no need to + // acquire a mutex. + if(seconds <= 0.0) return false; + S32 key = setLock(); + if(!key) return false; + LLRunner::run_handle_t handle = mRunner.addRunnable( + LLChainSleeper::build(this, key), + LLRunner::RUN_IN, + seconds); + if(0 == handle) return false; + return true; +} + +bool LLPumpIO::copyCurrentLinkInfo(links_t& links) const +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + if(current_chain_t() == mCurrentChain) + { + return false; + } + std::copy( + (*mCurrentChain).mChainLinks.begin(), + (*mCurrentChain).mChainLinks.end(), + std::back_insert_iterator(links)); + return true; +} + +void LLPumpIO::pump() +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + //llinfos << "LLPumpIO::pump()" << llendl; + + // Run any pending runners. + mRunner.run(); + + // We need to move all of the pending heads over to the running + // chains. + PUMP_DEBUG; + if(true) + { +#if LL_THREADS_APR + LLScopedLock lock(mChainsMutex); +#endif + // bail if this pump is paused. + if(PAUSING == mState) + { + mState = PAUSED; + } + if(PAUSED == mState) + { + return; + } + + PUMP_DEBUG; + // Move the pending chains over to the running chaings + if(!mPendingChains.empty()) + { + PUMP_DEBUG; + //lldebugs << "Pushing " << mPendingChains.size() << "." << llendl; + std::copy( + mPendingChains.begin(), + mPendingChains.end(), + std::back_insert_iterator(mRunningChains)); + mPendingChains.clear(); + PUMP_DEBUG; + } + + // Clear any locks. This needs to be done here so that we do + // not clash during a call to clearLock(). + if(!mClearLocks.empty()) + { + PUMP_DEBUG; + running_chains_t::iterator it = mRunningChains.begin(); + running_chains_t::iterator end = mRunningChains.end(); + std::set::iterator not_cleared = mClearLocks.end(); + for(; it != end; ++it) + { + if((*it).mLock && mClearLocks.find((*it).mLock) != not_cleared) + { + (*it).mLock = 0; + } + } + PUMP_DEBUG; + mClearLocks.clear(); + } + } + + PUMP_DEBUG; + // rebuild the pollset if necessary + if(mRebuildPollset) + { + PUMP_DEBUG; + rebuildPollset(); + mRebuildPollset = false; + } + + // Poll based on the last known pollset + // *FIX: may want to pass in a poll timeout so it works correctly + // in single and multi threaded processes. + PUMP_DEBUG; + typedef std::set signal_client_t; + signal_client_t signalled_client; + if(mPollset) + { + PUMP_DEBUG; + //llinfos << "polling" << llendl; + S32 count = 0; + S32 client_id = 0; + const apr_pollfd_t* poll_fd = NULL; + apr_pollset_poll(mPollset, DEFAULT_POLL_TIMEOUT, &count, &poll_fd); + PUMP_DEBUG; + for(S32 i = 0; i < count; ++i) + { + client_id = *((S32*)poll_fd[i].client_data); + signalled_client.insert(client_id); + } + PUMP_DEBUG; + } + + PUMP_DEBUG; + // set up for a check to see if each one was signalled + signal_client_t::iterator not_signalled = signalled_client.end(); + + // Process everything as appropriate + //lldebugs << "Running chain count: " << mRunningChains.size() << llendl; + running_chains_t::iterator run_chain = mRunningChains.begin(); + bool process_this_chain = false; + for(; run_chain != mRunningChains.end(); ) + { + PUMP_DEBUG; + if((*run_chain).mInit + && (*run_chain).mTimer.getStarted() + && (*run_chain).mTimer.hasExpired()) + { + PUMP_DEBUG; + if(handleChainError(*run_chain, LLIOPipe::STATUS_EXPIRED)) + { + // the pipe probably handled the error. If the handler + // forgot to reset the expiration then we need to do + // that here. + if((*run_chain).mTimer.getStarted() + && (*run_chain).mTimer.hasExpired()) + { + PUMP_DEBUG; + llinfos << "Error handler forgot to reset timeout. " + << "Resetting to " << DEFAULT_CHAIN_EXPIRY_SECS + << " seconds." << llendl; + (*run_chain).setTimeoutSeconds(DEFAULT_CHAIN_EXPIRY_SECS); + } + } + else + { + PUMP_DEBUG; + // it timed out and no one handled it, so we need to + // retire the chain +#if LL_DEBUG_PIPE_TYPE_IN_PUMP + lldebugs << "Removing chain " + << (*run_chain).mChainLinks[0].mPipe + << " '" + << typeid(*((*run_chain).mChainLinks[0].mPipe)).name() + << "' because it timed out." << llendl; +#else +// lldebugs << "Removing chain " +// << (*run_chain).mChainLinks[0].mPipe +// << " because we reached the end." << llendl; +#endif + mRunningChains.erase(run_chain++); + continue; + } + } + PUMP_DEBUG; + if((*run_chain).mLock) + { + ++run_chain; + continue; + } + PUMP_DEBUG; + mCurrentChain = run_chain; + if((*run_chain).mDescriptors.empty()) + { + // if there are no conditionals, just process this chain. + process_this_chain = true; + //lldebugs << "no conditionals - processing" << llendl; + } + else + { + PUMP_DEBUG; + //lldebugs << "checking conditionals" << llendl; + // Check if this run chain was signalled. If any file + // descriptor is ready for something, then go ahead and + // process this chian. + process_this_chain = false; + if(!signalled_client.empty()) + { + PUMP_DEBUG; + LLChainInfo::conditionals_t::iterator it; + it = (*run_chain).mDescriptors.begin(); + LLChainInfo::conditionals_t::iterator end; + end = (*run_chain).mDescriptors.end(); + S32 client_id = 0; + for(; it != end; ++it) + { + PUMP_DEBUG; + client_id = *((S32*)((*it).second.client_data)); + if(signalled_client.find(client_id) != not_signalled) + { + process_this_chain = true; + break; + } + //llinfos << "no fd ready for this one." << llendl; + } + } + } + if(process_this_chain) + { + PUMP_DEBUG; + if(!((*run_chain).mInit)) + { + (*run_chain).mHead = (*run_chain).mChainLinks.begin(); + (*run_chain).mInit = true; + } + PUMP_DEBUG; + processChain(*run_chain); + } + + PUMP_DEBUG; + if((*run_chain).mHead == (*run_chain).mChainLinks.end()) + { +#if LL_DEBUG_PIPE_TYPE_IN_PUMP + lldebugs << "Removing chain " << (*run_chain).mChainLinks[0].mPipe + << " '" + << typeid(*((*run_chain).mChainLinks[0].mPipe)).name() + << "' because we reached the end." << llendl; +#else +// lldebugs << "Removing chain " << (*run_chain).mChainLinks[0].mPipe +// << " because we reached the end." << llendl; +#endif + + PUMP_DEBUG; + // This chain is done. Clean up any allocated memory and + // erase the chain info. + std::for_each( + (*run_chain).mDescriptors.begin(), + (*run_chain).mDescriptors.end(), + ll_delete_apr_pollset_fd_client_data()); + mRunningChains.erase(run_chain++); + + // *NOTE: may not always need to rebuild the pollset. + mRebuildPollset = true; + } + else + { + PUMP_DEBUG; + // this chain needs more processing - just go to the next + // chain. + ++run_chain; + } + } + + PUMP_DEBUG; + // null out the chain + mCurrentChain = current_chain_t(); + END_PUMP_DEBUG; +} + +//bool LLPumpIO::respond(const chain_t& pipes) +//{ +//#if LL_THREADS_APR +// LLScopedLock lock(mCallbackMutex); +//#endif +// LLChainInfo info; +// links_t links; +// +// mPendingCallbacks.push_back(info); +// return true; +//} + +bool LLPumpIO::respond(LLIOPipe* pipe) +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + if(NULL == pipe) return false; + +#if LL_THREADS_APR + LLScopedLock lock(mCallbackMutex); +#endif + LLChainInfo info; + LLLinkInfo link; + link.mPipe = pipe; + info.mChainLinks.push_back(link); + mPendingCallbacks.push_back(info); + return true; +} + +bool LLPumpIO::respond( + const links_t& links, + LLIOPipe::buffer_ptr_t data, + LLSD context) +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + // if the caller is providing a full link description, we need to + // have that description matched to a particular buffer. + if(!data) return false; + if(links.empty()) return false; + +#if LL_THREADS_APR + LLScopedLock lock(mCallbackMutex); +#endif + + // Add the callback response + LLChainInfo info; + info.mChainLinks = links; + info.mData = data; + info.mContext = context; + mPendingCallbacks.push_back(info); + return true; +} + +void LLPumpIO::callback() +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + //llinfos << "LLPumpIO::callback()" << llendl; + if(true) + { +#if LL_THREADS_APR + LLScopedLock lock(mCallbackMutex); +#endif + std::copy( + mPendingCallbacks.begin(), + mPendingCallbacks.end(), + std::back_insert_iterator(mCallbacks)); + mPendingCallbacks.clear(); + } + if(!mCallbacks.empty()) + { + callbacks_t::iterator it = mCallbacks.begin(); + callbacks_t::iterator end = mCallbacks.end(); + for(; it != end; ++it) + { + // it's always the first and last time for respone chains + (*it).mHead = (*it).mChainLinks.begin(); + (*it).mInit = true; + (*it).mEOS = true; + processChain(*it); + } + mCallbacks.clear(); + } +} + +void LLPumpIO::control(LLPumpIO::EControl op) +{ +#if LL_THREADS_APR + LLScopedLock lock(mChainsMutex); +#endif + switch(op) + { + case PAUSE: + mState = PAUSING; + break; + case RESUME: + mState = NORMAL; + break; + default: + // no-op + break; + } +} + +void LLPumpIO::initialize(apr_pool_t* pool) +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + if(!pool) return; +#if LL_THREADS_APR + apr_thread_mutex_create(&mChainsMutex, APR_THREAD_MUTEX_DEFAULT, pool); + apr_thread_mutex_create(&mCallbackMutex, APR_THREAD_MUTEX_DEFAULT, pool); +#endif + mPool = pool; +} + +void LLPumpIO::cleanup() +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); +#if LL_THREADS_APR + if(mChainsMutex) apr_thread_mutex_destroy(mChainsMutex); + if(mCallbackMutex) apr_thread_mutex_destroy(mCallbackMutex); +#endif + mChainsMutex = NULL; + mCallbackMutex = NULL; + if(mPollset) + { +// lldebugs << "cleaning up pollset" << llendl; + apr_pollset_destroy(mPollset); + mPollset = NULL; + } + if(mCurrentPool) + { + apr_pool_destroy(mCurrentPool); + mCurrentPool = NULL; + } + mPool = NULL; +} + +void LLPumpIO::rebuildPollset() +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); +// lldebugs << "LLPumpIO::rebuildPollset()" << llendl; + if(mPollset) + { + //lldebugs << "destroying pollset" << llendl; + apr_pollset_destroy(mPollset); + mPollset = NULL; + } + U32 size = 0; + running_chains_t::iterator run_it = mRunningChains.begin(); + running_chains_t::iterator run_end = mRunningChains.end(); + for(; run_it != run_end; ++run_it) + { + size += (*run_it).mDescriptors.size(); + } + //lldebugs << "found " << size << " descriptors." << llendl; + if(size) + { + // Recycle the memory pool + const S32 POLLSET_POOL_RECYCLE_COUNT = 100; + if(mCurrentPool + && (0 == (++mCurrentPoolReallocCount % POLLSET_POOL_RECYCLE_COUNT))) + { + apr_pool_destroy(mCurrentPool); + mCurrentPool = NULL; + mCurrentPoolReallocCount = 0; + } + if(!mCurrentPool) + { + apr_status_t status = apr_pool_create(&mCurrentPool, mPool); + (void)ll_apr_warn_status(status); + } + + // add all of the file descriptors + run_it = mRunningChains.begin(); + LLChainInfo::conditionals_t::iterator fd_it; + LLChainInfo::conditionals_t::iterator fd_end; + apr_pollset_create(&mPollset, size, mCurrentPool, 0); + for(; run_it != run_end; ++run_it) + { + fd_it = (*run_it).mDescriptors.begin(); + fd_end = (*run_it).mDescriptors.end(); + for(; fd_it != fd_end; ++fd_it) + { + apr_pollset_add(mPollset, &((*fd_it).second)); + } + } + } +} + +void LLPumpIO::processChain(LLChainInfo& chain) +{ + PUMP_DEBUG; + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + LLIOPipe::EStatus status = LLIOPipe::STATUS_OK; + links_t::iterator it = chain.mHead; + links_t::iterator end = chain.mChainLinks.end(); + bool need_process_signaled = false; + bool keep_going = true; + do + { +#if LL_DEBUG_PROCESS_LINK +#if LL_DEBUG_PIPE_TYPE_IN_PUMP + llinfos << "Processing " << typeid(*((*it).mPipe)).name() << "." + << llendl; +#else + llinfos << "Processing link " << (*it).mPipe << "." << llendl; +#endif +#endif +#if LL_DEBUG_SPEW_BUFFER_CHANNEL_IN + if(chain.mData) + { + char* buf = NULL; + S32 bytes = chain.mData->countAfter((*it).mChannels.in(), NULL); + if(bytes) + { + buf = new char[bytes + 1]; + chain.mData->readAfter( + (*it).mChannels.in(), + NULL, + (U8*)buf, + bytes); + buf[bytes] = '\0'; + llinfos << "CHANNEL IN(" << (*it).mChannels.in() << "): " + << buf << llendl; + delete[] buf; + buf = NULL; + } + else + { + llinfos << "CHANNEL IN(" << (*it).mChannels.in()<< "): (null)" + << llendl; + } + } +#endif + PUMP_DEBUG; + status = (*it).mPipe->process( + (*it).mChannels, + chain.mData, + chain.mEOS, + chain.mContext, + this); +#if LL_DEBUG_SPEW_BUFFER_CHANNEL_OUT + if(chain.mData) + { + char* buf = NULL; + S32 bytes = chain.mData->countAfter((*it).mChannels.out(), NULL); + if(bytes) + { + buf = new char[bytes + 1]; + chain.mData->readAfter( + (*it).mChannels.out(), + NULL, + (U8*)buf, + bytes); + buf[bytes] = '\0'; + llinfos << "CHANNEL OUT(" << (*it).mChannels.out()<< "): " + << buf << llendl; + delete[] buf; + buf = NULL; + } + else + { + llinfos << "CHANNEL OUT(" << (*it).mChannels.out()<< "): (null)" + << llendl; + } + } +#endif + +#if LL_DEBUG_PROCESS_RETURN_VALUE + // Only bother with the success codes - error codes are logged + // below. + if(LLIOPipe::isSuccess(status)) + { + llinfos << "Pipe returned: '" +#if LL_DEBUG_PIPE_TYPE_IN_PUMP + << typeid(*((*it).mPipe)).name() << "':'" +#endif + << LLIOPipe::lookupStatusString(status) << "'" << llendl; + } +#endif + + PUMP_DEBUG; + switch(status) + { + case LLIOPipe::STATUS_OK: + // no-op + break; + case LLIOPipe::STATUS_STOP: + PUMP_DEBUG; + status = LLIOPipe::STATUS_OK; + chain.mHead = end; + keep_going = false; + break; + case LLIOPipe::STATUS_DONE: + PUMP_DEBUG; + status = LLIOPipe::STATUS_OK; + chain.mHead = (it + 1); + chain.mEOS = true; + break; + case LLIOPipe::STATUS_BREAK: + PUMP_DEBUG; + status = LLIOPipe::STATUS_OK; + keep_going = false; + break; + case LLIOPipe::STATUS_NEED_PROCESS: + PUMP_DEBUG; + status = LLIOPipe::STATUS_OK; + if(!need_process_signaled) + { + need_process_signaled = true; + chain.mHead = it; + } + break; + default: + PUMP_DEBUG; + if(LLIOPipe::isError(status)) + { + llinfos << "Pump generated pipe error: '" +#if LL_DEBUG_PIPE_TYPE_IN_PUMP + << typeid(*((*it).mPipe)).name() << "':'" +#endif + << LLIOPipe::lookupStatusString(status) + << "'" << llendl; +#if LL_DEBUG_SPEW_BUFFER_CHANNEL_IN_ON_ERROR + if(chain.mData) + { + char* buf = NULL; + S32 bytes = chain.mData->countAfter( + (*it).mChannels.in(), + NULL); + if(bytes) + { + buf = new char[bytes + 1]; + chain.mData->readAfter( + (*it).mChannels.in(), + NULL, + (U8*)buf, + bytes); + buf[bytes] = '\0'; + llinfos << "Input After Error: " << buf << llendl; + delete[] buf; + buf = NULL; + } + else + { + llinfos << "Input After Error: (null)" << llendl; + } + } + else + { + llinfos << "Input After Error: (null)" << llendl; + } +#endif + keep_going = false; + chain.mHead = it; + if(!handleChainError(chain, status)) + { + chain.mHead = end; + } + } + else + { + llinfos << "Unhandled status code: " << status << ":" + << LLIOPipe::lookupStatusString(status) << llendl; + } + break; + } + PUMP_DEBUG; + } while(keep_going && (++it != end)); + PUMP_DEBUG; +} + +bool LLPumpIO::handleChainError( + LLChainInfo& chain, + LLIOPipe::EStatus error) +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + links_t::reverse_iterator rit; + if(chain.mHead == chain.mChainLinks.end()) + { + rit = links_t::reverse_iterator(chain.mHead); + } + else + { + rit = links_t::reverse_iterator(chain.mHead + 1); + } + + links_t::reverse_iterator rend = chain.mChainLinks.rend(); + bool handled = false; + bool keep_going = true; + do + { +#if LL_DEBUG_PIPE_TYPE_IN_PUMP + lldebugs << "Passing error to " << typeid(*((*rit).mPipe)).name() + << "." << llendl; +#endif + error = (*rit).mPipe->handleError(error, this); + switch(error) + { + case LLIOPipe::STATUS_OK: + handled = true; + chain.mHead = rit.base(); + break; + case LLIOPipe::STATUS_STOP: + case LLIOPipe::STATUS_DONE: + case LLIOPipe::STATUS_BREAK: + case LLIOPipe::STATUS_NEED_PROCESS: +#if LL_DEBUG_PIPE_TYPE_IN_PUMP + lldebugs << "Pipe " << typeid(*((*rit).mPipe)).name() + << " returned code to stop error handler." << llendl; +#endif + keep_going = false; + break; + default: + if(LLIOPipe::isSuccess(error)) + { + llinfos << "Unhandled status code: " << error << ":" + << LLIOPipe::lookupStatusString(error) << llendl; + error = LLIOPipe::STATUS_ERROR; + keep_going = false; + } + break; + } + } while(keep_going && !handled && (++rit != rend)); + return handled; +} + +/** + * LLPumpIO::LLChainInfo + */ + +LLPumpIO::LLChainInfo::LLChainInfo() : + mInit(false), + mLock(0), + mEOS(false) +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + mTimer.setTimerExpirySec(DEFAULT_CHAIN_EXPIRY_SECS); +} + +void LLPumpIO::LLChainInfo::setTimeoutSeconds(F32 timeout) +{ + LLMemType m1(LLMemType::MTYPE_IO_PUMP); + if(timeout > 0.0f) + { + mTimer.start(); + mTimer.reset(); + mTimer.setTimerExpirySec(timeout); + } + else + { + mTimer.stop(); + } +} diff --git a/indra/llmessage/llpumpio.h b/indra/llmessage/llpumpio.h new file mode 100644 index 0000000000..50f7411298 --- /dev/null +++ b/indra/llmessage/llpumpio.h @@ -0,0 +1,406 @@ +/** + * @file llpumpio.h + * @author Phoenix + * @date 2004-11-19 + * @brief Declaration of pump class which manages io chains. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLPUMPIO_H +#define LL_LLPUMPIO_H + +#include +#if LL_LINUX // needed for PATH_MAX in APR. +#include +#endif + +#include "apr-1/apr_pools.h" +#include "llbuffer.h" +#include "llframetimer.h" +#include "lliopipe.h" +#include "llrun.h" + +// Define this to enable use with the APR thread library. +//#define LL_THREADS_APR 1 + +// some simple constants to help with timeouts +extern const F32 DEFAULT_CHAIN_EXPIRY_SECS; +extern const F32 SHORT_CHAIN_EXPIRY_SECS; +extern const F32 NEVER_CHAIN_EXPIRY_SECS; + +/** + * @class LLPumpIO + * @brief Class to manage sets of io chains. + * + * The pump class provides a thread abstraction for doing IO based + * communication between two threads in a structured and optimized for + * processor time. The primary usage is to create a pump, and call + * pump() on a thread used for IO and call + * respond() on a thread that is expected to do higher + * level processing. You can call almost any other method from any + * thread - see notes for each method for details. In order for the + * threading abstraction to work, you need to call prime() + * with a valid apr pool. + * A pump instance manages much of the state for the pipe, including + * the list of pipes in the chain, the channel for each element in the + * chain, the buffer, and if any pipe has marked the stream or process + * as done. Pipes can also set file descriptor based conditional + * statements so that calls to process do not happen until data is + * ready to be read or written. Pipes control execution of calls to + * process by returning a status code such as STATUS_OK or + * STATUS_BREAK. + * One way to conceptualize the way IO will work is that a pump + * combines the unit processing of pipes to behave like file pipes on + * the unix command line. + */ +class LLPumpIO +{ +public: + /** + * @brief Constructor. + */ + LLPumpIO(apr_pool_t* pool); + + /** + * @brief Destructor. + */ + ~LLPumpIO(); + + /** + * @brief Prepare this pump for usage. + * + * If you fail to call this method prior to use, the pump will + * try to work, but will not come with any thread locking + * mechanisms. + * @param pool The apr pool to use. + * @return Returns true if the pump is primed. + */ + bool prime(apr_pool_t* pool); + + /** + * @brief Typedef for having a chain of pipes. + */ + typedef std::vector chain_t; + + /** + * @brief Add a chain to this pump and process in the next cycle. + * + * This method will automatically generate a buffer and assign + * each link in the chain as if it were the consumer to the + * previous. + * @param chain The pipes for the chain + * @param timeout The number of seconds in the future to + * expire. Pass in 0.0f to never expire. + * @return Returns true if anything was added to the pump. + */ + bool addChain(const chain_t& chain, F32 timeout); + + /** + * @brief Struct to associate a pipe with it's buffer io indexes. + */ + struct LLLinkInfo + { + LLIOPipe::ptr_t mPipe; + LLChannelDescriptors mChannels; + }; + + /** + * @brief Typedef for having a chain of LLLinkInfo + * instances. + */ + typedef std::vector links_t; + + /** + * @brief Add a chain to this pump and process in the next cycle. + * + * This method provides a slightly more sophisticated method for + * adding a chain where the caller can specify which link elements + * are on what channels. This method will fail if no buffer is + * provided since any calls to generate new channels for the + * buffers will cause unpredictable interleaving of data. + * @param links The pipes and io indexes for the chain + * @param data Shared pointer to data buffer + * @param context Potentially undefined context meta-data for chain. + * @param timeout The number of seconds in the future to + * expire. Pass in 0.0f to never expire. + * @return Returns true if anything was added to the pump. + */ + bool addChain( + const links_t& links, + LLIOPipe::buffer_ptr_t data, + LLSD context, + F32 timeout); + + /** + * @brief Set or clear a timeout for the running chain + * + * @param timeout The number of seconds in the future to + * expire. Pass in 0.0f to never expire. + * @return Returns true if the timer was set. + */ + bool setTimeoutSeconds(F32 timeout); + + /** + * @brief Set up file descriptors for for the running chain. + * @see rebuildPollset() + * + * There is currently a limit of one conditional per pipe. + * *NOTE: The internal mechanism for building a pollset based on + * pipe/pollfd/chain generates an epoll error on linux (and + * probably behaves similarly on other platforms) because the + * pollset rebuilder will add each apr_pollfd_t serially. This + * does not matter for pipes on the same chain, since any + * signalled pipe will eventually invoke a call to process(), but + * is a problem if the same apr_pollfd_t is on different + * chains. Once we have more than just network i/o on the pump, + * this might matter. + * *FIX: Given the structure of the pump and pipe relationship, + * this should probably go through a different mechanism than the + * pump. I think it would be best if the pipe had some kind of + * controller which was passed into process() rather + * than the pump which exposed this interface. + * @param pipe The pipe which is setting a conditional + * @param poll The entire socket and read/write condition - null to remove + * @return Returns true if the poll state was set. + */ + bool setConditional(LLIOPipe* pipe, const apr_pollfd_t* poll); + + /** + * @brief Lock the current chain. + * @see sleepChain() since it relies on the implementation of this method. + * + * This locks the currently running chain so that no more calls to + * process() until you call clearLock() + * with the lock identifier. + * *FIX: Given the structure of the pump and pipe relationship, + * this should probably go through a different mechanism than the + * pump. I think it would be best if the pipe had some kind of + * controller which was passed into process() rather + * than the pump which exposed this interface. + * @return Returns the lock identifer to be used in + * clearLock() or 0 on failure. + */ + S32 setLock(); + + /** + * @brief Clears the identified lock. + * + * @param links A container for the links which will be appended + */ + void clearLock(S32 key); + + /** + * @brief Stop processing a chain for a while. + * @see setLock() + * + * This method will not update the timeout for this + * chain, so it is possible to sleep the chain until it is + * collected by the pump during a timeout cleanup. + * @param seconds The number of seconds in the future to + * resume processing. + * @return Returns true if the + */ + bool sleepChain(F64 seconds); + + /** + * @brief Copy the currently running chain link info + * + * *FIX: Given the structure of the pump and pipe relationship, + * this should probably go through a different mechanism than the + * pump. I think it would be best if the pipe had some kind of + * controller which was passed into process() rather + * than the pump which exposed this interface. + * @param links A container for the links which will be appended + * @return Returns true if the currently running chain was copied. + */ + bool copyCurrentLinkInfo(links_t& links) const; + + /** + * @brief Call this method to call process on all running chains. + * + * This method iterates through the running chains, and if all + * pipe on a chain are unconditionally ready or if any pipe has + * any conditional processiong condition then process will be + * called on every chain which has requested processing. that + * chain has a file descriptor ready, process() will + * be called for all pipes which have requested it. + */ + void pump(); + + /** + * @brief Add a chain to a special queue which will be called + * during the next call to callback() and then + * dropped from the queue. + * + * @param chain The IO chain that will get one process(). + */ + //void respond(const chain_t& pipes); + + /** + * @brief Add pipe to a special queue which will be called + * during the next call to callback() and then dropped + * from the queue. + * + * This call will add a single pipe, with no buffer, context, or + * channel information to the callback queue. It will be called + * once, and then dropped. + * @param pipe A single io pipe which will be called + * @return Returns true if anything was added to the pump. + */ + bool respond(LLIOPipe* pipe); + + /** + * @brief Add a chain to a special queue which will be called + * during the next call to callback() and then + * dropped from the queue. + * + * It is important to remember that you should not add a data + * buffer or context which may still be in another chain - that + * will almost certainly lead to a problems. Ensure that you are + * done reading and writing to those parameters, have new + * generated, or empty pointers. + * @param links The pipes and io indexes for the chain + * @param data Shared pointer to data buffer + * @param context Potentially undefined context meta-data for chain. + * @return Returns true if anything was added to the pump. + */ + bool respond( + const links_t& links, + LLIOPipe::buffer_ptr_t data, + LLSD context); + + /** + * @brief Run through the callback queue and call process(). + * + * This call will process all prending responses and call process + * on each. This method will then drop all processed callback + * requests which may lead to deleting the referenced objects. + */ + void callback(); + + /** + * @brief Enumeration to send commands to the pump. + */ + enum EControl + { + PAUSE, + RESUME, + }; + + /** + * @brief Send a command to the pump. + * + * @param op What control to send to the pump. + */ + void control(EControl op); + +protected: + /** + * @brief State of the pump + */ + enum EState + { + NORMAL, + PAUSING, + PAUSED + }; + + // instance data + EState mState; + bool mRebuildPollset; + apr_pollset_t* mPollset; + S32 mPollsetClientID; + S32 mNextLock; + std::set mClearLocks; + + // This is the pump's runnable scheduler used for handling + // expiring locks. + LLRunner mRunner; + + // This structure is the stuff we track while running chains. + struct LLChainInfo + { + // methods + LLChainInfo(); + void setTimeoutSeconds(F32 timeout); + + // basic member data + bool mInit; + S32 mLock; + LLFrameTimer mTimer; + links_t::iterator mHead; + links_t mChainLinks; + LLIOPipe::buffer_ptr_t mData; + bool mEOS; + LLSD mContext; + + // tracking inside the pump + typedef std::pair pipe_conditional_t; + typedef std::vector conditionals_t; + conditionals_t mDescriptors; + }; + + // All the running chains & info + typedef std::vector pending_chains_t; + pending_chains_t mPendingChains; + typedef std::list running_chains_t; + running_chains_t mRunningChains; + + typedef running_chains_t::iterator current_chain_t; + current_chain_t mCurrentChain; + + // structures necessary for doing callbacks + // since the callbacks only get one chance to run, we do not have + // to maintain a list. + typedef std::vector callbacks_t; + callbacks_t mPendingCallbacks; + callbacks_t mCallbacks; + + // memory allocator for pollsets & mutexes. + apr_pool_t* mPool; + apr_pool_t* mCurrentPool; + S32 mCurrentPoolReallocCount; + +#if LL_THREADS_APR + apr_thread_mutex_t* mChainsMutex; + apr_thread_mutex_t* mCallbackMutex; +#else + int* mChainsMutex; + int* mCallbackMutex; +#endif + +protected: + void initialize(apr_pool_t* pool); + void cleanup(); + + /** + * @brief Given the internal state of the chains, rebuild the pollset + * @see setConditional() + */ + void rebuildPollset(); + + /** + * @brief Process the chain passed in. + * + * This method will potentially modify the internals of the + * chain. On end, the chain.mHead will equal + * chain.mChainLinks.end(). + * @param chain The LLChainInfo object to work on. + */ + void processChain(LLChainInfo& chain); + + /** + * @brief Rewind through the chain to try to recover from an error. + * + * This method will potentially modify the internals of the + * chain. + * @param chain The LLChainInfo object to work on. + * @return Retuns true if someone handled the error + */ + bool handleChainError(LLChainInfo& chain, LLIOPipe::EStatus error); +}; + + +#endif // LL_LLPUMPIO_H diff --git a/indra/llmessage/llqueryflags.h b/indra/llmessage/llqueryflags.h new file mode 100644 index 0000000000..be6c9acf67 --- /dev/null +++ b/indra/llmessage/llqueryflags.h @@ -0,0 +1,32 @@ +/** + * @file llqueryflags.h + * @brief Flags for directory queries + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLQUERYFLAGS_H +#define LL_LLQUERYFLAGS_H + +// Binary flags used for Find queries, shared between viewer and dataserver. + +// DirFindQuery flags +const U32 DFQ_PEOPLE = 0x0001; +const U32 DFQ_ONLINE = 0x0002; +//const U32 DFQ_PLACES = 0x0004; +const U32 DFQ_EVENTS = 0x0008; +const U32 DFQ_GROUPS = 0x0010; +const U32 DFQ_DATE_EVENTS = 0x0020; + +const U32 DFQ_AGENT_OWNED = 0x0040; +const U32 DFQ_FOR_SALE = 0x0080; +const U32 DFQ_GROUP_OWNED = 0x0100; +//const U32 DFQ_AUCTION = 0x0200; +const U32 DFQ_DWELL_SORT = 0x0400; +const U32 DFQ_PG_SIMS_ONLY = 0x0800; +const U32 DFQ_PICTURES_ONLY = 0x1000; +const U32 DFQ_PG_EVENTS_ONLY = 0x2000; +const U32 DFQ_MATURE_SIMS_ONLY = 0x4000; + +#endif diff --git a/indra/llmessage/llregionflags.h b/indra/llmessage/llregionflags.h new file mode 100644 index 0000000000..3f37c72cee --- /dev/null +++ b/indra/llmessage/llregionflags.h @@ -0,0 +1,157 @@ +/** + * @file llregionflags.h + * @brief Flags that are sent in the statistics message region_flags field. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLREGIONFLAGS_H +#define LL_LLREGIONFLAGS_H + +// Can you be hurt here? Should health be on? +const U32 REGION_FLAGS_ALLOW_DAMAGE = (1 << 0); + +// Can you make landmarks here? +const U32 REGION_FLAGS_ALLOW_LANDMARK = (1 << 1); + +// Do we reset the home position when someone teleports away from here? +const U32 REGION_FLAGS_ALLOW_SET_HOME = (1 << 2); + +// Do we reset the home position when someone teleports away from here? +const U32 REGION_FLAGS_RESET_HOME_ON_TELEPORT = (1 << 3); + +// Does the sun move? +const U32 REGION_FLAGS_SUN_FIXED = (1 << 4); + +// Tax free zone (no taxes on objects, land, etc.) +const U32 REGION_FLAGS_TAX_FREE = (1 << 5); + +// Can't change the terrain heightfield, even on owned parcels, +// but can plant trees and grass. +const U32 REGION_FLAGS_BLOCK_TERRAFORM = (1 << 6); + +// Can't release, sell, or buy land. +const U32 REGION_FLAGS_BLOCK_LAND_RESELL = (1 << 7); + +// All content wiped once per night +const U32 REGION_FLAGS_SANDBOX = (1 << 8); + +const U32 REGION_FLAGS_SKIP_AGENT_ACTION = (1 << 10); +const U32 REGION_FLAGS_SKIP_UPDATE_INTEREST_LIST= (1 << 11); +const U32 REGION_FLAGS_SKIP_COLLISIONS = (1 << 12); // Pin all non agent rigid bodies +const U32 REGION_FLAGS_SKIP_SCRIPTS = (1 << 13); +const U32 REGION_FLAGS_SKIP_PHYSICS = (1 << 14); // Skip all physics +const U32 REGION_FLAGS_EXTERNALLY_VISIBLE = (1 << 15); +const U32 REGION_FLAGS_MAINLAND_VISIBLE = (1 << 16); +const U32 REGION_FLAGS_PUBLIC_ALLOWED = (1 << 17); +const U32 REGION_FLAGS_BLOCK_DWELL = (1 << 18); + +// Is flight allowed? +const U32 REGION_FLAGS_BLOCK_FLY = (1 << 19); + +// Is direct teleport (p2p) allowed? +const U32 REGION_FLAGS_ALLOW_DIRECT_TELEPORT = (1 << 20); + +// Is there an administrative override on scripts in the region at the +// moment. This is the similar skip scripts, except this flag is +// presisted in the database on an estate level. +const U32 REGION_FLAGS_ESTATE_SKIP_SCRIPTS = (1 << 21); + +const U32 REGION_FLAGS_RESTRICT_PUSHOBJECT = (1 << 22); + +const U32 REGION_FLAGS_DENY_ANONYMOUS = (1 << 23); +const U32 REGION_FLAGS_DENY_IDENTIFIED = (1 << 24); +const U32 REGION_FLAGS_DENY_TRANSACTED = (1 << 25); + +const U32 REGION_FLAGS_ALLOW_PARCEL_CHANGES = (1 << 26); + +const U32 REGION_FLAGS_NULL_LAYER = (1 << 9); + +const U32 REGION_FLAGS_DEFAULT = REGION_FLAGS_ALLOW_LANDMARK | + REGION_FLAGS_ALLOW_SET_HOME | + REGION_FLAGS_ALLOW_PARCEL_CHANGES; + +const U32 REGION_FLAGS_PRELUDE_SET = REGION_FLAGS_RESET_HOME_ON_TELEPORT; +const U32 REGION_FLAGS_PRELUDE_UNSET = REGION_FLAGS_ALLOW_LANDMARK + | REGION_FLAGS_ALLOW_SET_HOME; + +const U32 REGION_FLAGS_ESTATE_MASK = REGION_FLAGS_EXTERNALLY_VISIBLE + | REGION_FLAGS_MAINLAND_VISIBLE + | REGION_FLAGS_PUBLIC_ALLOWED + | REGION_FLAGS_SUN_FIXED + | REGION_FLAGS_DENY_ANONYMOUS + | REGION_FLAGS_DENY_IDENTIFIED + | REGION_FLAGS_DENY_TRANSACTED; + +inline BOOL is_prelude( U32 flags ) +{ + // definition of prelude does not depend on fixed-sun + return 0 == (flags & REGION_FLAGS_PRELUDE_UNSET) + && 0 != (flags & REGION_FLAGS_PRELUDE_SET); +} + +inline U32 set_prelude_flags(U32 flags) +{ + // also set the sun-fixed flag + return ((flags & ~REGION_FLAGS_PRELUDE_UNSET) + | (REGION_FLAGS_PRELUDE_SET | REGION_FLAGS_SUN_FIXED)); +} + +inline U32 unset_prelude_flags(U32 flags) +{ + // also unset the fixed-sun flag + return ((flags | REGION_FLAGS_PRELUDE_UNSET) + & ~(REGION_FLAGS_PRELUDE_SET | REGION_FLAGS_SUN_FIXED)); +} + +// estate constants. Need to match first few etries in indra.estate table. +const U32 ESTATE_ALL = 0; // will not match in db, reserved key for logic +const U32 ESTATE_MAINLAND = 1; +const U32 ESTATE_ORIENTATION = 2; +const U32 ESTATE_INTERNAL = 3; +const U32 ESTATE_SHOWCASE = 4; +const U32 ESTATE_KIDGRID = 5; +const U32 ESTATE_LAST_LINDEN = 5; // last linden owned/managed estate + +// for EstateOwnerRequest, setaccess message +const U32 ESTATE_ACCESS_ALLOWED_AGENTS = 1 << 0; +const U32 ESTATE_ACCESS_ALLOWED_GROUPS = 1 << 1; +const U32 ESTATE_ACCESS_BANNED_AGENTS = 1 << 2; +const U32 ESTATE_ACCESS_MANAGERS = 1 << 3; + +//maximum number of access list entries we can fit in one packet +const S32 ESTATE_ACCESS_MAX_ENTRIES_PER_PACKET = 63; + +// for reply to "getinfo", don't need to forward to all sims in estate +const U32 ESTATE_ACCESS_SEND_TO_AGENT_ONLY = 1 << 4; + +const U32 ESTATE_ACCESS_ALL = ESTATE_ACCESS_ALLOWED_AGENTS + | ESTATE_ACCESS_ALLOWED_GROUPS + | ESTATE_ACCESS_BANNED_AGENTS + | ESTATE_ACCESS_MANAGERS; + +// for EstateOwnerRequest, estateaccessdelta message +const U32 ESTATE_ACCESS_APPLY_TO_ALL_ESTATES = 1 << 0; +const U32 ESTATE_ACCESS_APPLY_TO_MANAGED_ESTATES = 1 << 1; + +const U32 ESTATE_ACCESS_ALLOWED_AGENT_ADD = 1 << 2; +const U32 ESTATE_ACCESS_ALLOWED_AGENT_REMOVE = 1 << 3; +const U32 ESTATE_ACCESS_ALLOWED_GROUP_ADD = 1 << 4; +const U32 ESTATE_ACCESS_ALLOWED_GROUP_REMOVE = 1 << 5; +const U32 ESTATE_ACCESS_BANNED_AGENT_ADD = 1 << 6; +const U32 ESTATE_ACCESS_BANNED_AGENT_REMOVE = 1 << 7; +const U32 ESTATE_ACCESS_MANAGER_ADD = 1 << 8; +const U32 ESTATE_ACCESS_MANAGER_REMOVE = 1 << 9; + +const S32 ESTATE_MAX_MANAGERS = 10; +const S32 ESTATE_MAX_ACCESS_IDS = 300; // max for access, banned +const S32 ESTATE_MAX_GROUP_IDS = (S32) ESTATE_ACCESS_MAX_ENTRIES_PER_PACKET; + +// 'Sim Wide Delete' flags +const U32 SWD_OTHERS_LAND_ONLY = (1 << 0); +const U32 SWD_ALWAYS_RETURN_OBJECTS = (1 << 1); +const U32 SWD_SCRIPTED_ONLY = (1 << 2); + +#endif + diff --git a/indra/llmessage/llregionhandle.h b/indra/llmessage/llregionhandle.h new file mode 100644 index 0000000000..41d104231c --- /dev/null +++ b/indra/llmessage/llregionhandle.h @@ -0,0 +1,110 @@ +/** + * @file llregionhandle.h + * @brief Routines for converting positions to/from region handles. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLREGIONHANDLE_H +#define LL_LLREGIONHANDLE_H + +#include + +#include "indra_constants.h" +#include "v3math.h" +#include "v3dmath.h" + +inline U64 to_region_handle(const U32 x_origin, const U32 y_origin) +{ + U64 region_handle; + region_handle = ((U64)x_origin) << 32; + region_handle |= (U64) y_origin; + return region_handle; +} + +inline U64 to_region_handle(const LLVector3d& pos_global) +{ + U32 global_x = (U32)pos_global.mdV[VX]; + global_x -= global_x % 256; + + U32 global_y = (U32)pos_global.mdV[VY]; + global_y -= global_y % 256; + + return to_region_handle(global_x, global_y); +} + +inline U64 to_region_handle_global(const F32 x_global, const F32 y_global) +{ + // Round down to the nearest origin + U32 x_origin = (U32)x_global; + x_origin -= x_origin % REGION_WIDTH_U32; + U32 y_origin = (U32)y_global; + y_origin -= y_origin % REGION_WIDTH_U32; + U64 region_handle; + region_handle = ((U64)x_origin) << 32; + region_handle |= (U64) y_origin; + return region_handle; +} + +inline BOOL to_region_handle(const F32 x_pos, const F32 y_pos, U64 *region_handle) +{ + U32 x_int, y_int; + if (x_pos < 0.f) + { +// llwarns << "to_region_handle:Clamping negative x position " << x_pos << " to zero!" << llendl; + return FALSE; + } + else + { + x_int = (U32)llround(x_pos); + } + if (y_pos < 0.f) + { +// llwarns << "to_region_handle:Clamping negative y position " << y_pos << " to zero!" << llendl; + return FALSE; + } + else + { + y_int = (U32)llround(y_pos); + } + *region_handle = to_region_handle(x_int, y_int); + return TRUE; +} + +// stuff the word-frame XY location of sim's SouthWest corner in x_pos, y_pos +inline void from_region_handle(const U64 ®ion_handle, F32 *x_pos, F32 *y_pos) +{ + *x_pos = (F32)((U32)(region_handle >> 32)); + *y_pos = (F32)((U32)(region_handle & 0xFFFFFFFF)); +} + +// stuff the word-frame XY location of sim's SouthWest corner in x_pos, y_pos +inline void from_region_handle(const U64 ®ion_handle, U32 *x_pos, U32 *y_pos) +{ + *x_pos = ((U32)(region_handle >> 32)); + *y_pos = ((U32)(region_handle & 0xFFFFFFFF)); +} + +// return the word-frame XY location of sim's SouthWest corner in LLVector3d +inline LLVector3d from_region_handle(const U64 ®ion_handle) +{ + return LLVector3d(((U32)(region_handle >> 32)), (U32)(region_handle & 0xFFFFFFFF), 0.f); +} + +// grid-based region handle encoding. pass in a grid position +// (eg: 1000,1000) and this will return the region handle. +inline U64 grid_to_region_handle(U32 grid_x, U32 grid_y) +{ + return to_region_handle(grid_x * REGION_WIDTH_UNITS, + grid_y * REGION_WIDTH_UNITS); +} + +inline void grid_from_region_handle(const U64& region_handle, U32* grid_x, U32* grid_y) +{ + from_region_handle(region_handle, grid_x, grid_y); + *grid_x /= REGION_WIDTH_UNITS; + *grid_y /= REGION_WIDTH_UNITS; +} + +#endif diff --git a/indra/llmessage/llsdappservices.cpp b/indra/llmessage/llsdappservices.cpp new file mode 100644 index 0000000000..c10e9bd113 --- /dev/null +++ b/indra/llmessage/llsdappservices.cpp @@ -0,0 +1,259 @@ +/** + * @file llsdappservices.cpp + * @author Phoenix + * @date 2006-09-12 + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llsdappservices.h" + +#include "llapp.h" +#include "llhttpnode.h" +#include "llsdserialize.h" + +void LLSDAppServices::useServices() +{ + /* + Having this function body here, causes the classes and globals in this + file to be linked into any program that uses the llmessage library. + */ +} + +class LLHTTPConfigService : public LLHTTPNode +{ +public: + virtual void describe(Description& desc) const + { + desc.shortInfo("GET an array of all the options in priority order."); + desc.getAPI(); + desc.source(__FILE__, __LINE__); + } + + virtual LLSD get() const + { + LLSD result; + LLApp* app = LLApp::instance(); + for(int ii = 0; ii < LLApp::PRIORITY_COUNT; ++ii) + { + result.append(app->getOptionData((LLApp::OptionPriority)ii)); + } + return result; + } +}; + +LLHTTPRegistration + gHTTPRegistratiAppConfig("/app/config"); + +class LLHTTPConfigRuntimeService : public LLHTTPNode +{ +public: + virtual void describe(Description& desc) const + { + desc.shortInfo("Manipulate a map of runtime-override options."); + desc.getAPI(); + desc.postAPI(); + desc.source(__FILE__, __LINE__); + } + + virtual LLSD get() const + { + return LLApp::instance()->getOptionData( + LLApp::PRIORITY_RUNTIME_OVERRIDE); + } + + virtual void post( + LLHTTPNode::ResponsePtr response, + const LLSD& context, + const LLSD& input) const + { + LLSD result = LLApp::instance()->getOptionData( + LLApp::PRIORITY_RUNTIME_OVERRIDE); + LLSD::map_const_iterator iter = input.beginMap(); + LLSD::map_const_iterator end = input.endMap(); + for(; iter != end; ++iter) + { + result[(*iter).first] = (*iter).second; + } + LLApp::instance()->setOptionData( + LLApp::PRIORITY_RUNTIME_OVERRIDE, + result); + response->result(result); + } +}; + +LLHTTPRegistration + gHTTPRegistrationRuntimeConfig("/app/config/runtime-override"); + +class LLHTTPConfigRuntimeSingleService : public LLHTTPNode +{ +public: + virtual void describe(Description& desc) const + { + desc.shortInfo("Manipulate a single runtime-override option."); + desc.getAPI(); + desc.putAPI(); + desc.delAPI(); + desc.source(__FILE__, __LINE__); + } + + virtual bool validate(const std::string& name, LLSD& context) const + { + //llinfos << "validate: " << name << ", " + // << LLSDOStreamer(context) << llendl; + if((std::string("PUT") == context["request"]["verb"].asString()) && !name.empty()) + { + return true; + } + else + { + // This is for GET and DELETE + LLSD options = LLApp::instance()->getOptionData( + LLApp::PRIORITY_RUNTIME_OVERRIDE); + if(options.has(name)) return true; + else return false; + } + } + + virtual void get( + LLHTTPNode::ResponsePtr response, + const LLSD& context) const + { + std::string name = context["request"]["wildcard"]["option-name"]; + LLSD options = LLApp::instance()->getOptionData( + LLApp::PRIORITY_RUNTIME_OVERRIDE); + response->result(options[name]); + } + + virtual void put( + LLHTTPNode::ResponsePtr response, + const LLSD& context, + const LLSD& input) const + { + std::string name = context["request"]["wildcard"]["option-name"]; + LLSD options = LLApp::instance()->getOptionData( + LLApp::PRIORITY_RUNTIME_OVERRIDE); + options[name] = input; + LLApp::instance()->setOptionData( + LLApp::PRIORITY_RUNTIME_OVERRIDE, + options); + response->result(input); + } + + virtual void del( + LLHTTPNode::ResponsePtr response, + const LLSD& context) const + { + std::string name = context["request"]["wildcard"]["option-name"]; + LLSD options = LLApp::instance()->getOptionData( + LLApp::PRIORITY_RUNTIME_OVERRIDE); + options.erase(name); + LLApp::instance()->setOptionData( + LLApp::PRIORITY_RUNTIME_OVERRIDE, + options); + response->result(LLSD()); + } +}; + +LLHTTPRegistration + gHTTPRegistrationRuntimeSingleConfig( + "/app/config/runtime-override/"); + +template +class LLHTTPConfigPriorityService : public LLHTTPNode +{ +public: + virtual void describe(Description& desc) const + { + desc.shortInfo("Get a map of the options at this priority."); + desc.getAPI(); + desc.source(__FILE__, __LINE__); + } + + virtual void get( + LLHTTPNode::ResponsePtr response, + const LLSD& context) const + { + response->result(LLApp::instance()->getOptionData( + (LLApp::OptionPriority)PRIORITY)); + } +}; + +LLHTTPRegistration< LLHTTPConfigPriorityService > + gHTTPRegistrationCommandLineConfig("/app/config/command-line"); +LLHTTPRegistration< + LLHTTPConfigPriorityService > + gHTTPRegistrationSpecificConfig("/app/config/specific"); +LLHTTPRegistration< + LLHTTPConfigPriorityService > + gHTTPRegistrationGeneralConfig("/app/config/general"); +LLHTTPRegistration< LLHTTPConfigPriorityService > + gHTTPRegistrationDefaultConfig("/app/config/default"); + +class LLHTTPLiveConfigService : public LLHTTPNode +{ +public: + virtual void describe(Description& desc) const + { + desc.shortInfo("Get a map of the currently live options."); + desc.getAPI(); + desc.source(__FILE__, __LINE__); + } + + virtual void get( + LLHTTPNode::ResponsePtr response, + const LLSD& context) const + { + LLSD result; + LLApp* app = LLApp::instance(); + LLSD::map_const_iterator iter; + LLSD::map_const_iterator end; + for(int ii = LLApp::PRIORITY_COUNT - 1; ii >= 0; --ii) + { + LLSD options = app->getOptionData((LLApp::OptionPriority)ii); + iter = options.beginMap(); + end = options.endMap(); + for(; iter != end; ++iter) + { + result[(*iter).first] = (*iter).second; + } + } + response->result(result); + } +}; + +LLHTTPRegistration + gHTTPRegistrationLiveConfig("/app/config/live"); + +class LLHTTPLiveConfigSingleService : public LLHTTPNode +{ +public: + virtual void describe(Description& desc) const + { + desc.shortInfo("Get the named live option."); + desc.getAPI(); + desc.source(__FILE__, __LINE__); + } + + virtual bool validate(const std::string& name, LLSD& context) const + { + llinfos << "LLHTTPLiveConfigSingleService::validate(" << name + << ")" << llendl; + LLSD option = LLApp::instance()->getOption(name); + if(option) return true; + else return false; + } + + virtual void get( + LLHTTPNode::ResponsePtr response, + const LLSD& context) const + { + std::string name = context["request"]["wildcard"]["option-name"]; + response->result(LLApp::instance()->getOption(name)); + } +}; + +LLHTTPRegistration + gHTTPRegistrationLiveSingleConfig("/app/config/live/"); diff --git a/indra/llmessage/llsdappservices.h b/indra/llmessage/llsdappservices.h new file mode 100644 index 0000000000..c9bc9570df --- /dev/null +++ b/indra/llmessage/llsdappservices.h @@ -0,0 +1,40 @@ +/** + * @file llsdappservices.h + * @author Phoenix + * @date 2006-09-12 + * @brief Header file to declare the /app common web services. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSDAPPSERVICES_H +#define LL_LLSDAPPSERVICES_H + +/** + * @class LLSDAppServices + * @brief This class forces a link to llsdappservices if the static + * method is called which declares the /app web services. + */ +class LLSDAppServices +{ +public: + /** + * @brief Call this method to declare the /app common web services. + * + * This will register: + * /app/config + * /app/config/runtime-override + * /app/config/runtime-override/ + * /app/config/command-line + * /app/config/specific + * /app/config/general + * /app/config/default + * /app/config/live + * /app/config/live/ + */ + static void useServices(); +}; + + +#endif // LL_LLSDAPPSERVICES_H diff --git a/indra/llmessage/llsdhttpserver.cpp b/indra/llmessage/llsdhttpserver.cpp new file mode 100644 index 0000000000..0eda0e69cb --- /dev/null +++ b/indra/llmessage/llsdhttpserver.cpp @@ -0,0 +1,132 @@ +/** + * @file llsdhttpserver.cpp + * @brief Standard LLSD services + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llsdhttpserver.h" + +#include "llhttpnode.h" + +/** + * This module implements common services that should be included + * in all server URL trees. These services facilitate debugging and + * introsepction. + */ + +void LLHTTPStandardServices::useServices() +{ + /* + Having this function body here, causes the classes and globals in this + file to be linked into any program that uses the llmessage library. + */ +} + + + +class LLHTTPHelloService : public LLHTTPNode +{ +public: + virtual void describe(Description& desc) const + { + desc.shortInfo("says hello"); + desc.getAPI(); + desc.output("\"hello\""); + desc.source(__FILE__, __LINE__); + } + + virtual LLSD get() const + { + LLSD result = "hello"; + return result; + } +}; + +LLHTTPRegistration + gHTTPRegistrationWebHello("/web/hello"); + + + +class LLHTTPEchoService : public LLHTTPNode +{ +public: + virtual void describe(Description& desc) const + { + desc.shortInfo("echo input"); + desc.postAPI(); + desc.input(""); + desc.output(""); + desc.source(__FILE__, __LINE__); + } + + virtual LLSD post(const LLSD& params) const + { + return params; + } +}; + +LLHTTPRegistration + gHTTPRegistrationWebEcho("/web/echo"); + + + +class LLAPIService : public LLHTTPNode +{ +public: + virtual void describe(Description& desc) const + { + desc.shortInfo("information about the URLs this server supports"); + desc.getAPI(); + desc.output("a list of URLs supported"); + desc.source(__FILE__, __LINE__); + } + + virtual bool handles(const LLSD& remainder, LLSD& context) const + { + return followRemainder(remainder) != NULL; + } + + virtual void get(ResponsePtr response, const LLSD& context) const + { + const LLSD& remainder = context["request"]["remainder"]; + + if (remainder.size() > 0) + { + const LLHTTPNode* node = followRemainder(remainder); + if (!node) + { + response->notFound(); + return; + } + + Description desc; + node->describe(desc); + response->result(desc.getInfo()); + return; + } + + response->result(rootNode()->allNodePaths()); + } + +private: + const LLHTTPNode* followRemainder(const LLSD& remainder) const + { + const LLHTTPNode* node = rootNode(); + + LLSD::array_const_iterator i = remainder.beginArray(); + LLSD::array_const_iterator end = remainder.endArray(); + for (; node && i != end; ++i) + { + node = node->findNode(*i); + } + + return node; + } +}; + +LLHTTPRegistration + gHTTPRegistrationWebServerApi("/web/server/api"); + diff --git a/indra/llmessage/llsdhttpserver.h b/indra/llmessage/llsdhttpserver.h new file mode 100644 index 0000000000..11ff38955b --- /dev/null +++ b/indra/llmessage/llsdhttpserver.h @@ -0,0 +1,33 @@ +/** + * @file llsdhttpserver.h + * @brief Standard LLSD services + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSDHTTPSERVER_H +#define LL_LLSDHTTPSERVER_H + +/** + * This module implements and defines common services that should be included + * in all server URL trees. These services facilitate debugging and + * introsepction. + */ + +class LLHTTPStandardServices +{ +public: + static void useServices(); + /**< + Having a call to this function causes the following services to be + registered: + /web/echo -- echo input + /web/hello -- return "hello" + /web/server/api -- return a list of url paths on the server + /web/server/api/<..path..> + -- return description of the path + */ +}; + +#endif // LL_LLSDHTTPSERVER_H diff --git a/indra/llmessage/llsdrpcclient.cpp b/indra/llmessage/llsdrpcclient.cpp new file mode 100644 index 0000000000..4832ddaa58 --- /dev/null +++ b/indra/llmessage/llsdrpcclient.cpp @@ -0,0 +1,230 @@ +/** + * @file llsdrpcclient.cpp + * @author Phoenix + * @date 2005-11-05 + * @brief Implementation of the llsd client classes. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llsdrpcclient.h" + +#include "llbufferstream.h" +#include "llfiltersd2xmlrpc.h" +#include "llmemtype.h" +#include "llpumpio.h" +#include "llsd.h" +#include "llsdserialize.h" +#include "llurlrequest.h" + +/** + * String constants + */ +static std::string LLSDRPC_RESPONSE_NAME("response"); +static std::string LLSDRPC_FAULT_NAME("fault"); + +/** + * LLSDRPCResponse + */ +LLSDRPCResponse::LLSDRPCResponse() : + mIsError(false), + mIsFault(false) +{ + LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); +} + +// virtual +LLSDRPCResponse::~LLSDRPCResponse() +{ + LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); +} + +bool LLSDRPCResponse::extractResponse(const LLSD& sd) +{ + LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); + bool rv = true; + if(sd.has(LLSDRPC_RESPONSE_NAME)) + { + mReturnValue = sd[LLSDRPC_RESPONSE_NAME]; + mIsFault = false; + } + else if(sd.has(LLSDRPC_FAULT_NAME)) + { + mReturnValue = sd[LLSDRPC_FAULT_NAME]; + mIsFault = true; + } + else + { + mReturnValue.clear(); + mIsError = true; + rv = false; + } + return rv; +} + +// virtual +LLIOPipe::EStatus LLSDRPCResponse::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); + if(mIsError) + { + error(pump); + } + else if(mIsFault) + { + fault(pump); + } + else + { + response(pump); + } + PUMP_DEBUG; + return STATUS_DONE; +} + +/** + * LLSDRPCClient + */ + +LLSDRPCClient::LLSDRPCClient() : + mState(STATE_NONE), + mQueue(EPBQ_PROCESS) +{ + LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); +} + +// virtual +LLSDRPCClient::~LLSDRPCClient() +{ + LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); +} + +bool LLSDRPCClient::call( + const std::string& uri, + const std::string& method, + const LLSD& parameter, + LLSDRPCResponse* response, + EPassBackQueue queue) +{ + LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); + //llinfos << "RPC: " << uri << "." << method << "(" << *parameter << ")" + // << llendl; + if(method.empty() || !response) + { + return false; + } + mState = STATE_READY; + mURI.assign(uri); + std::stringstream req; + req << LLSDRPC_REQUEST_HEADER_1 << method + << LLSDRPC_REQUEST_HEADER_2; + LLSDSerialize::toNotation(parameter, req); + req << LLSDRPC_REQUEST_FOOTER; + mRequest = req.str(); + mQueue = queue; + mResponse = response; + return true; +} + +bool LLSDRPCClient::call( + const std::string& uri, + const std::string& method, + const std::string& parameter, + LLSDRPCResponse* response, + EPassBackQueue queue) +{ + LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); + //llinfos << "RPC: " << uri << "." << method << "(" << parameter << ")" + // << llendl; + if(method.empty() || parameter.empty() || !response) + { + return false; + } + mState = STATE_READY; + mURI.assign(uri); + std::stringstream req; + req << LLSDRPC_REQUEST_HEADER_1 << method + << LLSDRPC_REQUEST_HEADER_2 << parameter + << LLSDRPC_REQUEST_FOOTER; + mRequest = req.str(); + mQueue = queue; + mResponse = response; + return true; +} + +// virtual +LLIOPipe::EStatus LLSDRPCClient::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); + if((STATE_NONE == mState) || (!pump)) + { + // You should have called the call() method already. + return STATUS_PRECONDITION_NOT_MET; + } + EStatus rv = STATUS_DONE; + switch(mState) + { + case STATE_READY: + { + PUMP_DEBUG; +// lldebugs << "LLSDRPCClient::process_impl STATE_READY" << llendl; + buffer->append( + channels.out(), + (U8*)mRequest.c_str(), + mRequest.length()); + context[CONTEXT_DEST_URI_SD_LABEL] = mURI; + mState = STATE_WAITING_FOR_RESPONSE; + break; + } + case STATE_WAITING_FOR_RESPONSE: + { + PUMP_DEBUG; + // The input channel has the sd response in it. + //lldebugs << "LLSDRPCClient::process_impl STATE_WAITING_FOR_RESPONSE" + // << llendl; + LLBufferStream resp(channels, buffer.get()); + LLSD sd; + LLSDSerialize::fromNotation(sd, resp); + LLSDRPCResponse* response = (LLSDRPCResponse*)mResponse.get(); + if (!response) + { + mState = STATE_DONE; + break; + } + response->extractResponse(sd); + if(EPBQ_PROCESS == mQueue) + { + LLPumpIO::chain_t chain; + chain.push_back(mResponse); + pump->addChain(chain, DEFAULT_CHAIN_EXPIRY_SECS); + } + else + { + pump->respond(mResponse.get()); + } + mState = STATE_DONE; + break; + } + case STATE_DONE: + default: + PUMP_DEBUG; + llinfos << "invalid state to process" << llendl; + rv = STATUS_ERROR; + break; + } + return rv; +} diff --git a/indra/llmessage/llsdrpcclient.h b/indra/llmessage/llsdrpcclient.h new file mode 100644 index 0000000000..173a0d1dbb --- /dev/null +++ b/indra/llmessage/llsdrpcclient.h @@ -0,0 +1,291 @@ +/** + * @file llsdrpcclient.h + * @author Phoenix + * @date 2005-11-05 + * @brief Implementation and helpers for structure data RPC clients. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSDRPCCLIENT_H +#define LL_LLSDRPCCLIENT_H + +/** + * This file declares classes to encapsulate a basic structured data + * remote procedure client. + */ + +#include "llchainio.h" +#include "llfiltersd2xmlrpc.h" +#include "lliopipe.h" +#include "llurlrequest.h" + +/** + * @class LLSDRPCClientResponse + * @brief Abstract base class to represent a response from an SD server. + * + * This is used as a base class for callbacks generated from an + * structured data remote procedure call. The + * extractResponse method will deal with the llsdrpc method + * call overhead, and keep track of what to call during the next call + * into process. If you use this as a base class, you + * need to implement response, fault, and + * error to do something useful. When in those methods, + * you can parse and utilize the mReturnValue member data. + */ +class LLSDRPCResponse : public LLIOPipe +{ +public: + LLSDRPCResponse(); + virtual ~LLSDRPCResponse(); + + /** + * @brief This method extracts the response out of the sd passed in + * + * Any appropriate data found in the sd passed in will be + * extracted and managed by this object - not copied or cloned. It + * will still be up to the caller to delete the pointer passed in. + * @param sd The raw structured data response from the remote server. + * @return Returns true if this was able to parse the structured data. + */ + bool extractResponse(const LLSD& sd); + +protected: + /** + * @brief Method called when the response is ready. + */ + virtual bool response(LLPumpIO* pump) = 0; + + /** + * @brief Method called when a fault is generated by the remote server. + */ + virtual bool fault(LLPumpIO* pump) = 0; + + /** + * @brief Method called when there was an error + */ + virtual bool error(LLPumpIO* pump) = 0; + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data in buffer + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: + LLSD mReturnValue; + bool mIsError; + bool mIsFault; +}; + +/** + * @class LLSDRPCClient + * @brief Client class for a structured data remote procedure call. + * + * This class helps deal with making structured data calls to a remote + * server. You can visualize the calls as: + * + * response = uri.method(parameter) + * + * where you pass in everything to call and this class + * takes care of the rest of the details. + * In typical usage, you will derive a class from this class and + * provide an API more useful for the specific application at + * hand. For example, if you were writing a service to send an instant + * message, you could create an API for it to send the messsage, and + * that class would do the work of translating it into the method and + * parameter, find the destination, and invoke call with + * a useful implementation of LLSDRPCResponse passed in to handle the + * response from the network. + */ +class LLSDRPCClient : public LLIOPipe +{ +public: + LLSDRPCClient(); + virtual ~LLSDRPCClient(); + + /** + * @brief Enumeration for tracking which queue to process the + * response. + */ + enum EPassBackQueue + { + EPBQ_PROCESS, + EPBQ_CALLBACK, + }; + + /** + * @brief Call a method on a remote LLSDRPCServer + * + * @param uri The remote object to call, eg, + * http://localhost/usher. If you are using a factory with a fixed + * url, the uri passed in will probably be ignored. + * @param method The method to call on the remote object + * @param parameter The parameter to pass into the remote + * object. It is up to the caller to delete the value passed in. + * @param response The object which gets the response. + * @param queue Specifies to call the response on the process or + * callback queue. + * @return Returns true if this object will be able to make the RPC call. + */ + bool call( + const std::string& uri, + const std::string& method, + const LLSD& parameter, + LLSDRPCResponse* response, + EPassBackQueue queue); + + /** + * @brief Call a method on a remote LLSDRPCServer + * + * @param uri The remote object to call, eg, + * http://localhost/usher. If you are using a factory with a fixed + * url, the uri passed in will probably be ignored. + * @param method The method to call on the remote object + * @param parameter The seriailized parameter to pass into the + * remote object. + * @param response The object which gets the response. + * @param queue Specifies to call the response on the process or + * callback queue. + * @return Returns true if this object will be able to make the RPC call. + */ + bool call( + const std::string& uri, + const std::string& method, + const std::string& parameter, + LLSDRPCResponse* response, + EPassBackQueue queue); + +protected: + /** + * @brief Enumeration for tracking client state. + */ + enum EState + { + STATE_NONE, + STATE_READY, + STATE_WAITING_FOR_RESPONSE, + STATE_DONE + }; + + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data in buffer + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: + EState mState; + std::string mURI; + std::string mRequest; + EPassBackQueue mQueue; + LLIOPipe::ptr_t mResponse; +}; + +/** + * @class LLSDRPCClientFactory + * @brief Basic implementation for making an SD RPC client factory + * + * This class eases construction of a basic sd rpc client. Here is an + * example of it's use: + * + * class LLUsefulService : public LLService { ... } + * LLService::registerCreator( + * "useful", + * LLService::creator_t(new LLSDRPCClientFactory)) + * + */ +template +class LLSDRPCClientFactory : public LLChainIOFactory +{ +public: + LLSDRPCClientFactory() {} + LLSDRPCClientFactory(const std::string& fixed_url) : mURL(fixed_url) {} + virtual bool build(LLPumpIO::chain_t& chain, LLSD context) const + { + lldebugs << "LLSDRPCClientFactory::build" << llendl; + LLIOPipe::ptr_t service(new Client); + chain.push_back(service); + LLURLRequest* http(new LLURLRequest(LLURLRequest::HTTP_POST)); + LLIOPipe::ptr_t http_pipe(http); + http->addHeader("Content-Type: text/llsd"); + if(mURL.empty()) + { + chain.push_back(LLIOPipe::ptr_t(new LLContextURLExtractor(http))); + } + else + { + http->setURL(mURL); + } + chain.push_back(http_pipe); + chain.push_back(service); + return true; + } +protected: + std::string mURL; +}; + +/** + * @class LLXMLSDRPCClientFactory + * @brief Basic implementation for making an XMLRPC to SD RPC client factory + * + * This class eases construction of a basic sd rpc client which uses + * xmlrpc as a serialization grammar. Here is an example of it's use: + * + * class LLUsefulService : public LLService { ... } + * LLService::registerCreator( + * "useful", + * LLService::creator_t(new LLXMLSDRPCClientFactory)) + * + */ +template +class LLXMLSDRPCClientFactory : public LLChainIOFactory +{ +public: + LLXMLSDRPCClientFactory() {} + LLXMLSDRPCClientFactory(const std::string& fixed_url) : mURL(fixed_url) {} + virtual bool build(LLPumpIO::chain_t& chain, LLSD context) const + { + lldebugs << "LLXMLSDRPCClientFactory::build" << llendl; + LLIOPipe::ptr_t service(new Client); + chain.push_back(service); + LLURLRequest* http(new LLURLRequest(LLURLRequest::HTTP_POST)); + LLIOPipe::ptr_t http_pipe(http); + http->addHeader("Content-Type: text/xml"); + if(mURL.empty()) + { + chain.push_back(LLIOPipe::ptr_t(new LLContextURLExtractor(http))); + } + else + { + http->setURL(mURL); + } + chain.push_back(LLIOPipe::ptr_t(new LLFilterSD2XMLRPCRequest(NULL))); + chain.push_back(http_pipe); + chain.push_back(LLIOPipe::ptr_t(new LLFilterXMLRPCResponse2LLSD)); + chain.push_back(service); + return true; + } +protected: + std::string mURL; +}; + +#endif // LL_LLSDRPCCLIENT_H diff --git a/indra/llmessage/llsdrpcserver.cpp b/indra/llmessage/llsdrpcserver.cpp new file mode 100644 index 0000000000..0348147a71 --- /dev/null +++ b/indra/llmessage/llsdrpcserver.cpp @@ -0,0 +1,322 @@ +/** + * @file llsdrpcserver.cpp + * @author Phoenix + * @date 2005-10-11 + * @brief Implementation of the LLSDRPCServer and related classes. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llsdrpcserver.h" + +#include "llbuffer.h" +#include "llbufferstream.h" +#include "llmemtype.h" +#include "llpumpio.h" +#include "llsdserialize.h" +#include "llstl.h" + +static const char FAULT_PART_1[] = "{'fault':{'code':i"; +static const char FAULT_PART_2[] = ", 'description':'"; +static const char FAULT_PART_3[] = "'}}"; + +static const char RESPONSE_PART_1[] = "{'response':"; +static const char RESPONSE_PART_2[] = "}"; + +static const S32 FAULT_GENERIC = 1000; +static const S32 FAULT_METHOD_NOT_FOUND = 1001; + +static const std::string LLSDRPC_METHOD_SD_NAME("method"); +static const std::string LLSDRPC_PARAMETER_SD_NAME("parameter"); + + +/** + * LLSDRPCServer + */ +LLSDRPCServer::LLSDRPCServer() : + mState(LLSDRPCServer::STATE_NONE), + mPump(NULL), + mLock(0) +{ + LLMemType m1(LLMemType::MTYPE_IO_SD_SERVER); +} + +LLSDRPCServer::~LLSDRPCServer() +{ + LLMemType m1(LLMemType::MTYPE_IO_SD_SERVER); + std::for_each( + mMethods.begin(), + mMethods.end(), + llcompose1( + DeletePointerFunctor(), + llselect2nd())); + std::for_each( + mCallbackMethods.begin(), + mCallbackMethods.end(), + llcompose1( + DeletePointerFunctor(), + llselect2nd())); +} + + +// virtual +ESDRPCSStatus LLSDRPCServer::deferredResponse( + const LLChannelDescriptors& channels, + LLBufferArray* data) { + // subclass should provide a sane implementation + return ESDRPCS_DONE; +} + +void LLSDRPCServer::clearLock() +{ + if(mLock && mPump) + { + mPump->clearLock(mLock); + mPump = NULL; + mLock = 0; + } +} + +// virtual +LLIOPipe::EStatus LLSDRPCServer::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + LLMemType m1(LLMemType::MTYPE_IO_SD_SERVER); +// lldebugs << "LLSDRPCServer::process_impl" << llendl; + // Once we have all the data, We need to read the sd on + // the the in channel, and respond on the out channel + if(!eos) return STATUS_BREAK; + if(!pump || !buffer) return STATUS_PRECONDITION_NOT_MET; + + std::string method_name; + LLIOPipe::EStatus status = STATUS_DONE; + + switch(mState) + { + case STATE_DEFERRED: + PUMP_DEBUG; + if(ESDRPCS_DONE != deferredResponse(channels, buffer.get())) + { + buildFault( + channels, + buffer.get(), + FAULT_GENERIC, + "deferred response failed."); + } + mState = STATE_DONE; + return STATUS_DONE; + + case STATE_DONE: +// lldebugs << "STATE_DONE" << llendl; + break; + case STATE_CALLBACK: +// lldebugs << "STATE_CALLBACK" << llendl; + PUMP_DEBUG; + method_name = mRequest[LLSDRPC_METHOD_SD_NAME].asString(); + if(!method_name.empty() && mRequest.has(LLSDRPC_PARAMETER_SD_NAME)) + { + if(ESDRPCS_DONE != callbackMethod( + method_name, + mRequest[LLSDRPC_PARAMETER_SD_NAME], + channels, + buffer.get())) + { + buildFault( + channels, + buffer.get(), + FAULT_GENERIC, + "Callback method call failed."); + } + } + else + { + // this should never happen, since we should not be in + // this state unless we originally found a method and + // params during the first call to process. + buildFault( + channels, + buffer.get(), + FAULT_GENERIC, + "Invalid LLSDRPC sever state - callback without method."); + } + pump->clearLock(mLock); + mLock = 0; + mState = STATE_DONE; + break; + case STATE_NONE: +// lldebugs << "STATE_NONE" << llendl; + default: + { + // First time we got here - process the SD request, and call + // the method. + PUMP_DEBUG; + LLBufferStream istr(channels, buffer.get()); + mRequest.clear(); + LLSDSerialize::fromNotation(mRequest, istr); + + // { 'method':'...', 'parameter': ... } + method_name = mRequest[LLSDRPC_METHOD_SD_NAME].asString(); + if(!method_name.empty() && mRequest.has(LLSDRPC_PARAMETER_SD_NAME)) + { + ESDRPCSStatus rv = callMethod( + method_name, + mRequest[LLSDRPC_PARAMETER_SD_NAME], + channels, + buffer.get()); + switch(rv) + { + case ESDRPCS_DEFERRED: + mPump = pump; + mLock = pump->setLock(); + mState = STATE_DEFERRED; + status = STATUS_BREAK; + break; + + case ESDRPCS_CALLBACK: + { + mState = STATE_CALLBACK; + LLPumpIO::LLLinkInfo link; + link.mPipe = LLIOPipe::ptr_t(this); + link.mChannels = channels; + LLPumpIO::links_t links; + links.push_back(link); + pump->respond(links, buffer, context); + mLock = pump->setLock(); + status = STATUS_BREAK; + break; + } + case ESDRPCS_DONE: + mState = STATE_DONE; + break; + case ESDRPCS_ERROR: + default: + buildFault( + channels, + buffer.get(), + FAULT_GENERIC, + "Method call failed."); + break; + } + } + else + { + // send a fault + buildFault( + channels, + buffer.get(), + FAULT_GENERIC, + "Unable to find method and parameter in request."); + } + break; + } + } + + PUMP_DEBUG; + return status; +} + +// virtual +ESDRPCSStatus LLSDRPCServer::callMethod( + const std::string& method, + const LLSD& params, + const LLChannelDescriptors& channels, + LLBufferArray* response) +{ + LLMemType m1(LLMemType::MTYPE_IO_SD_SERVER); + // Try to find the method in the method table. + ESDRPCSStatus rv = ESDRPCS_DONE; + method_map_t::iterator it = mMethods.find(method); + if(it != mMethods.end()) + { + rv = (*it).second->call(params, channels, response); + } + else + { + it = mCallbackMethods.find(method); + if(it == mCallbackMethods.end()) + { + // method not found. + std::ostringstream message; + message << "rpc server unable to find method: " << method; + buildFault( + channels, + response, + FAULT_METHOD_NOT_FOUND, + message.str()); + } + else + { + // we found it in the callback methods - tell the process + // to coordinate calling on the pump callback. + return ESDRPCS_CALLBACK; + } + } + return rv; +} + +// virtual +ESDRPCSStatus LLSDRPCServer::callbackMethod( + const std::string& method, + const LLSD& params, + const LLChannelDescriptors& channels, + LLBufferArray* response) +{ + LLMemType m1(LLMemType::MTYPE_IO_SD_SERVER); + // Try to find the method in the callback method table. + ESDRPCSStatus rv = ESDRPCS_DONE; + method_map_t::iterator it = mCallbackMethods.find(method); + if(it != mCallbackMethods.end()) + { + rv = (*it).second->call(params, channels, response); + } + else + { + std::ostringstream message; + message << "pcserver unable to find callback method: " << method; + buildFault( + channels, + response, + FAULT_METHOD_NOT_FOUND, + message.str()); + } + return rv; +} + +// static +void LLSDRPCServer::buildFault( + const LLChannelDescriptors& channels, + LLBufferArray* data, + S32 code, + const std::string& msg) +{ + LLMemType m1(LLMemType::MTYPE_IO_SD_SERVER); + LLBufferStream ostr(channels, data); + ostr << FAULT_PART_1 << code << FAULT_PART_2 << msg << FAULT_PART_3; + llinfos << "LLSDRPCServer::buildFault: " << code << ", " << msg << llendl; +} + +// static +void LLSDRPCServer::buildResponse( + const LLChannelDescriptors& channels, + LLBufferArray* data, + const LLSD& response) +{ + LLMemType m1(LLMemType::MTYPE_IO_SD_SERVER); + LLBufferStream ostr(channels, data); + ostr << RESPONSE_PART_1; + LLSDSerialize::toNotation(response, ostr); + ostr << RESPONSE_PART_2; +#if LL_DEBUG + std::ostringstream debug_ostr; + debug_ostr << "LLSDRPCServer::buildResponse: "; + LLSDSerialize::toNotation(response, debug_ostr); + llinfos << debug_ostr.str() << llendl; +#endif +} diff --git a/indra/llmessage/llsdrpcserver.h b/indra/llmessage/llsdrpcserver.h new file mode 100644 index 0000000000..abb291d007 --- /dev/null +++ b/indra/llmessage/llsdrpcserver.h @@ -0,0 +1,342 @@ +/** + * @file llsdrpcserver.h + * @author Phoenix + * @date 2005-10-11 + * @brief Declaration of the structured data remote procedure call server. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSDRPCSERVER_H +#define LL_LLSDRPCSERVER_H + +/** + * I've set this up to be pretty easy to use when you want to make a + * structured data rpc server which responds to methods by + * name. Derive a class from the LLSDRPCServer, and during + * construction (or initialization if you have the luxury) map method + * names to pointers to member functions. This will look a lot like: + * + * + * class LLMessageAgents : public LLSDRPCServer {
+ * public:
+ * typedef LLSDRPCServer mem_fn_t;
+ * LLMessageAgents() {
+ * mMethods["message"] = new mem_fn_t(this, &LLMessageAgents::rpc_IM);
+ * mMethods["alert"] = new mem_fn_t(this, &LLMessageAgents::rpc_Alrt);
+ * }
+ * protected:
+ * rpc_IM(const LLSD& params, + * const LLChannelDescriptors& channels, + * LLBufferArray* data) + * {...}
+ * rpc_Alert(const LLSD& params, + * const LLChannelDescriptors& channels, + * LLBufferArray* data) + * {...}
+ * };
+ *
+ * + * The params are an array where each element in the array is a single + * parameter in the call. + * + * It is up to you to pack a valid serialized llsd response into the + * data object passed into the method, but you can use the helper + * methods below to help. + */ + +#include +#include "lliopipe.h" +#include "lliohttpserver.h" +#include "llfiltersd2xmlrpc.h" + +class LLSD; + +/** + * @brief Enumeration for specifying server method call status. This + * enumeration controls how the server class will manage the pump + * process/callback mechanism. + */ +enum ESDRPCSStatus +{ + // The call went ok, but the response is not yet ready. The + // method will arrange for the clearLock() call to be made at + // a later date, after which, once the chain is being pumped + // again, deferredResponse() will be called to gather the result + ESDRPCS_DEFERRED, + + // The LLSDRPCServer would like to handle the method on the + // callback queue of the pump. + ESDRPCS_CALLBACK, + + // The method call finished and generated output. + ESDRPCS_DONE, + + // Method failed for some unspecified reason - you should avoid + // this. A generic fault will be sent to the output. + ESDRPCS_ERROR, + + ESDRPCS_COUNT, +}; + +/** + * @class LLSDRPCMethodCallBase + * @brief Base class for calling a member function in an sd rpcserver + * implementation. + */ +class LLSDRPCMethodCallBase +{ +public: + LLSDRPCMethodCallBase() {} + virtual ~LLSDRPCMethodCallBase() {} + + virtual ESDRPCSStatus call( + const LLSD& params, + const LLChannelDescriptors& channels, + LLBufferArray* response) = 0; +protected: +}; + +/** + * @class LLSDRPCMethodCall + * @brief Class which implements member function calls. + */ +template +class LLSDRPCMethodCall : public LLSDRPCMethodCallBase +{ +public: + typedef ESDRPCSStatus (Server::*mem_fn)( + const LLSD& params, + const LLChannelDescriptors& channels, + LLBufferArray* data); + LLSDRPCMethodCall(Server* s, mem_fn fn) : + mServer(s), + mMemFn(fn) + { + } + virtual ~LLSDRPCMethodCall() {} + virtual ESDRPCSStatus call( + const LLSD& params, + const LLChannelDescriptors& channels, + LLBufferArray* data) + { + return (*mServer.*mMemFn)(params, channels, data); + } + +protected: + Server* mServer; + mem_fn mMemFn; + //bool (Server::*mMemFn)(const LLSD& params, LLBufferArray& data); +}; + + +/** + * @class LLSDRPCServer + * @brief Basic implementation of a structure data rpc server + * + * The rpc server is also designed to appropriately straddle the pump + * process() and callback() to specify which + * thread you want to work on when handling a method call. The + * mMethods methods are called from + * process(), while the mCallbackMethods are + * called when a pump is in a callback() cycle. + */ +class LLSDRPCServer : public LLIOPipe +{ +public: + LLSDRPCServer(); + virtual ~LLSDRPCServer(); + + /** + * enumeration for generic fault codes + */ + enum + { + FAULT_BAD_REQUEST = 2000, + FAULT_NO_RESPONSE = 2001, + }; + + /** + * @brief Call this method to return an rpc fault. + * + * @param channel The channel for output on the data buffer + * @param data buffer which will recieve the final output + * @param code The fault code + * @param msg The fault message + */ + static void buildFault( + const LLChannelDescriptors& channels, + LLBufferArray* data, + S32 code, + const std::string& msg); + + /** + * @brief Call this method to build an rpc response. + * + * @param channel The channel for output on the data buffer + * @param data buffer which will recieve the final output + * @param response The return value from the method call + */ + static void buildResponse( + const LLChannelDescriptors& channels, + LLBufferArray* data, + const LLSD& response); + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data in buffer + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: + + /** + * @brief Enumeration to track the state of the rpc server instance + */ + enum EState + { + STATE_NONE, + STATE_CALLBACK, + STATE_DEFERRED, + STATE_DONE + }; + + /** + * @brief This method is called when an http post comes in. + * + * The default behavior is to look at the method name, look up the + * method in the method table, and call it. If the method is not + * found, this function will build a fault response. You can + * implement your own version of this function if you want to hard + * wire some behavior or optimize things a bit. + * @param method The method name being called + * @param params The parameters + * @param channel The channel for output on the data buffer + * @param data The http data + * @return Returns the status of the method call, done/deferred/etc + */ + virtual ESDRPCSStatus callMethod( + const std::string& method, + const LLSD& params, + const LLChannelDescriptors& channels, + LLBufferArray* data); + + /** + * @brief This method is called when a pump callback is processed. + * + * The default behavior is to look at the method name, look up the + * method in the callback method table, and call it. If the method + * is not found, this function will build a fault response. You + * can implement your own version of this function if you want to + * hard wire some behavior or optimize things a bit. + * @param method The method name being called + * @param params The parameters + * @param channel The channel for output on the data buffer + * @param data The http data + * @return Returns the status of the method call, done/deferred/etc + */ + virtual ESDRPCSStatus callbackMethod( + const std::string& method, + const LLSD& params, + const LLChannelDescriptors& channels, + LLBufferArray* data); + + /** + * @brief Called after a deferred service is unlocked + * + * If a method returns ESDRPCS_DEFERRED, then the service chain + * will be locked and not processed until some other system calls + * clearLock() on the service instance again. At that point, + * once the pump starts processing the chain again, this method + * will be called so the service can output the final result + * into the buffers. + */ + virtual ESDRPCSStatus deferredResponse( + const LLChannelDescriptors& channels, + LLBufferArray* data); + + // donovan put this public here 7/27/06 +public: + /** + * @brief unlock a service that as ESDRPCS_DEFERRED + */ + void clearLock(); + +protected: + EState mState; + LLSD mRequest; + LLPumpIO* mPump; + S32 mLock; + typedef std::map method_map_t; + method_map_t mMethods; + method_map_t mCallbackMethods; +}; + +/** + * @name Helper Templates for making LLHTTPNodes + * + * These templates help in creating nodes for handing a service from + * either SDRPC or XMLRPC, given a single implementation of LLSDRPCServer. + * + * To use it: + * \code + * class LLUsefulServer : public LLSDRPCServer { ... } + * + * LLHTTPNode& root = LLCreateHTTPWireServer(...); + * root.addNode("llsdrpc/useful", new LLSDRPCNode); + * root.addNode("xmlrpc/useful", new LLXMLRPCNode); + * \endcode + */ +//@{ + +template +class LLSDRPCServerFactory : public LLChainIOFactory +{ +public: + virtual bool build(LLPumpIO::chain_t& chain, LLSD context) const + { + lldebugs << "LLXMLSDRPCServerFactory::build" << llendl; + chain.push_back(LLIOPipe::ptr_t(new Server)); + return true; + } +}; + +template +class LLSDRPCNode : public LLHTTPNodeForFactory< + LLSDRPCServerFactory > +{ +}; + +template +class LLXMLRPCServerFactory : public LLChainIOFactory +{ +public: + virtual bool build(LLPumpIO::chain_t& chain, LLSD context) const + { + lldebugs << "LLXMLSDRPCServerFactory::build" << llendl; + chain.push_back(LLIOPipe::ptr_t(new LLFilterXMLRPCRequest2LLSD)); + chain.push_back(LLIOPipe::ptr_t(new Server)); + chain.push_back(LLIOPipe::ptr_t(new LLFilterSD2XMLRPCResponse)); + return true; + } +}; + +template +class LLXMLRPCNode : public LLHTTPNodeForFactory< + LLXMLRPCServerFactory > +{ +}; + +//@} + +#endif // LL_LLSDRPCSERVER_H diff --git a/indra/llmessage/llservice.cpp b/indra/llmessage/llservice.cpp new file mode 100644 index 0000000000..03b04054dc --- /dev/null +++ b/indra/llmessage/llservice.cpp @@ -0,0 +1,93 @@ +/** + * @file llservice.cpp + * @author Phoenix + * @date 2005-04-20 + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llservice.h" + +LLService::creators_t LLService::sCreatorFunctors; + +LLService::LLService() +{ +} + +LLService::~LLService() +{ +} + +// static +bool LLService::registerCreator(const std::string& name, creator_t fn) +{ + llinfos << "LLService::registerCreator(" << name << ")" << llendl; + if(name.empty()) + { + return false; + } + + creators_t::value_type vt(name, fn); + std::pair rv = sCreatorFunctors.insert(vt); + return rv.second; + + // alternately... + //std::string name_str(name); + //sCreatorFunctors[name_str] = fn; +} + +// static +LLIOPipe* LLService::activate( + const std::string& name, + LLPumpIO::chain_t& chain, + LLSD context) +{ + if(name.empty()) + { + llinfos << "LLService::activate - no service specified." << llendl; + return NULL; + } + creators_t::iterator it = sCreatorFunctors.find(name); + LLIOPipe* rv = NULL; + if(it != sCreatorFunctors.end()) + { + if((*it).second->build(chain, context)) + { + rv = chain[0].get(); + } + else + { + // empty out the chain, because failed service creation + // should just discard this stuff. + llwarns << "LLService::activate - unable to build chain: " << name + << llendl; + chain.clear(); + } + } + else + { + llwarns << "LLService::activate - unable find factory: " << name + << llendl; + } + return rv; +} + +// static +bool LLService::discard(const std::string& name) +{ + if(name.empty()) + { + return false; + } + creators_t::iterator it = sCreatorFunctors.find(name); + if(it != sCreatorFunctors.end()) + { + //(*it).second->discard(); + sCreatorFunctors.erase(it); + return true; + } + return false; +} + diff --git a/indra/llmessage/llservice.h b/indra/llmessage/llservice.h new file mode 100644 index 0000000000..e243e710d6 --- /dev/null +++ b/indra/llmessage/llservice.h @@ -0,0 +1,167 @@ +/** + * @file llservice.h + * @author Phoenix + * @date 2004-11-21 + * @brief Declaration file for LLService and related classes. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSERVICE_H +#define LL_LLSERVICE_H + +#include +#include +//#include +//#include + +//#include "llframetimer.h" +#include "lliopipe.h" +#include "llchainio.h" + +#if 0 +class LLServiceCreator; +/** + * intrusive pointer support + */ +namespace boost +{ + void intrusive_ptr_add_ref(LLServiceCreator* p); + void intrusive_ptr_release(LLServiceCreator* p); +}; +#endif + +/** + * @class LLServiceCreator + * @brief This class is an abstract base class for classes which create + * new LLService instances. + * + * Derive classes from this class which appropriately implement the + * operator() and destructor. + * @see LLService + */ +#if 0 +class LLServiceCreator +{ +public: + typedef boost::intrusive_ptr service_t; + virtual ~LLServiceCreator() {} + virtual service_t activate() = 0; + virtual void discard() = 0; + +protected: + LLServiceCreator() : mReferenceCount(0) + { + } + +private: + friend void boost::intrusive_ptr_add_ref(LLServiceCreator* p); + friend void boost::intrusive_ptr_release(LLServiceCreator* p); + U32 mReferenceCount; +}; +#endif + +#if 0 +namespace boost +{ + inline void intrusive_ptr_add_ref(LLServiceCreator* p) + { + ++p->mReferenceCount; + } + inline void intrusive_ptr_release(LLServiceCreator* p) + { + if(0 == --p->mReferenceCount) + { + delete p; + } + } +}; +#endif + +/** + * @class LLService + * @brief This class is the base class for the service classes. + * @see LLIOPipe + * + * The services map a string to a chain factory with a known interface + * at the front of the chain. So, to activate a service, call + * activate() with the name of the service needed which + * will call the associated factory, and return a pointer to the + * known interface. + * NOTE: If you are implementing a service factory, it is + * vitally important that the service pipe is at the front of the + * chain. + */ +class LLService : public LLIOPipe +{ +public: + //typedef boost::intrusive_ptr creator_t; + //typedef boost::intrusive_ptr service_t; + typedef boost::shared_ptr creator_t; + + /** + * @brief This method is used to register a protocol name with a + * a functor that creates the service. + * + * THOROUGH_DESCRIPTION + * @param aParameter A brief description of aParameter. + * @return Returns true if a service was successfully registered. + */ + static bool registerCreator(const std::string& name, creator_t fn); + + /** + * @brief This method connects to a service by name. + * + * @param name The name of the service to connect to. + * @param chain The constructed chain including the service instance. + * @param context Context for the activation. + * @return An instance of the service for use or NULL on failure. + */ + static LLIOPipe* activate( + const std::string& name, + LLPumpIO::chain_t& chain, + LLSD context); + + /** + * @brief + * + * @param name The name of the service to discard. + * @return true if service creator was found and discarded. + */ + static bool discard(const std::string& name); + +protected: + // The creation factory static data. + typedef std::map creators_t; + static creators_t sCreatorFunctors; + +protected: + // construction & destruction. since this class is an abstract + // base class, it is up to Service implementations to actually + // deal with construction and not a public method. How that + // construction takes place will be handled by the service + // creators. + LLService(); + virtual ~LLService(); + +protected: + // This frame timer records how long this service has + // existed. Useful for derived services to give themselves a + // lifetime and expiration. + // *NOTE: Phoenix - This functionaity has been moved to the + // pump. 2005-12-13 + //LLFrameTimer mTimer; + + // Since services are designed in an 'ask now, respond later' + // idiom which probably crosses thread boundaries, almost all + // services will need a handle to a response pipe. It will usually + // be the job of the service author to derive a useful + // implementation of response, and up to the service subscriber to + // further derive that class to do something useful when the + // response comes in. + LLIOPipe::ptr_t mResponse; +}; + + +#endif // LL_LLSERVICE_H diff --git a/indra/llmessage/lltaskname.h b/indra/llmessage/lltaskname.h new file mode 100644 index 0000000000..2a5a823e08 --- /dev/null +++ b/indra/llmessage/lltaskname.h @@ -0,0 +1,42 @@ +/** + * @file lltaskname.h + * @brief This contains the current list of valid tasks and is inluded + * into both simulator and viewer + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTASKNAME_H +#define LL_LLTASKNAME_H + +// Current valid tasks +// If you add a taskname here you will have to +// 1) Add an initializer to init_object() in llscript.cpp +// 1.1) Add to object_type_to_task_name() in llregion.cpp +// 2) Add display code to LLStupidObject::render2(LLAgent* agentp) in llstupidobject.cpp +// 3) Add any additional code to support new opcodes you create + +typedef enum e_lltask_name +{ + LLTASK_NULL = 0, // Not a valid task + LLTASK_AGENT = 1, // The player's agent in Linden World + LLTASK_CHILD_AGENT = 2, // Child agents sent to adjacent regions +// LLTASK_BASIC_SHOT, // Simple shot that moves in a straight line +// LLTASK_BIG_SHOT, // Big shot that uses gravity + LLTASK_TREE = 5, // A tree +// LLTASK_BIRD, // a bird +// LLTASK_ATOR, // a predator +// LLTASK_SMOKE, // Smoke poof +// LLTASK_SPARK, // Little spark +// LLTASK_ROCK, // Rock + LLTASK_GRASS = 11, // Grass + LLTASK_PSYS = 12, // particle system test example +// LLTASK_ORACLE, +// LLTASK_DEMON, // Maxwell's demon +// LLTASK_LSL_TEST, // Linden Scripting Language Test Object + LLTASK_PRIMITIVE = 16, +// LLTASK_GHOST = 17, // a ghost (Boo!) + LLTASK_TREE_NEW = 18 +} ELLTaskName; +#endif diff --git a/indra/llmessage/llteleportflags.h b/indra/llmessage/llteleportflags.h new file mode 100644 index 0000000000..001d05d109 --- /dev/null +++ b/indra/llmessage/llteleportflags.h @@ -0,0 +1,43 @@ +/** + * @file llteleportflags.h + * @brief Teleport flags + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTELEPORTFLAGS_H +#define LL_LLTELEPORTFLAGS_H + +const U32 TELEPORT_FLAGS_DEFAULT = 0; +const U32 TELEPORT_FLAGS_SET_HOME_TO_TARGET = 1 << 0; // newbie leaving prelude +const U32 TELEPORT_FLAGS_SET_LAST_TO_TARGET = 1 << 1; +const U32 TELEPORT_FLAGS_VIA_LURE = 1 << 2; +const U32 TELEPORT_FLAGS_VIA_LANDMARK = 1 << 3; +const U32 TELEPORT_FLAGS_VIA_LOCATION = 1 << 4; +const U32 TELEPORT_FLAGS_VIA_HOME = 1 << 5; +const U32 TELEPORT_FLAGS_VIA_TELEHUB = 1 << 6; +const U32 TELEPORT_FLAGS_VIA_LOGIN = 1 << 7; +const U32 TELEPORT_FLAGS_VIA_GODLIKE_LURE = 1 << 8; +const U32 TELEPORT_FLAGS_GODLIKE = 1 << 9; +const U32 TELEPORT_FLAGS_911 = 1 << 10; +const U32 TELEPORT_FLAGS_DISABLE_CANCEL = 1 << 11; // Used for llTeleportAgentHome() +const U32 TELEPORT_FLAGS_VIA_REGION_ID = 1 << 12; +const U32 TELEPORT_FLAGS_IS_FLYING = 1 << 13; + +const U32 TELEPORT_FLAGS_MASK_VIA = TELEPORT_FLAGS_VIA_LURE + | TELEPORT_FLAGS_VIA_LANDMARK + | TELEPORT_FLAGS_VIA_LOCATION + | TELEPORT_FLAGS_VIA_HOME + | TELEPORT_FLAGS_VIA_TELEHUB + | TELEPORT_FLAGS_VIA_LOGIN + | TELEPORT_FLAGS_VIA_REGION_ID; + + + + +const U32 LURE_FLAG_NORMAL_LURE = 1 << 0; +const U32 LURE_FLAG_GODLIKE_LURE = 1 << 1; +const U32 LURE_FLAG_GODLIKE_PURSUIT = 1 << 2; + +#endif diff --git a/indra/llmessage/llthrottle.cpp b/indra/llmessage/llthrottle.cpp new file mode 100644 index 0000000000..01e83ca5cd --- /dev/null +++ b/indra/llmessage/llthrottle.cpp @@ -0,0 +1,542 @@ +/** + * @file llthrottle.cpp + * @brief LLThrottle class used for network bandwidth control. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llthrottle.h" +#include "llmath.h" +#include "lldatapacker.h" +#include "message.h" + + +LLThrottle::LLThrottle(const F32 rate) +{ + mRate = rate; + mAvailable = 0.f; + mLookaheadSecs = 0.25f; + mLastSendTime = LLMessageSystem::getMessageTimeSeconds(TRUE); +} + + +void LLThrottle::setRate(const F32 rate) +{ + // Need to accumulate available bits when adjusting the rate. + mAvailable = getAvailable(); + mLastSendTime = LLMessageSystem::getMessageTimeSeconds(); + mRate = rate; +} + +F32 LLThrottle::getAvailable() +{ + // use a temporary bits_available + // since we don't want to change mBitsAvailable every time + F32 elapsed_time = (F32)(LLMessageSystem::getMessageTimeSeconds() - mLastSendTime); + return mAvailable + (mRate * elapsed_time); +} + +BOOL LLThrottle::checkOverflow(const F32 amount) +{ + BOOL retval = TRUE; + + F32 lookahead_amount = mRate * mLookaheadSecs; + + // use a temporary bits_available + // since we don't want to change mBitsAvailable every time + F32 elapsed_time = (F32)(LLMessageSystem::getMessageTimeSeconds() - mLastSendTime); + F32 amount_available = mAvailable + (mRate * elapsed_time); + + if ((amount_available >= lookahead_amount) || (amount_available > amount)) + { + // ...enough space to send this message + // Also do if > lookahead so we can use if amount > capped amount. + retval = FALSE; + } + + return retval; +} + +BOOL LLThrottle::throttleOverflow(const F32 amount) +{ + F32 elapsed_time; + F32 lookahead_amount; + BOOL retval = TRUE; + + lookahead_amount = mRate * mLookaheadSecs; + + F64 mt_sec = LLMessageSystem::getMessageTimeSeconds(); + elapsed_time = (F32)(mt_sec - mLastSendTime); + mLastSendTime = mt_sec; + + mAvailable += mRate * elapsed_time; + + if (mAvailable >= lookahead_amount) + { + // ...channel completely open, so allow send regardless + // of size. This allows sends on very low BPS channels. + mAvailable = lookahead_amount; + retval = FALSE; + } + else if (mAvailable > amount) + { + // ...enough space to send this message + retval = FALSE; + } + + // We actually already sent the bits. + mAvailable -= amount; + + // What if bitsavailable goes negative? + // That's OK, because it means someone is banging on the channel, + // so we need some time to recover. + + return retval; +} + + + +const F32 THROTTLE_LOOKAHEAD_TIME = 1.f; // seconds + +// Make sure that we don't set above these +// values, even if the client asks to be set +// higher +// Note that these values are replicated on the +// client side to set max bandwidth throttling there, +// in llviewerthrottle.cpp. These values are the sum +// of the top two tiers of bandwidth there. + +F32 gThrottleMaximumBPS[TC_EOF] = +{ + 150000.f, // TC_RESEND + 170000.f, // TC_LAND + 34000.f, // TC_WIND + 34000.f, // TC_CLOUD + 446000.f, // TC_TASK + 446000.f, // TC_TEXTURE + 220000.f, // TC_ASSET +}; + +// Start low until viewer informs us of capability +// Asset and resend get high values, since they +// aren't used JUST by the viewer necessarily. +// This is a HACK and should be dealt with more properly on +// circuit creation. + +F32 gThrottleDefaultBPS[TC_EOF] = +{ + 100000.f, // TC_RESEND + 4000.f, // TC_LAND + 4000.f, // TC_WIND + 4000.f, // TC_CLOUD + 4000.f, // TC_TASK + 4000.f, // TC_TEXTURE + 100000.f, // TC_ASSET +}; + +// Don't throttle down lower than this +// This potentially wastes 50 kbps, but usually +// wont. +F32 gThrottleMinimumBPS[TC_EOF] = +{ + 10000.f, // TC_RESEND + 10000.f, // TC_LAND + 4000.f, // TC_WIND + 4000.f, // TC_CLOUD + 20000.f, // TC_TASK + 10000.f, // TC_TEXTURE + 10000.f, // TC_ASSET +}; + +const char* THROTTLE_NAMES[TC_EOF] = +{ + "Resend ", + "Land ", + "Wind ", + "Cloud ", + "Task ", + "Texture", + "Asset " +}; + +LLThrottleGroup::LLThrottleGroup() +{ + S32 i; + for (i = 0; i < TC_EOF; i++) + { + mThrottleTotal[i] = gThrottleDefaultBPS[i]; + mNominalBPS[i] = gThrottleDefaultBPS[i]; + } + + resetDynamicAdjust(); +} + +void LLThrottleGroup::packThrottle(LLDataPacker &dp) const +{ + S32 i; + for (i = 0; i < TC_EOF; i++) + { + dp.packF32(mThrottleTotal[i], "Throttle"); + } +} + +void LLThrottleGroup::unpackThrottle(LLDataPacker &dp) +{ + S32 i; + for (i = 0; i < TC_EOF; i++) + { + F32 temp_throttle; + dp.unpackF32(temp_throttle, "Throttle"); + temp_throttle = llclamp(temp_throttle, 0.f, 2250000.f); + mThrottleTotal[i] = temp_throttle; + if(mThrottleTotal[i] > gThrottleMaximumBPS[i]) + { + mThrottleTotal[i] = gThrottleMaximumBPS[i]; + } + } +} + +// Call this whenever mNominalBPS changes. Need to reset +// the measurement systems. In the future, we should look +// into NOT resetting the system. +void LLThrottleGroup::resetDynamicAdjust() +{ + F64 mt_sec = LLMessageSystem::getMessageTimeSeconds(); + S32 i; + for (i = 0; i < TC_EOF; i++) + { + mCurrentBPS[i] = mNominalBPS[i]; + mBitsAvailable[i] = mNominalBPS[i] * THROTTLE_LOOKAHEAD_TIME; + mLastSendTime[i] = mt_sec; + mBitsSentThisPeriod[i] = 0; + mBitsSentHistory[i] = 0; + } + mDynamicAdjustTime = mt_sec; +} + + +BOOL LLThrottleGroup::setNominalBPS(F32* throttle_vec) +{ + BOOL changed = FALSE; + S32 i; + for (i = 0; i < TC_EOF; i++) + { + if (mNominalBPS[i] != throttle_vec[i]) + { + changed = TRUE; + mNominalBPS[i] = throttle_vec[i]; + } + } + + // If we changed the nominal settings, reset the dynamic + // adjustment subsystem. + if (changed) + { + resetDynamicAdjust(); + } + + return changed; +} + + +BOOL LLThrottleGroup::checkOverflow(S32 throttle_cat, F32 bits) +{ + BOOL retval = TRUE; + + F32 category_bps = mCurrentBPS[throttle_cat]; + F32 lookahead_bits = category_bps * THROTTLE_LOOKAHEAD_TIME; + + // use a temporary bits_available + // since we don't want to change mBitsAvailable every time + F32 elapsed_time = (F32)(LLMessageSystem::getMessageTimeSeconds() - mLastSendTime[throttle_cat]); + F32 bits_available = mBitsAvailable[throttle_cat] + (category_bps * elapsed_time); + + if (bits_available >= lookahead_bits) + { + // ...channel completely open, so allow send regardless + // of size. This allows sends on very low BPS channels. + mBitsAvailable[throttle_cat] = lookahead_bits; + retval = FALSE; + } + else if ( bits_available > bits ) + { + // ...enough space to send this message + retval = FALSE; + } + + return retval; +} + +BOOL LLThrottleGroup::throttleOverflow(S32 throttle_cat, F32 bits) +{ + F32 elapsed_time; + F32 category_bps; + F32 lookahead_bits; + BOOL retval = TRUE; + + category_bps = mCurrentBPS[throttle_cat]; + lookahead_bits = category_bps * THROTTLE_LOOKAHEAD_TIME; + + F64 mt_sec = LLMessageSystem::getMessageTimeSeconds(); + elapsed_time = (F32)(mt_sec - mLastSendTime[throttle_cat]); + mLastSendTime[throttle_cat] = mt_sec; + mBitsAvailable[throttle_cat] += category_bps * elapsed_time; + + if (mBitsAvailable[throttle_cat] >= lookahead_bits) + { + // ...channel completely open, so allow send regardless + // of size. This allows sends on very low BPS channels. + mBitsAvailable[throttle_cat] = lookahead_bits; + retval = FALSE; + } + else if ( mBitsAvailable[throttle_cat] > bits ) + { + // ...enough space to send this message + retval = FALSE; + } + + // We actually already sent the bits. + mBitsAvailable[throttle_cat] -= bits; + + mBitsSentThisPeriod[throttle_cat] += bits; + + // What if bitsavailable goes negative? + // That's OK, because it means someone is banging on the channel, + // so we need some time to recover. + + return retval; +} + + +BOOL LLThrottleGroup::dynamicAdjust() +{ + const F32 DYNAMIC_ADJUST_TIME = 1.0f; // seconds + const F32 CURRENT_PERIOD_WEIGHT = .25f; // how much weight to give to last period while determining BPS utilization + const F32 BUSY_PERCENT = 0.75f; // if use more than this fraction of BPS, you are busy + const F32 IDLE_PERCENT = 0.70f; // if use less than this fraction, you are "idle" + const F32 TRANSFER_PERCENT = 0.90f; // how much unused bandwidth to take away each adjustment + const F32 RECOVER_PERCENT = 0.25f; // how much to give back during recovery phase + + S32 i; + + F64 mt_sec = LLMessageSystem::getMessageTimeSeconds(); + + // Only dynamically adjust every few seconds + if ((mt_sec - mDynamicAdjustTime) < DYNAMIC_ADJUST_TIME) + { + return FALSE; + } + mDynamicAdjustTime = mt_sec; + + S32 total = 0; + // Update historical information + for (i = 0; i < TC_EOF; i++) + { + if (mBitsSentHistory[i] == 0) + { + // first run, just copy current period + mBitsSentHistory[i] = mBitsSentThisPeriod[i]; + } + else + { + // have some history, so weight accordingly + mBitsSentHistory[i] = (1.f - CURRENT_PERIOD_WEIGHT) * mBitsSentHistory[i] + + CURRENT_PERIOD_WEIGHT * mBitsSentThisPeriod[i]; + } + + mBitsSentThisPeriod[i] = 0; + total += llround(mBitsSentHistory[i]); + } + + // Look for busy channels + // TODO: Fold into loop above. + BOOL channels_busy = FALSE; + F32 busy_nominal_sum = 0; + BOOL channel_busy[TC_EOF]; + BOOL channel_idle[TC_EOF]; + BOOL channel_over_nominal[TC_EOF]; + + for (i = 0; i < TC_EOF; i++) + { + // Is this a busy channel? + if (mBitsSentHistory[i] >= BUSY_PERCENT * DYNAMIC_ADJUST_TIME * mCurrentBPS[i]) + { + // this channel is busy + channels_busy = TRUE; + busy_nominal_sum += mNominalBPS[i]; // use for allocation of pooled idle bandwidth + channel_busy[i] = TRUE; + } + else + { + channel_busy[i] = FALSE; + } + + // Is this an idle channel? + if ((mBitsSentHistory[i] < IDLE_PERCENT * DYNAMIC_ADJUST_TIME * mCurrentBPS[i]) && + (mBitsAvailable[i] > 0)) + { + channel_idle[i] = TRUE; + } + else + { + channel_idle[i] = FALSE; + } + + // Is this an overpumped channel? + if (mCurrentBPS[i] > mNominalBPS[i]) + { + channel_over_nominal[i] = TRUE; + } + else + { + channel_over_nominal[i] = FALSE; + } + + //if (total) + //{ + // llinfos << i << ": B" << channel_busy[i] << " I" << channel_idle[i] << " N" << channel_over_nominal[i]; + // llcont << " Nom: " << mNominalBPS[i] << " Cur: " << mCurrentBPS[i] << " BS: " << mBitsSentHistory[i] << llendl; + //} + } + + if (channels_busy) + { + // Some channels are busy. Let's see if we can get them some bandwidth. + F32 used_bps; + F32 avail_bps; + F32 transfer_bps; + + F32 pool_bps = 0; + + for (i = 0; i < TC_EOF; i++) + { + if (channel_idle[i] || channel_over_nominal[i] ) + { + // Either channel i is idle, or has been overpumped. + // Therefore it's a candidate to give up some bandwidth. + // Figure out how much bandwidth it has been using, and how + // much is available to steal. + used_bps = mBitsSentHistory[i] / DYNAMIC_ADJUST_TIME; + + // CRO make sure to keep a minimum amount of throttle available + // CRO NB: channels set to < MINIMUM_BPS will never give up bps, + // which is correct I think + if (used_bps < gThrottleMinimumBPS[i]) + { + used_bps = gThrottleMinimumBPS[i]; + } + + if (channel_over_nominal[i]) + { + F32 unused_current = mCurrentBPS[i] - used_bps; + avail_bps = llmax(mCurrentBPS[i] - mNominalBPS[i], unused_current); + } + else + { + avail_bps = mCurrentBPS[i] - used_bps; + } + + //llinfos << i << " avail " << avail_bps << llendl; + + // Historically, a channel could have used more than its current share, + // even if it's idle right now. + // Make sure we don't steal too much. + if (avail_bps < 0) + { + continue; + } + + // Transfer some bandwidth from this channel into the global pool. + transfer_bps = avail_bps * TRANSFER_PERCENT; + mCurrentBPS[i] -= transfer_bps; + pool_bps += transfer_bps; + } + } + + //llinfos << "Pool BPS: " << pool_bps << llendl; + // Now redistribute the bandwidth to busy channels. + F32 unused_bps = 0.f; + + for (i = 0; i < TC_EOF; i++) + { + if (channel_busy[i]) + { + F32 add_amount = pool_bps * (mNominalBPS[i] / busy_nominal_sum); + //llinfos << "Busy " << i << " gets " << pool_bps << llendl; + mCurrentBPS[i] += add_amount; + + // CRO: make sure this doesn't get too huge + // JC - Actually, need to let mCurrentBPS go less than nominal, otherwise + // you aren't allowing bandwidth to actually be moved from one channel + // to another. + // FIXME: If clamping high end, would be good to re- + // allocate to other channels in the above code. + const F32 MAX_BPS = 4 * mNominalBPS[i]; + if (mCurrentBPS[i] > MAX_BPS) + { + F32 overage = mCurrentBPS[i] - MAX_BPS; + mCurrentBPS[i] -= overage; + unused_bps += overage; + } + + // Paranoia + if (mCurrentBPS[i] < gThrottleMinimumBPS[i]) + { + mCurrentBPS[i] = gThrottleMinimumBPS[i]; + } + } + } + + // For fun, add the overage back in to objects + if (unused_bps > 0.f) + { + mCurrentBPS[TC_TASK] += unused_bps; + } + } + else + { + // No one is busy. + // Make the channel allocations seek toward nominal. + + // Look for overpumped channels + F32 starved_nominal_sum = 0; + F32 avail_bps = 0; + F32 transfer_bps = 0; + F32 pool_bps = 0; + for (i = 0; i < TC_EOF; i++) + { + if (mCurrentBPS[i] > mNominalBPS[i]) + { + avail_bps = (mCurrentBPS[i] - mNominalBPS[i]); + transfer_bps = avail_bps * RECOVER_PERCENT; + + mCurrentBPS[i] -= transfer_bps; + pool_bps += transfer_bps; + } + } + + // Evenly distribute bandwidth to channels currently + // using less than nominal. + for (i = 0; i < TC_EOF; i++) + { + if (mCurrentBPS[i] < mNominalBPS[i]) + { + // We're going to weight allocations by nominal BPS. + starved_nominal_sum += mNominalBPS[i]; + } + } + + for (i = 0; i < TC_EOF; i++) + { + if (mCurrentBPS[i] < mNominalBPS[i]) + { + // Distribute bandwidth according to nominal allocation ratios. + mCurrentBPS[i] += pool_bps * (mNominalBPS[i] / starved_nominal_sum); + } + } + } + return TRUE; +} diff --git a/indra/llmessage/llthrottle.h b/indra/llmessage/llthrottle.h new file mode 100644 index 0000000000..1ee772e687 --- /dev/null +++ b/indra/llmessage/llthrottle.h @@ -0,0 +1,81 @@ +/** + * @file llthrottle.h + * @brief LLThrottle class used for network bandwidth control + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTHROTTLE_H +#define LL_LLTHROTTLE_H + +#include "lltimer.h" + +const S32 MAX_THROTTLE_SIZE = 32; + +class LLDataPacker; + +// Single instance of a generic throttle +class LLThrottle +{ +public: + LLThrottle(const F32 throttle = 1.f); + ~LLThrottle() { } + + void setRate(const F32 rate); + BOOL checkOverflow(const F32 amount); // I'm about to add an amount, TRUE if would overflow throttle + BOOL throttleOverflow(const F32 amount); // I just sent amount, TRUE if that overflowed the throttle + + F32 getAvailable(); // Return the available bits + F32 getRate() const { return mRate; } +private: + F32 mLookaheadSecs; // Seconds to look ahead, maximum + F32 mRate; // BPS available, dynamically adjusted + F32 mAvailable; // Bits available to send right now on each channel + F64 mLastSendTime; // Time since last send on this channel +}; + +typedef enum e_throttle_categories +{ + TC_RESEND, + TC_LAND, + TC_WIND, + TC_CLOUD, + TC_TASK, + TC_TEXTURE, + TC_ASSET, + TC_EOF +} EThrottleCats; + + +class LLThrottleGroup +{ +public: + LLThrottleGroup(); + ~LLThrottleGroup() { } + + void resetDynamicAdjust(); + BOOL checkOverflow(S32 throttle_cat, F32 bits); // I'm about to send bits, TRUE if would overflow channel + BOOL throttleOverflow(S32 throttle_cat, F32 bits); // I just sent bits, TRUE if that overflowed the channel + BOOL dynamicAdjust(); // Shift bandwidth from idle channels to busy channels, TRUE if adjustment occurred + BOOL setNominalBPS(F32* throttle_vec); // TRUE if any value was different, resets adjustment system if was different + + void packThrottle(LLDataPacker &dp) const; + void unpackThrottle(LLDataPacker &dp); +public: + F32 mThrottleTotal[TC_EOF]; // BPS available, sent by viewer, sum for all simulators + +protected: + F32 mNominalBPS[TC_EOF]; // BPS available, adjusted to be just this simulator + F32 mCurrentBPS[TC_EOF]; // BPS available, dynamically adjusted + + F32 mBitsAvailable[TC_EOF]; // Bits available to send right now on each channel + F32 mBitsSentThisPeriod[TC_EOF]; // Sent in this dynamic allocation period + F32 mBitsSentHistory[TC_EOF]; // Sent before this dynamic allocation period, adjusted to one period length + + F64 mLastSendTime[TC_EOF]; // Time since last send on this channel + F64 mDynamicAdjustTime; // Only dynamic adjust every 2 seconds or so. + +}; + +#endif diff --git a/indra/llmessage/lltransfermanager.cpp b/indra/llmessage/lltransfermanager.cpp new file mode 100644 index 0000000000..46fc386d71 --- /dev/null +++ b/indra/llmessage/lltransfermanager.cpp @@ -0,0 +1,1270 @@ +/** + * @file lltransfermanager.cpp + * @brief Improved transfer mechanism for moving data through the + * message system. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lltransfermanager.h" + +#include "llerror.h" +#include "message.h" +#include "lldatapacker.h" + +#include "lltransfersourcefile.h" +#include "lltransfersourceasset.h" +#include "lltransfertargetfile.h" +#include "lltransfertargetvfile.h" + +const S32 MAX_PACKET_DATA_SIZE = 2048; +const S32 MAX_PARAMS_SIZE = 1024; + +LLTransferManager gTransferManager; +LLTransferSource::stype_scfunc_map LLTransferSource::sSourceCreateMap; + +// +// LLTransferManager implementation +// + +LLTransferManager::LLTransferManager() : + mValid(FALSE) +{ + S32 i; + for (i = 0; i < LLTTT_NUM_TYPES; i++) + { + mTransferBitsIn[i] = 0; + mTransferBitsOut[i] = 0; + } +} + + +LLTransferManager::~LLTransferManager() +{ + if (mValid) + { + llwarns << "LLTransferManager::~LLTransferManager - Should have been cleaned up by message system shutdown process" << llendl; + cleanup(); + } +} + + +void LLTransferManager::init() +{ + if (mValid) + { + llerrs << "Double initializing LLTransferManager!" << llendl; + } + mValid = TRUE; + + // Register message system handlers + gMessageSystem->setHandlerFunc("TransferRequest", processTransferRequest, NULL); + gMessageSystem->setHandlerFunc("TransferInfo", processTransferInfo, NULL); + gMessageSystem->setHandlerFunc("TransferPacket", processTransferPacket, NULL); + gMessageSystem->setHandlerFunc("TransferAbort", processTransferAbort, NULL); +} + + +void LLTransferManager::cleanup() +{ + mValid = FALSE; + + host_tc_map::iterator iter; + for (iter = mTransferConnections.begin(); iter != mTransferConnections.end(); iter++) + { + delete iter->second; + } + mTransferConnections.clear(); +} + + +void LLTransferManager::updateTransfers() +{ + host_tc_map::iterator iter; + for (iter = mTransferConnections.begin(); iter != mTransferConnections.end(); iter++) + { + iter->second->updateTransfers(); + } +} + + +void LLTransferManager::cleanupConnection(const LLHost &host) +{ + host_tc_map::iterator iter; + iter = mTransferConnections.find(host); + if (iter == mTransferConnections.end()) + { + // This can happen legitimately if we've never done a transfer, and we're + // cleaning up a circuit. + //llwarns << "Cleaning up nonexistent transfer connection to " << host << llendl; + return; + } + LLTransferConnection *connp = iter->second; + delete connp; + mTransferConnections.erase(iter); +} + + +LLTransferConnection *LLTransferManager::getTransferConnection(const LLHost &host) +{ + host_tc_map::iterator iter; + iter = mTransferConnections.find(host); + if (iter == mTransferConnections.end()) + { + mTransferConnections[host] = new LLTransferConnection(host); + return mTransferConnections[host]; + } + + return iter->second; +} + + +LLTransferSourceChannel *LLTransferManager::getSourceChannel(const LLHost &host, const LLTransferChannelType type) +{ + LLTransferConnection *tcp = getTransferConnection(host); + if (!tcp) + { + return NULL; + } + return tcp->getSourceChannel(type); +} + + + +LLTransferTargetChannel *LLTransferManager::getTargetChannel(const LLHost &host, const LLTransferChannelType type) +{ + LLTransferConnection *tcp = getTransferConnection(host); + if (!tcp) + { + return NULL; + } + return tcp->getTargetChannel(type); +} + +// virtual +LLTransferSourceParams::~LLTransferSourceParams() +{ } + + +LLTransferSource *LLTransferManager::findTransferSource(const LLUUID &transfer_id) +{ + // This linear traversal could screw us later if we do lots of + // searches for sources. However, this ONLY happens right now + // in asset transfer callbacks, so this should be relatively quick. + host_tc_map::iterator iter; + for (iter = mTransferConnections.begin(); iter != mTransferConnections.end(); iter++) + { + LLTransferConnection *tcp = iter->second; + LLTransferConnection::tsc_iter sc_iter; + for (sc_iter = tcp->mTransferSourceChannels.begin(); sc_iter != tcp->mTransferSourceChannels.end(); sc_iter++) + { + LLTransferSourceChannel *scp = *sc_iter; + LLTransferSource *sourcep = scp->findTransferSource(transfer_id); + if (sourcep) + { + return sourcep; + } + } + } + + return NULL; +} + +// +// Message handlers +// + +//static +void LLTransferManager::processTransferRequest(LLMessageSystem *msgp, void **) +{ + //llinfos << "LLTransferManager::processTransferRequest" << llendl; + + LLUUID transfer_id; + LLTransferSourceType source_type; + LLTransferChannelType channel_type; + F32 priority; + + msgp->getUUID("TransferInfo", "TransferID", transfer_id); + msgp->getS32("TransferInfo", "SourceType", (S32 &)source_type); + msgp->getS32("TransferInfo", "ChannelType", (S32 &)channel_type); + msgp->getF32("TransferInfo", "Priority", priority); + + LLTransferSourceChannel *tscp = gTransferManager.getSourceChannel(msgp->getSender(), channel_type); + + if (!tscp) + { + llwarns << "Source channel not found" << llendl; + return; + } + + if (tscp->findTransferSource(transfer_id)) + { + llwarns << "Duplicate request for transfer " << transfer_id << ", aborting!" << llendl; + return; + } + + + //llinfos << transfer_id << ":" << source_type << ":" << channel_type << ":" << priority << llendl; + LLTransferSource *tsp = LLTransferSource::createSource(source_type, transfer_id, priority); + if (!tsp) + { + llwarns << "LLTransferManager::processTransferRequest couldn't create transfer source!" << llendl; + return; + } + tscp->addTransferSource(tsp); + + U8 tmp[MAX_PARAMS_SIZE]; + S32 size = msgp->getSize("TransferInfo", "Params"); + gMessageSystem->getBinaryData("TransferInfo", "Params", tmp, size); + + LLDataPackerBinaryBuffer dpb(tmp, MAX_PARAMS_SIZE); + BOOL unpack_ok = tsp->unpackParams(dpb); + if (!unpack_ok) + { + llwarns << "Got bad parameters for a transfer request!" << llendl; + } + + tsp->initTransfer(); + // Don't use the status code from initTransfer for anything right now, was used before but the logic + // changed. +} + + +//static +void LLTransferManager::processTransferInfo(LLMessageSystem *msgp, void **) +{ + //llinfos << "LLTransferManager::processTransferInfo" << llendl; + + LLUUID transfer_id; + LLTransferTargetType target_type; + LLTransferChannelType channel_type; + LLTSCode status; + S32 size; + + msgp->getUUID("TransferInfo", "TransferID", transfer_id); + msgp->getS32("TransferInfo", "TargetType", (S32 &)target_type); + msgp->getS32("TransferInfo", "ChannelType", (S32 &)channel_type); + msgp->getS32("TransferInfo", "Status", (S32 &)status); + msgp->getS32("TransferInfo", "Size", size); + + //llinfos << transfer_id << ":" << target_type<< ":" << channel_type << llendl; + LLTransferTargetChannel *ttcp = gTransferManager.getTargetChannel(msgp->getSender(), channel_type); + if (!ttcp) + { + llwarns << "Target channel not found" << llendl; + // Should send a message to abort the transfer. + return; + } + + LLTransferTarget *ttp = ttcp->findTransferTarget(transfer_id); + if (!ttp) + { + llwarns << "TransferInfo for unknown transfer! Not able to handle this yet!" << llendl; + // This could happen if we're doing a push transfer, although to avoid confusion, + // maybe it should be a different message. + return; + } + + if (status != LLTS_OK) + { + llwarns << transfer_id << ": Non-ok status, cleaning up" << llendl; + ttp->completionCallback(status); + // Clean up the transfer. + ttcp->deleteTransfer(ttp); + return; + } + + llinfos << "Receiving " << transfer_id << ", size " << size << " bytes" << llendl; + ttp->setSize(size); + ttp->setGotInfo(TRUE); + + // OK, at this point we to handle any delayed transfer packets (which could happen + // if this packet was lost) + + // This is a lame cut and paste of code down below. If we change the logic down there, + // we HAVE to change the logic up here. + + while (1) + { + S32 packet_id = 0; + U8 tmp_data[MAX_PACKET_DATA_SIZE]; + // See if we've got any delayed packets + packet_id = ttp->getNextPacketID(); + if (ttp->mDelayedPacketMap.find(packet_id) != ttp->mDelayedPacketMap.end()) + { + // Perhaps this stuff should be inside a method in LLTransferPacket? + // I'm too lazy to do it now, though. + llinfos << "Playing back delayed packet " << packet_id << llendl; + LLTransferPacket *packetp = ttp->mDelayedPacketMap[packet_id]; + + // This is somewhat inefficient, but avoids us having to duplicate + // code between the off-the-wire and delayed paths. + packet_id = packetp->mPacketID; + size = packetp->mSize; + if (size) + { + if ((packetp->mDatap != NULL) && (size<(S32)sizeof(tmp_data))) + { + memcpy(tmp_data, packetp->mDatap, size); + } + } + status = packetp->mStatus; + ttp->mDelayedPacketMap.erase(packet_id); + delete packetp; + } + else + { + // No matching delayed packet, we're done. + break; + } + + LLTSCode ret_code = ttp->dataCallback(packet_id, tmp_data, size); + if (ret_code == LLTS_OK) + { + ttp->setLastPacketID(packet_id); + } + + if (status != LLTS_OK) + { + if (status != LLTS_DONE) + { + llwarns << "LLTransferManager::processTransferInfo Error in playback!" << llendl; + } + else + { + llinfos << "LLTransferManager::processTransferInfo replay FINISHED for " << transfer_id << llendl; + } + // This transfer is done, either via error or not. + ttp->completionCallback(status); + ttcp->deleteTransfer(ttp); + return; + } + } +} + + +//static +void LLTransferManager::processTransferPacket(LLMessageSystem *msgp, void **) +{ + //llinfos << "LLTransferManager::processTransferPacket" << llendl; + + LLUUID transfer_id; + LLTransferChannelType channel_type; + S32 packet_id; + LLTSCode status; + S32 size; + msgp->getUUID("TransferData", "TransferID", transfer_id); + msgp->getS32("TransferData", "ChannelType", (S32 &)channel_type); + msgp->getS32("TransferData", "Packet", packet_id); + msgp->getS32("TransferData", "Status", (S32 &)status); + + // Find the transfer associated with this packet. + //llinfos << transfer_id << ":" << channel_type << llendl; + LLTransferTargetChannel *ttcp = gTransferManager.getTargetChannel(msgp->getSender(), channel_type); + if (!ttcp) + { + llwarns << "Target channel not found" << llendl; + return; + } + + LLTransferTarget *ttp = ttcp->findTransferTarget(transfer_id); + if (!ttp) + { + llwarns << "Didn't find matching transfer for " << transfer_id << ", aborting!" << llendl; + llwarns << "Packet ID: " << packet_id << llendl; + llwarns << "Should notify source of this!" << llendl; + return; + } + + size = msgp->getSize("TransferData", "Data"); + + S32 msg_bytes = 0; + if (msgp->getReceiveCompressedSize()) + { + msg_bytes = msgp->getReceiveCompressedSize(); + } + else + { + msg_bytes = msgp->getReceiveSize(); + } + gTransferManager.addTransferBitsIn(ttcp->mChannelType, msg_bytes*8); + + if ((size < 0) || (size > MAX_PACKET_DATA_SIZE)) + { + llwarns << "Invalid transfer packet size " << size << llendl; + return; + } + + U8 tmp_data[MAX_PACKET_DATA_SIZE]; + if (size > 0) + { + // Only pull the data out if the size is > 0 + msgp->getBinaryData("TransferData", "Data", tmp_data, size); + } + + if ((!ttp->gotInfo()) || (ttp->getNextPacketID() != packet_id)) + { + + llwarns << "Out of order packet in transfer " << transfer_id << ", got " << packet_id << " expecting " << ttp->getNextPacketID() << llendl; + + // Put this on a list of packets to be delivered later. + ttp->addDelayedPacket(packet_id, status, tmp_data, size); + return; + } + + // Loop through this until we're done with all delayed packets + + // + // NOTE: THERE IS A CUT AND PASTE OF THIS CODE IN THE TRANSFERINFO HANDLER + // SO WE CAN PLAY BACK DELAYED PACKETS THERE!!!!!!!!!!!!!!!!!!!!!!!!! + // + BOOL done = FALSE; + while (!done) + { + LLTSCode ret_code = ttp->dataCallback(packet_id, tmp_data, size); + if (ret_code == LLTS_OK) + { + ttp->setLastPacketID(packet_id); + } + + if (status != LLTS_OK) + { + if (status != LLTS_DONE) + { + llwarns << "LLTransferManager::processTransferPacket Error in transfer!" << llendl; + } + else + { +// llinfos << "LLTransferManager::processTransferPacket done for " << transfer_id << llendl; + } + // This transfer is done, either via error or not. + ttp->completionCallback(status); + ttcp->deleteTransfer(ttp); + return; + } + + // See if we've got any delayed packets + packet_id = ttp->getNextPacketID(); + if (ttp->mDelayedPacketMap.find(packet_id) != ttp->mDelayedPacketMap.end()) + { + // Perhaps this stuff should be inside a method in LLTransferPacket? + // I'm too lazy to do it now, though. + llinfos << "Playing back delayed packet " << packet_id << llendl; + LLTransferPacket *packetp = ttp->mDelayedPacketMap[packet_id]; + + // This is somewhat inefficient, but avoids us having to duplicate + // code between the off-the-wire and delayed paths. + packet_id = packetp->mPacketID; + size = packetp->mSize; + if (size) + { + if ((packetp->mDatap != NULL) && (size<(S32)sizeof(tmp_data))) + { + memcpy(tmp_data, packetp->mDatap, size); + } + } + status = packetp->mStatus; + ttp->mDelayedPacketMap.erase(packet_id); + delete packetp; + } + else + { + // No matching delayed packet, abort it. + done = TRUE; + } + } +} + + +//static +void LLTransferManager::processTransferAbort(LLMessageSystem *msgp, void **) +{ + //llinfos << "LLTransferManager::processTransferPacket" << llendl; + + LLUUID transfer_id; + LLTransferChannelType channel_type; + msgp->getUUID("TransferInfo", "TransferID", transfer_id); + msgp->getS32("TransferInfo", "ChannelType", (S32 &)channel_type); + + + // See if it's a target that we're trying to abort + // Find the transfer associated with this packet. + LLTransferTargetChannel *ttcp = gTransferManager.getTargetChannel(msgp->getSender(), channel_type); + if (ttcp) + { + LLTransferTarget *ttp = ttcp->findTransferTarget(transfer_id); + if (ttp) + { + ttp->abortTransfer(); + ttcp->deleteTransfer(ttp); + return; + } + } + + // Hmm, not a target. Maybe it's a source. + LLTransferSourceChannel *tscp = gTransferManager.getSourceChannel(msgp->getSender(), channel_type); + if (tscp) + { + LLTransferSource *tsp = tscp->findTransferSource(transfer_id); + if (tsp) + { + tsp->abortTransfer(); + tscp->deleteTransfer(tsp); + return; + } + } + + llwarns << "Couldn't find transfer " << transfer_id << " to abort!" << llendl; +} + + +//static +void LLTransferManager::processTransferPriority(LLMessageSystem *msgp, void **) +{ + //llinfos << "LLTransferManager::processTransferPacket" << llendl; + + LLUUID transfer_id; + LLTransferChannelType channel_type; + F32 priority = 0.f; + msgp->getUUID("TransferInfo", "TransferID", transfer_id); + msgp->getS32("TransferInfo", "ChannelType", (S32 &)channel_type); + msgp->getF32("TransferInfo", "Priority", priority); + + // Hmm, not a target. Maybe it's a source. + LLTransferSourceChannel *tscp = gTransferManager.getSourceChannel(msgp->getSender(), channel_type); + if (tscp) + { + LLTransferSource *tsp = tscp->findTransferSource(transfer_id); + if (tsp) + { + tscp->updatePriority(tsp, priority); + return; + } + } + + llwarns << "Couldn't find transfer " << transfer_id << " to set priority!" << llendl; +} + + +//static +void LLTransferManager::reliablePacketCallback(void **user_data, S32 result) +{ + LLUUID *transfer_idp = (LLUUID *)user_data; + if (result) + { + llwarns << "Aborting reliable transfer " << *transfer_idp << " due to failed reliable resends!" << llendl; + LLTransferSource *tsp = gTransferManager.findTransferSource(*transfer_idp); + if (tsp) + { + LLTransferSourceChannel *tscp = tsp->mChannelp; + tsp->abortTransfer(); + tscp->deleteTransfer(tsp); + } + } + delete transfer_idp; +} + +// +// LLTransferConnection implementation +// + +LLTransferConnection::LLTransferConnection(const LLHost &host) +{ + mHost = host; +} + +LLTransferConnection::~LLTransferConnection() +{ + tsc_iter itersc; + for (itersc = mTransferSourceChannels.begin(); itersc != mTransferSourceChannels.end(); itersc++) + { + delete *itersc; + } + mTransferSourceChannels.clear(); + + ttc_iter itertc; + for (itertc = mTransferTargetChannels.begin(); itertc != mTransferTargetChannels.end(); itertc++) + { + delete *itertc; + } + mTransferTargetChannels.clear(); +} + + +void LLTransferConnection::updateTransfers() +{ + // Do stuff for source transfers (basically, send data out). + tsc_iter iter; + for (iter = mTransferSourceChannels.begin(); iter != mTransferSourceChannels.end(); iter++) + { + (*iter)->updateTransfers(); + } + + // Do stuff for target transfers + // Primarily, we should be aborting transfers that are irredeemably broken + // (large packet gaps that don't appear to be getting filled in, most likely) + // Probably should NOT be doing timeouts for other things, as new priority scheme + // means that a high priority transfer COULD block a transfer for a long time. +} + + +LLTransferSourceChannel *LLTransferConnection::getSourceChannel(const LLTransferChannelType channel_type) +{ + tsc_iter iter; + for (iter = mTransferSourceChannels.begin(); iter != mTransferSourceChannels.end(); iter++) + { + if ((*iter)->getChannelType() == channel_type) + { + return *iter; + } + } + + LLTransferSourceChannel *tscp = new LLTransferSourceChannel(channel_type, mHost); + mTransferSourceChannels.push_back(tscp); + return tscp; +} + + +LLTransferTargetChannel *LLTransferConnection::getTargetChannel(const LLTransferChannelType channel_type) +{ + ttc_iter iter; + for (iter = mTransferTargetChannels.begin(); iter != mTransferTargetChannels.end(); iter++) + { + if ((*iter)->getChannelType() == channel_type) + { + return *iter; + } + } + + LLTransferTargetChannel *ttcp = new LLTransferTargetChannel(channel_type, mHost); + mTransferTargetChannels.push_back(ttcp); + return ttcp; +} + + +// +// LLTransferSourceChannel implementation +// + +const S32 DEFAULT_PACKET_SIZE = 1000; + + +LLTransferSourceChannel::LLTransferSourceChannel(const LLTransferChannelType channel_type, const LLHost &host) : + mChannelType(channel_type), + mHost(host), + mTransferSources(LLTransferSource::sSetPriority, LLTransferSource::sGetPriority), + mThrottleID(TC_ASSET) +{ +} + + +LLTransferSourceChannel::~LLTransferSourceChannel() +{ + LLPriQueueMap::pqm_iter iter; + for (iter = mTransferSources.mMap.begin(); iter != mTransferSources.mMap.end(); iter++) + { + // Just kill off all of the transfers + (*iter).second->abortTransfer(); + delete iter->second; + } + mTransferSources.mMap.clear(); +} + +void LLTransferSourceChannel::updatePriority(LLTransferSource *tsp, const F32 priority) +{ + mTransferSources.reprioritize(priority, tsp); +} + +void LLTransferSourceChannel::updateTransfers() +{ + // Actually, this should do the following: + // Decide if we can actually send data. + // If so, update priorities so we know who gets to send it. + // Send data from the sources, while updating until we've sent our throttle allocation. + + LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit(getHost()); + if (!cdp) + { + return; + } + + if (cdp->isBlocked()) + { + // FIXME We need to make sure that the throttle bits available gets reset. + + // We DON'T want to send any packets if they're blocked, they'll just end up + // piling up on the other end. + //llwarns << "Blocking transfers due to blocked circuit for " << getHost() << llendl; + return; + } + + const S32 throttle_id = mThrottleID; + + LLThrottleGroup &tg = cdp->getThrottleGroup(); + + if (tg.checkOverflow(throttle_id, 0.f)) + { + return; + } + + LLPriQueueMap::pqm_iter iter; + + + BOOL done = FALSE; + for (iter = mTransferSources.mMap.begin(); (iter != mTransferSources.mMap.end()) && !done;) + { + //llinfos << "LLTransferSourceChannel::updateTransfers()" << llendl; + // Do stuff. + LLTransferSource *tsp = iter->second; + U8 *datap = NULL; + S32 data_size = 0; + BOOL delete_data = FALSE; + S32 packet_id = 0; + S32 sent_bytes = 0; + LLTSCode status = LLTS_OK; + + // Get the packetID for the next packet that we're transferring. + packet_id = tsp->getNextPacketID(); + status = tsp->dataCallback(packet_id, DEFAULT_PACKET_SIZE, &datap, data_size, delete_data); + + if (status == LLTS_SKIP) + { + // We don't have any data, but we're not done, just go on. + // This will presumably be used for streaming or async transfers that + // are stalled waiting for data from another source. + iter++; + continue; + } + + LLUUID *cb_uuid = new LLUUID(tsp->getID()); + + // Send the data now, even if it's an error. + // The status code will tell the other end what to do. + gMessageSystem->newMessage("TransferPacket"); + gMessageSystem->nextBlock("TransferData"); + gMessageSystem->addUUID("TransferID", tsp->getID()); + gMessageSystem->addS32("ChannelType", getChannelType()); + gMessageSystem->addS32("Packet", packet_id); // HACK! Need to put in a REAL packet id + gMessageSystem->addS32("Status", status); + gMessageSystem->addBinaryData("Data", datap, data_size); + sent_bytes = gMessageSystem->getCurrentSendTotal(); + gMessageSystem->sendReliable(getHost(), LL_DEFAULT_RELIABLE_RETRIES, TRUE, 0.f, + LLTransferManager::reliablePacketCallback, (void**)cb_uuid); + + // Do bookkeeping for the throttle + done = tg.throttleOverflow(throttle_id, sent_bytes*8.f); + gTransferManager.addTransferBitsOut(mChannelType, sent_bytes*8); + + // Clean up our temporary data. + if (delete_data) + { + delete[] datap; + datap = NULL; + } + + // Update the packet counter + tsp->setLastPacketID(packet_id); + + switch (status) + { + case LLTS_OK: + // We're OK, don't need to do anything. Keep sending data. + break; + case LLTS_ERROR: + llwarns << "Error in transfer dataCallback!" << llendl; + case LLTS_DONE: + // We need to clean up this transfer source. + //llinfos << "LLTransferSourceChannel::updateTransfers() " << tsp->getID() << " done" << llendl; + tsp->completionCallback(status); + delete tsp; + + mTransferSources.mMap.erase(iter++); + break; + default: + llerrs << "Unknown transfer error code!" << llendl; + } + + // At this point, we should do priority adjustment (since some transfers like + // streaming transfers will adjust priority based on how much they've sent and time, + // but I'm not going to bother yet. - djs. + } +} + + +void LLTransferSourceChannel::addTransferSource(LLTransferSource *sourcep) +{ + sourcep->mChannelp = this; + mTransferSources.push(sourcep->getPriority(), sourcep); +} + + +LLTransferSource *LLTransferSourceChannel::findTransferSource(const LLUUID &transfer_id) +{ + LLPriQueueMap::pqm_iter iter; + for (iter = mTransferSources.mMap.begin(); iter != mTransferSources.mMap.end(); iter++) + { + LLTransferSource *tsp = iter->second; + if (tsp->getID() == transfer_id) + { + return tsp; + } + } + return NULL; +} + + +BOOL LLTransferSourceChannel::deleteTransfer(LLTransferSource *tsp) +{ + LLPriQueueMap::pqm_iter iter; + for (iter = mTransferSources.mMap.begin(); iter != mTransferSources.mMap.end(); iter++) + { + if (iter->second == tsp) + { + break; + } + } + + if (iter == mTransferSources.mMap.end()) + { + llerrs << "Unable to find transfer source to delete!" << llendl; + return FALSE; + } + mTransferSources.mMap.erase(iter); + delete tsp; + return TRUE; +} + + +// +// LLTransferTargetChannel implementation +// + +LLTransferTargetChannel::LLTransferTargetChannel(const LLTransferChannelType channel_type, const LLHost &host) : + mChannelType(channel_type), + mHost(host) +{ +} + +LLTransferTargetChannel::~LLTransferTargetChannel() +{ + tt_iter iter; + for (iter = mTransferTargets.begin(); iter != mTransferTargets.end(); iter++) + { + // Abort all of the current transfers + (*iter)->abortTransfer(); + delete *iter; + } + mTransferTargets.clear(); +} + + +void LLTransferTargetChannel::requestTransfer(const LLTransferSourceParams &source_params, + const LLTransferTargetParams &target_params, + const F32 priority) +{ + LLUUID id; + id.generate(); + LLTransferTarget *ttp = LLTransferTarget::createTarget(target_params.getType(), id); + if (!ttp) + { + llwarns << "LLTransferManager::requestTransfer aborting due to target creation failure!" << llendl; + } + + ttp->applyParams(target_params); + addTransferTarget(ttp); + + sendTransferRequest(ttp, source_params, priority); +} + + +void LLTransferTargetChannel::sendTransferRequest(LLTransferTarget *targetp, + const LLTransferSourceParams ¶ms, + const F32 priority) +{ + // + // Pack the message with data which explains how to get the source, and + // send it off to the source for this channel. + // + llassert(targetp); + llassert(targetp->getChannel() == this); + + gMessageSystem->newMessage("TransferRequest"); + gMessageSystem->nextBlock("TransferInfo"); + gMessageSystem->addUUID("TransferID", targetp->getID()); + gMessageSystem->addS32("SourceType", params.getType()); + gMessageSystem->addS32("ChannelType", getChannelType()); + gMessageSystem->addF32("Priority", priority); + + U8 tmp[MAX_PARAMS_SIZE]; + LLDataPackerBinaryBuffer dp(tmp, MAX_PARAMS_SIZE); + params.packParams(dp); + S32 len = dp.getCurrentSize(); + gMessageSystem->addBinaryData("Params", tmp, len); + + gMessageSystem->sendReliable(mHost); +} + + +void LLTransferTargetChannel::addTransferTarget(LLTransferTarget *targetp) +{ + targetp->mChannelp = this; + mTransferTargets.push_back(targetp); +} + + +LLTransferTarget *LLTransferTargetChannel::findTransferTarget(const LLUUID &transfer_id) +{ + tt_iter iter; + for (iter = mTransferTargets.begin(); iter != mTransferTargets.end(); iter++) + { + LLTransferTarget *ttp = *iter; + if (ttp->getID() == transfer_id) + { + return ttp; + } + } + return NULL; +} + + +BOOL LLTransferTargetChannel::deleteTransfer(LLTransferTarget *ttp) +{ + tt_iter iter; + for (iter = mTransferTargets.begin(); iter != mTransferTargets.end(); iter++) + { + if (*iter == ttp) + { + break; + } + } + + if (iter == mTransferTargets.end()) + { + llerrs << "Unable to find transfer target to delete!" << llendl; + return FALSE; + } + mTransferTargets.erase(iter); + delete ttp; + return TRUE; +} + + +// +// LLTransferSource implementation +// + +LLTransferSource::LLTransferSource(const LLTransferSourceType type, + const LLUUID &transfer_id, + const F32 priority) : + mType(type), + mID(transfer_id), + mChannelp(NULL), + mPriority(priority), + mSize(0), + mLastPacketID(-1) +{ + setPriority(priority); +} + + +LLTransferSource::~LLTransferSource() +{ + // No actual cleanup of the transfer is done here, this is purely for + // memory cleanup. The completionCallback is guaranteed to get called + // before this happens. +} + + +void LLTransferSource::sendTransferStatus(LLTSCode status) +{ + gMessageSystem->newMessage("TransferInfo"); + gMessageSystem->nextBlock("TransferInfo"); + gMessageSystem->addUUID("TransferID", getID()); + gMessageSystem->addS32("TargetType", LLTTT_UNKNOWN); + gMessageSystem->addS32("ChannelType", mChannelp->getChannelType()); + gMessageSystem->addS32("Status", status); + gMessageSystem->addS32("Size", mSize); + gMessageSystem->sendReliable(mChannelp->getHost()); + + // Abort if there was as asset system issue. + if (status != LLTS_OK) + { + completionCallback(status); + mChannelp->deleteTransfer(this); + } +} + + +// This should never be called directly, the transfer manager is responsible for +// aborting the transfer from the channel. I might want to rethink this in the +// future, though. +void LLTransferSource::abortTransfer() +{ + // Send a message down, call the completion callback + llinfos << "Aborting transfer " << getID() << " to " << mChannelp->getHost() << llendl; + gMessageSystem->newMessage("TransferAbort"); + gMessageSystem->nextBlock("TransferInfo"); + gMessageSystem->addUUID("TransferID", getID()); + gMessageSystem->addS32("ChannelType", mChannelp->getChannelType()); + gMessageSystem->sendReliable(mChannelp->getHost()); + + completionCallback(LLTS_ABORT); +} + + +//static +void LLTransferSource::registerSourceType(const LLTransferSourceType stype, LLTransferSourceCreateFunc func) +{ + if (sSourceCreateMap.count(stype)) + { + // Disallow changing what class handles a source type + // Unclear when you would want to do this, and whether it would work. + llerrs << "Reregistering source type " << stype << llendl; + } + else + { + sSourceCreateMap[stype] = func; + } +} + +//static +LLTransferSource *LLTransferSource::createSource(const LLTransferSourceType stype, + const LLUUID &id, + const F32 priority) +{ + switch (stype) + { + // *NOTE: The source file transfer mechanism is highly insecure and could + // lead to easy exploitation of a server process. + // I have removed all uses of it from the codebase. Phoenix. + // + //case LLTST_FILE: + // return new LLTransferSourceFile(id, priority); + case LLTST_ASSET: + return new LLTransferSourceAsset(id, priority); + default: + { + if (!sSourceCreateMap.count(stype)) + { + // Use the callback to create the source type if it's not there. + llwarns << "Unknown transfer source type: " << stype << llendl; + return NULL; + } + return (sSourceCreateMap[stype])(id, priority); + } + } +} + + +// static +void LLTransferSource::sSetPriority(LLTransferSource *&tsp, const F32 priority) +{ + tsp->setPriority(priority); +} + + +// static +F32 LLTransferSource::sGetPriority(LLTransferSource *&tsp) +{ + return tsp->getPriority(); +} + + +// +// LLTransferPacket implementation +// + +LLTransferPacket::LLTransferPacket(const S32 packet_id, const LLTSCode status, const U8 *datap, const S32 size) : + mPacketID(packet_id), + mStatus(status), + mDatap(NULL), + mSize(size) +{ + if (size == 0) + { + return; + } + + mDatap = new U8[size]; + if (mDatap != NULL) + { + memcpy(mDatap, datap, size); + } +} + +LLTransferPacket::~LLTransferPacket() +{ + delete[] mDatap; +} + +// +// LLTransferTarget implementation +// + +LLTransferTarget::LLTransferTarget(LLTransferTargetType type, const LLUUID &id) : + mType(type), + mID(id), + mGotInfo(FALSE), + mSize(0), + mLastPacketID(-1) +{ +} + +LLTransferTarget::~LLTransferTarget() +{ + // No actual cleanup of the transfer is done here, this is purely for + // memory cleanup. The completionCallback is guaranteed to get called + // before this happens. + tpm_iter iter; + for (iter = mDelayedPacketMap.begin(); iter != mDelayedPacketMap.end(); iter++) + { + delete iter->second; + } + mDelayedPacketMap.clear(); +} + +// This should never be called directly, the transfer manager is responsible for +// aborting the transfer from the channel. I might want to rethink this in the +// future, though. +void LLTransferTarget::abortTransfer() +{ + // Send a message up, call the completion callback + llinfos << "Aborting transfer " << getID() << " from " << mChannelp->getHost() << llendl; + gMessageSystem->newMessage("TransferAbort"); + gMessageSystem->nextBlock("TransferInfo"); + gMessageSystem->addUUID("TransferID", getID()); + gMessageSystem->addS32("ChannelType", mChannelp->getChannelType()); + gMessageSystem->sendReliable(mChannelp->getHost()); + + completionCallback(LLTS_ABORT); +} + +void LLTransferTarget::addDelayedPacket(const S32 packet_id, const LLTSCode status, U8 *datap, const S32 size) +{ + LLTransferPacket *tpp = new LLTransferPacket(packet_id, status, datap, size); +#ifdef _DEBUG + if (mDelayedPacketMap.find(packet_id) != mDelayedPacketMap.end()) + { + llerrs << "Packet ALREADY in delayed packet map!" << llendl; + } +#endif + mDelayedPacketMap[packet_id] = tpp; +} + + +LLTransferTarget *LLTransferTarget::createTarget(const LLTransferTargetType type, const LLUUID &id) +{ + switch (type) + { + case LLTTT_FILE: + return new LLTransferTargetFile(id); + case LLTTT_VFILE: + return new LLTransferTargetVFile(id); + default: + llwarns << "Unknown transfer target type: " << type << llendl; + return NULL; + } +} + + +LLTransferSourceParamsInvItem::LLTransferSourceParamsInvItem() : LLTransferSourceParams(LLTST_SIM_INV_ITEM), mAssetType(LLAssetType::AT_NONE) +{ +} + + +void LLTransferSourceParamsInvItem::setAgentSession(const LLUUID &agent_id, const LLUUID &session_id) +{ + mAgentID = agent_id; + mSessionID = session_id; +} + + +void LLTransferSourceParamsInvItem::setInvItem(const LLUUID &owner_id, const LLUUID &task_id, const LLUUID &item_id) +{ + mOwnerID = owner_id; + mTaskID = task_id; + mItemID = item_id; +} + + +void LLTransferSourceParamsInvItem::setAsset(const LLUUID &asset_id, const LLAssetType::EType asset_type) +{ + mAssetID = asset_id; + mAssetType = asset_type; +} + + +void LLTransferSourceParamsInvItem::packParams(LLDataPacker &dp) const +{ + dp.packUUID(mAgentID, "AgentID"); + dp.packUUID(mSessionID, "SessionID"); + dp.packUUID(mOwnerID, "OwnerID"); + dp.packUUID(mTaskID, "TaskID"); + dp.packUUID(mItemID, "ItemID"); + dp.packUUID(mAssetID, "AssetID"); + dp.packS32(mAssetType, "AssetType"); +} + + +BOOL LLTransferSourceParamsInvItem::unpackParams(LLDataPacker &dp) +{ + S32 tmp_at; + + dp.unpackUUID(mAgentID, "AgentID"); + dp.unpackUUID(mSessionID, "SessionID"); + dp.unpackUUID(mOwnerID, "OwnerID"); + dp.unpackUUID(mTaskID, "TaskID"); + dp.unpackUUID(mItemID, "ItemID"); + dp.unpackUUID(mAssetID, "AssetID"); + dp.unpackS32(tmp_at, "AssetType"); + + mAssetType = (LLAssetType::EType)tmp_at; + + return TRUE; +} + +LLTransferSourceParamsEstate::LLTransferSourceParamsEstate() : + LLTransferSourceParams(LLTST_SIM_ESTATE), mEstateAssetType(ET_NONE) +{ +} + +void LLTransferSourceParamsEstate::setAgentSession(const LLUUID &agent_id, const LLUUID &session_id) +{ + mAgentID = agent_id; + mSessionID = session_id; +} + +void LLTransferSourceParamsEstate::setEstateAssetType(const EstateAssetType etype) +{ + mEstateAssetType = etype; +} + +void LLTransferSourceParamsEstate::setAsset(const LLUUID &asset_id, const LLAssetType::EType asset_type) +{ + mAssetID = asset_id; + mAssetType = asset_type; +} + +void LLTransferSourceParamsEstate::packParams(LLDataPacker &dp) const +{ + dp.packUUID(mAgentID, "AgentID"); + dp.packUUID(mSessionID, "SessionID"); + dp.packS32(mEstateAssetType, "EstateAssetType"); +} + + +BOOL LLTransferSourceParamsEstate::unpackParams(LLDataPacker &dp) +{ + S32 tmp_et; + + dp.unpackUUID(mAgentID, "AgentID"); + dp.unpackUUID(mSessionID, "SessionID"); + dp.unpackS32(tmp_et, "EstateAssetType"); + + mEstateAssetType = (EstateAssetType)tmp_et; + + return TRUE; +} diff --git a/indra/llmessage/lltransfermanager.h b/indra/llmessage/lltransfermanager.h new file mode 100644 index 0000000000..8c4c9f8ba7 --- /dev/null +++ b/indra/llmessage/lltransfermanager.h @@ -0,0 +1,466 @@ +/** + * @file lltransfermanager.h + * @brief Improved transfer mechanism for moving data through the + * message system. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTRANSFERMANAGER_H +#define LL_LLTRANSFERMANAGER_H + +#include +#include + +#include "llhost.h" +#include "lluuid.h" +#include "llthrottle.h" +#include "llpriqueuemap.h" +#include "llassettype.h" + +// +// Definition of the manager class for the new LLXfer replacement. +// Provides prioritized, bandwidth-throttled transport of arbitrary +// binary data between host/circuit combos +// + + +typedef enum e_transfer_channel_type +{ + LLTCT_UNKNOWN = 0, + LLTCT_MISC, + LLTCT_ASSET, + LLTCT_NUM_TYPES +} LLTransferChannelType; + + +typedef enum e_transfer_source_type +{ + LLTST_UNKNOWN = 0, + LLTST_FILE, + LLTST_ASSET, + LLTST_SIM_INV_ITEM, // Simulator specific, may not be handled + LLTST_SIM_ESTATE, // Simulator specific, may not be handled + LLTST_NUM_TYPES +} LLTransferSourceType; + + +typedef enum e_transfer_target_type +{ + LLTTT_UNKNOWN = 0, + LLTTT_FILE, + LLTTT_VFILE, + LLTTT_NUM_TYPES +} LLTransferTargetType; + + +// Errors are negative, expected values are positive. +typedef enum e_status_codes +{ + LLTS_OK = 0, + LLTS_DONE = 1, + LLTS_SKIP = 2, + LLTS_ABORT = 3, + LLTS_ERROR = -1, + LLTS_UNKNOWN_SOURCE = -2, // Equivalent of a 404 + LLTS_INSUFFICIENT_PERMISSIONS = -3 // Not enough permissions +} LLTSCode; + +// Types of requests for estate wide information +typedef enum e_estate_type +{ + ET_Covenant = 0, + ET_NONE = -1 +} EstateAssetType; + +class LLMessageSystem; +class LLDataPacker; + +class LLTransferConnection; +class LLTransferSourceChannel; +class LLTransferTargetChannel; +class LLTransferSourceParams; +class LLTransferTargetParams; +class LLTransferSource; +class LLTransferTarget; + +class LLTransferManager +{ +public: + LLTransferManager(); + virtual ~LLTransferManager(); + + void init(); + void cleanup(); + + void updateTransfers(); // Called per frame to push packets out on the various different channels. + void cleanupConnection(const LLHost &host); + + + LLTransferSourceChannel *getSourceChannel(const LLHost &host, const LLTransferChannelType stype); + LLTransferTargetChannel *getTargetChannel(const LLHost &host, const LLTransferChannelType stype); + + LLTransferSource *findTransferSource(const LLUUID &transfer_id); + + BOOL isValid() const { return mValid; } + + static void processTransferRequest(LLMessageSystem *mesgsys, void **); + static void processTransferInfo(LLMessageSystem *mesgsys, void **); + static void processTransferPacket(LLMessageSystem *mesgsys, void **); + static void processTransferAbort(LLMessageSystem *mesgsys, void **); + static void processTransferPriority(LLMessageSystem *mesgsys, void **); + + static void reliablePacketCallback(void **, S32 result); + + S32 getTransferBitsIn(const LLTransferChannelType tctype) const { return mTransferBitsIn[tctype]; } + S32 getTransferBitsOut(const LLTransferChannelType tctype) const { return mTransferBitsOut[tctype]; } + void resetTransferBitsIn(const LLTransferChannelType tctype) { mTransferBitsIn[tctype] = 0; } + void resetTransferBitsOut(const LLTransferChannelType tctype) { mTransferBitsOut[tctype] = 0; } + void addTransferBitsIn(const LLTransferChannelType tctype, const S32 bits) { mTransferBitsIn[tctype] += bits; } + void addTransferBitsOut(const LLTransferChannelType tctype, const S32 bits) { mTransferBitsOut[tctype] += bits; } +protected: + LLTransferConnection *getTransferConnection(const LLHost &host); + BOOL removeTransferConnection(const LLHost &host); + +protected: + // Convenient typedefs + typedef std::map host_tc_map; + + BOOL mValid; + LLHost mHost; + + S32 mTransferBitsIn[LLTTT_NUM_TYPES]; + S32 mTransferBitsOut[LLTTT_NUM_TYPES]; + + // We keep a map between each host and LLTransferConnection. + host_tc_map mTransferConnections; +}; + + +// +// Keeps tracks of all channels to/from a particular host. +// +class LLTransferConnection +{ +public: + LLTransferConnection(const LLHost &host); + virtual ~LLTransferConnection(); + + void updateTransfers(); + + LLTransferSourceChannel *getSourceChannel(const LLTransferChannelType type); + LLTransferTargetChannel *getTargetChannel(const LLTransferChannelType type); + + // Convenient typedefs + typedef std::list::iterator tsc_iter; + typedef std::list::iterator ttc_iter; + friend class LLTransferManager; +protected: + + LLHost mHost; + std::list mTransferSourceChannels; + std::list mTransferTargetChannels; + +}; + + +// +// A channel which is pushing data out. +// + +class LLTransferSourceChannel +{ +public: + LLTransferSourceChannel(const LLTransferChannelType channel_type, + const LLHost &host); + virtual ~LLTransferSourceChannel(); + + void updateTransfers(); + + void updatePriority(LLTransferSource *tsp, const F32 priority); + + void addTransferSource(LLTransferSource *sourcep); + LLTransferSource *findTransferSource(const LLUUID &transfer_id); + BOOL deleteTransfer(LLTransferSource *tsp); + + void setThrottleID(const S32 throttle_id) { mThrottleID = throttle_id; } + + LLTransferChannelType getChannelType() const { return mChannelType; } + LLHost getHost() const { return mHost; } + +protected: + typedef std::list::iterator ts_iter; + + LLTransferChannelType mChannelType; + LLHost mHost; + LLPriQueueMap mTransferSources; + + // The throttle that this source channel should use + S32 mThrottleID; +}; + + +// +// A channel receiving data from a source. +// +class LLTransferTargetChannel +{ +public: + LLTransferTargetChannel(const LLTransferChannelType channel_type, const LLHost &host); + virtual ~LLTransferTargetChannel(); + + void requestTransfer(const LLTransferSourceParams &source_params, + const LLTransferTargetParams &target_params, + const F32 priority); + + LLTransferTarget *findTransferTarget(const LLUUID &transfer_id); + BOOL deleteTransfer(LLTransferTarget *ttp); + + + LLTransferChannelType getChannelType() const { return mChannelType; } + LLHost getHost() const { return mHost; } + +protected: + void sendTransferRequest(LLTransferTarget *targetp, + const LLTransferSourceParams ¶ms, + const F32 priority); + + void addTransferTarget(LLTransferTarget *targetp); + + friend class LLTransferTarget; + friend class LLTransferManager; +protected: + typedef std::list::iterator tt_iter; + + LLTransferChannelType mChannelType; + LLHost mHost; + std::list mTransferTargets; +}; + + +class LLTransferSourceParams +{ +public: + LLTransferSourceParams(const LLTransferSourceType type) : mType(type) { } + virtual ~LLTransferSourceParams(); + + virtual void packParams(LLDataPacker &dp) const = 0; + virtual BOOL unpackParams(LLDataPacker &dp) = 0; + + LLTransferSourceType getType() const { return mType; } + +protected: + LLTransferSourceType mType; +}; + + +// +// LLTransferSource is an interface, all transfer sources should be derived from it. +// +typedef LLTransferSource *(*LLTransferSourceCreateFunc)(const LLUUID &id, const F32 priority); + +class LLTransferSource +{ +public: + + LLUUID getID() { return mID; } + + friend class LLTransferManager; + friend class LLTransferSourceChannel; + +protected: + LLTransferSource(const LLTransferSourceType source_type, + const LLUUID &request_id, + const F32 priority); + virtual ~LLTransferSource(); + + void sendTransferStatus(LLTSCode status); // When you've figured out your transfer status, do this + + virtual void initTransfer() = 0; + virtual F32 updatePriority() = 0; + virtual LLTSCode dataCallback(const S32 packet_id, + const S32 max_bytes, + U8 **datap, + S32 &returned_bytes, + BOOL &delete_returned) = 0; + + // The completionCallback is GUARANTEED to be called before the destructor. + virtual void completionCallback(const LLTSCode status) = 0; + + virtual BOOL unpackParams(LLDataPacker &dp) = 0; + + virtual S32 getNextPacketID() { return mLastPacketID + 1; } + virtual void setLastPacketID(const S32 packet_id) { mLastPacketID = packet_id; } + + + // For now, no self-induced priority changes + F32 getPriority() { return mPriority; } + void setPriority(const F32 pri) { mPriority = pri; } + + virtual void abortTransfer(); // DON'T USE THIS ONE, used internally by LLTransferManager + + static LLTransferSource *createSource(const LLTransferSourceType stype, + const LLUUID &request_id, + const F32 priority); + static void registerSourceType(const LLTransferSourceType stype, LLTransferSourceCreateFunc); + + static void sSetPriority(LLTransferSource *&tsp, const F32 priority); + static F32 sGetPriority(LLTransferSource *&tsp); +protected: + typedef std::map stype_scfunc_map; + static stype_scfunc_map sSourceCreateMap; + + LLTransferSourceType mType; + LLUUID mID; + LLTransferSourceChannel *mChannelp; + F32 mPriority; + S32 mSize; + S32 mLastPacketID; +}; + + +class LLTransferTargetParams +{ +public: + LLTransferTargetParams(const LLTransferTargetType type) : mType(type) {} + LLTransferTargetType getType() const { return mType; } +protected: + LLTransferTargetType mType; +}; + + +class LLTransferPacket +{ + // Used for storing a packet that's being delivered later because it's out of order. + // ONLY should be accessed by the following two classes, for now. + friend class LLTransferTarget; + friend class LLTransferManager; + +protected: + + LLTransferPacket(const S32 packet_id, const LLTSCode status, const U8 *datap, const S32 size); + virtual ~LLTransferPacket(); + +protected: + S32 mPacketID; + LLTSCode mStatus; + U8 *mDatap; + S32 mSize; +}; + + +class LLTransferTarget +{ +public: + LLTransferTarget(LLTransferTargetType target_type, const LLUUID &transfer_id); + virtual ~LLTransferTarget(); + + + // Accessors + LLUUID getID() const { return mID; } + LLTransferTargetType getType() const { return mType; } + LLTransferTargetChannel *getChannel() const { return mChannelp; } + + friend class LLTransferManager; + friend class LLTransferTargetChannel; + + static LLTransferTarget *createTarget(const LLTransferTargetType type, + const LLUUID &request_id); +protected: + virtual void applyParams(const LLTransferTargetParams ¶ms) = 0; + virtual LLTSCode dataCallback(const S32 packet_id, U8 *in_datap, const S32 in_size) = 0; + + // The completionCallback is GUARANTEED to be called before the destructor, so all handling + // of errors/aborts should be done here. + virtual void completionCallback(const LLTSCode status) = 0; + + void abortTransfer(); + + virtual S32 getNextPacketID() { return mLastPacketID + 1; } + virtual void setLastPacketID(const S32 packet_id) { mLastPacketID = packet_id; } + void setSize(const S32 size) { mSize = size; } + void setGotInfo(const BOOL got_info) { mGotInfo = got_info; } + BOOL gotInfo() const { return mGotInfo; } + + void addDelayedPacket(const S32 packet_id, const LLTSCode status, U8 *datap, const S32 size); + +protected: + typedef std::map transfer_packet_map; + typedef std::map::iterator tpm_iter; + + LLTransferTargetType mType; + LLUUID mID; + LLTransferTargetChannel *mChannelp; + BOOL mGotInfo; + S32 mSize; + S32 mLastPacketID; + + transfer_packet_map mDelayedPacketMap; // Packets that are waiting because of missing/out of order issues +}; + + +// Hack, here so it's publicly available even though LLTransferSourceInvItem is only available on the simulator +class LLTransferSourceParamsInvItem: public LLTransferSourceParams +{ +public: + LLTransferSourceParamsInvItem(); + virtual ~LLTransferSourceParamsInvItem() {} + /*virtual*/ void packParams(LLDataPacker &dp) const; + /*virtual*/ BOOL unpackParams(LLDataPacker &dp); + + void setAgentSession(const LLUUID &agent_id, const LLUUID &session_id); + void setInvItem(const LLUUID &owner_id, const LLUUID &task_id, const LLUUID &item_id); + void setAsset(const LLUUID &asset_id, const LLAssetType::EType at); + + LLUUID getAgentID() const { return mAgentID; } + LLUUID getSessionID() const { return mSessionID; } + LLUUID getOwnerID() const { return mOwnerID; } + LLUUID getTaskID() const { return mTaskID; } + LLUUID getItemID() const { return mItemID; } + LLUUID getAssetID() const { return mAssetID; } + LLAssetType::EType getAssetType() const { return mAssetType; } + +protected: + LLUUID mAgentID; + LLUUID mSessionID; + LLUUID mOwnerID; + LLUUID mTaskID; + LLUUID mItemID; + LLUUID mAssetID; + LLAssetType::EType mAssetType; +}; + + +// Hack, here so it's publicly available even though LLTransferSourceEstate is only available on the simulator +class LLTransferSourceParamsEstate: public LLTransferSourceParams +{ +public: + LLTransferSourceParamsEstate(); + virtual ~LLTransferSourceParamsEstate() {} + /*virtual*/ void packParams(LLDataPacker &dp) const; + /*virtual*/ BOOL unpackParams(LLDataPacker &dp); + + void setAgentSession(const LLUUID &agent_id, const LLUUID &session_id); + void setEstateAssetType(const EstateAssetType etype); + void setAsset(const LLUUID &asset_id, const LLAssetType::EType at); + + LLUUID getAgentID() const { return mAgentID; } + LLUUID getSessionID() const { return mSessionID; } + EstateAssetType getEstateAssetType() const { return mEstateAssetType; } + LLUUID getAssetID() const { return mAssetID; } + LLAssetType::EType getAssetType() const { return mAssetType; } + +protected: + LLUUID mAgentID; + LLUUID mSessionID; + EstateAssetType mEstateAssetType; + // these are set on the sim based on estateinfotype + LLUUID mAssetID; + LLAssetType::EType mAssetType; +}; + + +extern LLTransferManager gTransferManager; + +#endif//LL_LLTRANSFERMANAGER_H diff --git a/indra/llmessage/lltransfersourceasset.cpp b/indra/llmessage/lltransfersourceasset.cpp new file mode 100644 index 0000000000..f7c6711bd0 --- /dev/null +++ b/indra/llmessage/lltransfersourceasset.cpp @@ -0,0 +1,235 @@ +/** + * @file lltransfersourceasset.cpp + * @brief Transfer system for sending an asset. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lltransfersourceasset.h" + +#include "llerror.h" +#include "message.h" +#include "lldatapacker.h" +#include "lldir.h" +#include "llvfile.h" + +LLTransferSourceAsset::LLTransferSourceAsset(const LLUUID &request_id, const F32 priority) : + LLTransferSource(LLTST_ASSET, request_id, priority), + mGotResponse(FALSE), + mCurPos(0) +{ +} + +LLTransferSourceAsset::~LLTransferSourceAsset() +{ +} + + +void LLTransferSourceAsset::initTransfer() +{ + if (gAssetStorage) + { + // *HACK: asset transfers will only be coming from the viewer + // to the simulator. This is subset of assets we allow to be + // simply pulled straight from the asset system. + // *FIX: Make this list smaller. + LLUUID* tidp; + switch(mParams.getAssetType()) + { + case LLAssetType::AT_SOUND: + case LLAssetType::AT_LANDMARK: + case LLAssetType::AT_CLOTHING: + case LLAssetType::AT_BODYPART: + case LLAssetType::AT_GESTURE: + case LLAssetType::AT_ANIMATION: + tidp = new LLUUID(getID()); + gAssetStorage->getAssetData( + mParams.getAssetID(), + mParams.getAssetType(), + LLTransferSourceAsset::responderCallback, + tidp, + FALSE); + break; + default: + llwarns << "Attempted to request blocked asset " + << mParams.getAssetID() << ":" + << LLAssetType::lookupHumanReadable(mParams.getAssetType()) + << llendl; + sendTransferStatus(LLTS_ERROR); + break; + } + } + else + { + llwarns << "Attempted to request asset " + << mParams.getAssetID() << ":" << LLAssetType::lookupHumanReadable(mParams.getAssetType()) + << " without an asset system!" << llendl; + sendTransferStatus(LLTS_ERROR); + } +} + +F32 LLTransferSourceAsset::updatePriority() +{ + return 0.f; +} + +LLTSCode LLTransferSourceAsset::dataCallback(const S32 packet_id, + const S32 max_bytes, + U8 **data_handle, + S32 &returned_bytes, + BOOL &delete_returned) +{ + //llinfos << "LLTransferSourceAsset::dataCallback" << llendl; + if (!mGotResponse) + { + return LLTS_SKIP; + } + + LLVFile vf(gAssetStorage->mVFS, mParams.getAssetID(), mParams.getAssetType(), LLVFile::READ); + + if (!vf.getSize()) + { + // Something bad happened with the asset request! + return LLTS_ERROR; + } + + if (packet_id != mLastPacketID + 1) + { + llerrs << "Can't handle out of order file transfer yet!" << llendl; + } + + // grab a buffer from the right place in the file + if (!vf.seek(mCurPos, 0)) + { + llwarns << "LLTransferSourceAsset Can't seek to " << mCurPos << " length " << vf.getSize() << llendl; + llwarns << "While sending " << mParams.getAssetID() << llendl; + return LLTS_ERROR; + } + + delete_returned = TRUE; + U8 *tmpp = new U8[max_bytes]; + *data_handle = tmpp; + if (!vf.read(tmpp, max_bytes)) /* Flawfinder: Ignore */ + { + // Crap, read failure, need to deal with it. + delete[] tmpp; + *data_handle = NULL; + returned_bytes = 0; + delete_returned = FALSE; + return LLTS_ERROR; + } + + returned_bytes = vf.getLastBytesRead(); + mCurPos += returned_bytes; + + + if (vf.eof()) + { + if (!returned_bytes) + { + delete[] tmpp; + *data_handle = NULL; + returned_bytes = 0; + delete_returned = FALSE; + } + return LLTS_DONE; + } + + return LLTS_OK; +} + +void LLTransferSourceAsset::completionCallback(const LLTSCode status) +{ + // No matter what happens, all we want to do is close the vfile if + // we've got it open. +} + +BOOL LLTransferSourceAsset::unpackParams(LLDataPacker &dp) +{ + //llinfos << "LLTransferSourceAsset::unpackParams" << llendl; + + return mParams.unpackParams(dp); +} + + +void LLTransferSourceAsset::responderCallback(LLVFS *vfs, const LLUUID& uuid, LLAssetType::EType type, + void *user_data, S32 result) +{ + LLUUID *tidp = ((LLUUID*) user_data); + LLUUID transfer_id = *(tidp); + delete tidp; + tidp = NULL; + + LLTransferSourceAsset *tsap = (LLTransferSourceAsset *) gTransferManager.findTransferSource(transfer_id); + + if (!tsap) + { + llinfos << "Aborting transfer " << transfer_id << " callback, transfer source went away" << llendl; + return; + } + + if (result) + { + llinfos << "AssetStorage: Error " << gAssetStorage->getErrorString(result) << " downloading uuid " << uuid << llendl; + } + + LLTSCode status; + + tsap->mGotResponse = TRUE; + if (LL_ERR_NOERR == result) + { + // Everything's OK. + LLVFile vf(gAssetStorage->mVFS, uuid, type, LLVFile::READ); + tsap->mSize = vf.getSize(); + status = LLTS_OK; + } + else + { + // Uh oh, something bad happened when we tried to get this asset! + switch (result) + { + case LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE: + status = LLTS_UNKNOWN_SOURCE; + break; + default: + status = LLTS_ERROR; + } + } + + tsap->sendTransferStatus(status); +} + + + +LLTransferSourceParamsAsset::LLTransferSourceParamsAsset() : LLTransferSourceParams(LLTST_ASSET) +{ +} + +void LLTransferSourceParamsAsset::setAsset(const LLUUID &asset_id, const LLAssetType::EType asset_type) +{ + mAssetID = asset_id; + mAssetType = asset_type; +} + +void LLTransferSourceParamsAsset::packParams(LLDataPacker &dp) const +{ + dp.packUUID(mAssetID, "AssetID"); + dp.packS32(mAssetType, "AssetType"); +} + + +BOOL LLTransferSourceParamsAsset::unpackParams(LLDataPacker &dp) +{ + S32 tmp_at; + + dp.unpackUUID(mAssetID, "AssetID"); + dp.unpackS32(tmp_at, "AssetType"); + + mAssetType = (LLAssetType::EType)tmp_at; + + return TRUE; +} + diff --git a/indra/llmessage/lltransfersourceasset.h b/indra/llmessage/lltransfersourceasset.h new file mode 100644 index 0000000000..931fc461f9 --- /dev/null +++ b/indra/llmessage/lltransfersourceasset.h @@ -0,0 +1,62 @@ +/** + * @file lltransfersourceasset.h + * @brief Transfer system for sending an asset. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTRANSFERSOURCEASSET_H +#define LL_LLTRANSFERSOURCEASSET_H + +#include "lltransfermanager.h" +#include "llassetstorage.h" + +class LLVFile; + +class LLTransferSourceParamsAsset : public LLTransferSourceParams +{ +public: + LLTransferSourceParamsAsset(); + virtual ~LLTransferSourceParamsAsset() {} + /*virtual*/ void packParams(LLDataPacker &dp) const; + /*virtual*/ BOOL unpackParams(LLDataPacker &dp); + + void setAsset(const LLUUID &asset_id, const LLAssetType::EType asset_type); + + LLUUID getAssetID() const { return mAssetID; } + LLAssetType::EType getAssetType() const { return mAssetType; } + +protected: + LLUUID mAssetID; + LLAssetType::EType mAssetType; +}; + +class LLTransferSourceAsset : public LLTransferSource +{ +public: + LLTransferSourceAsset(const LLUUID &request_id, const F32 priority); + virtual ~LLTransferSourceAsset(); + + static void responderCallback(LLVFS *vfs, const LLUUID& uuid, LLAssetType::EType type, + void *user_data, S32 result); +protected: + /*virtual*/ void initTransfer(); + /*virtual*/ F32 updatePriority(); + /*virtual*/ LLTSCode dataCallback(const S32 packet_id, + const S32 max_bytes, + U8 **datap, + S32 &returned_bytes, + BOOL &delete_returned); + /*virtual*/ void completionCallback(const LLTSCode status); + + /*virtual*/ BOOL unpackParams(LLDataPacker &dp); + +protected: + LLTransferSourceParamsAsset mParams; + BOOL mGotResponse; + + S32 mCurPos; +}; + +#endif // LL_LLTRANSFERSOURCEASSET_H diff --git a/indra/llmessage/lltransfersourcefile.cpp b/indra/llmessage/lltransfersourcefile.cpp new file mode 100644 index 0000000000..45b03d7653 --- /dev/null +++ b/indra/llmessage/lltransfersourcefile.cpp @@ -0,0 +1,151 @@ +/** + * @file lltransfersourcefile.cpp + * @brief Transfer system for sending a file. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lltransfersourcefile.h" + +#include "llerror.h" +#include "message.h" +#include "lldatapacker.h" +#include "lldir.h" + +LLTransferSourceFile::LLTransferSourceFile(const LLUUID &request_id, const F32 priority) : + LLTransferSource(LLTST_FILE, request_id, priority), + mFP(NULL) +{ +} + +LLTransferSourceFile::~LLTransferSourceFile() +{ + if (mFP) + { + llerrs << "Destructor called without the completion callback being called!" << llendl; + } +} + +void LLTransferSourceFile::initTransfer() +{ + std::string filename = mParams.getFilename(); + std::string delimiter = gDirUtilp->getDirDelimiter(); + + if((filename == ".") + || (filename == "..") + || (filename.find(delimiter[0]) != std::string::npos)) + { + llwarns << "Attempting to transfer file " << filename << " with path delimiter, aborting!" << llendl; + + sendTransferStatus(LLTS_ERROR); + return; + } + // Look for the file. + mFP = LLFile::fopen(mParams.getFilename().c_str(), "rb"); /* Flawfinder: ignore */ + if (!mFP) + { + sendTransferStatus(LLTS_ERROR); + return; + } + + // Get the size of the file using the hack from + fseek(mFP,0,SEEK_END); + mSize = ftell(mFP); + fseek(mFP,0,SEEK_SET); + + sendTransferStatus(LLTS_OK); +} + +F32 LLTransferSourceFile::updatePriority() +{ + return 0.f; +} + +LLTSCode LLTransferSourceFile::dataCallback(const S32 packet_id, + const S32 max_bytes, + U8 **data_handle, + S32 &returned_bytes, + BOOL &delete_returned) +{ + //llinfos << "LLTransferSourceFile::dataCallback" << llendl; + + if (!mFP) + { + llerrs << "Data callback without file set!" << llendl; + return LLTS_ERROR; + } + + if (packet_id != mLastPacketID + 1) + { + llerrs << "Can't handle out of order file transfer yet!" << llendl; + } + + // Grab up until the max number of bytes from the file. + delete_returned = TRUE; + U8 *tmpp = new U8[max_bytes]; + *data_handle = tmpp; + returned_bytes = (S32)fread(tmpp, 1, max_bytes, mFP); + if (!returned_bytes) + { + delete[] tmpp; + *data_handle = NULL; + returned_bytes = 0; + delete_returned = FALSE; + return LLTS_DONE; + } + + return LLTS_OK; +} + +void LLTransferSourceFile::completionCallback(const LLTSCode status) +{ + // No matter what happens, all we want to do is close the file pointer if + // we've got it open. + if (mFP) + { + fclose(mFP); + mFP = NULL; + + } + // Delete the file iff the filename begins with "TEMP" + if (mParams.getDeleteOnCompletion() && memcmp(mParams.getFilename().c_str(), "TEMP", 4) == 0) + { + LLFile::remove(mParams.getFilename().c_str()); + } +} + +BOOL LLTransferSourceFile::unpackParams(LLDataPacker &dp) +{ + //llinfos << "LLTransferSourceFile::unpackParams" << llendl; + + return mParams.unpackParams(dp); +} + + +LLTransferSourceParamsFile::LLTransferSourceParamsFile() : LLTransferSourceParams(LLTST_FILE) +{ +} + + +void LLTransferSourceParamsFile::packParams(LLDataPacker &dp) const +{ + dp.packString(mFilename.c_str(), "Filename"); + dp.packU8((U8)mDeleteOnCompletion, "Delete"); +} + + +BOOL LLTransferSourceParamsFile::unpackParams(LLDataPacker &dp) +{ + char tmp_str[512]; /* Flawfinder: ignore */ + dp.unpackString(tmp_str, "Filename"); + mFilename = tmp_str; + U8 delete_flag; + dp.unpackU8(delete_flag, "Delete"); + mDeleteOnCompletion = delete_flag; + + llinfos << "Unpacked filename: " << mFilename << llendl; + return TRUE; +} diff --git a/indra/llmessage/lltransfersourcefile.h b/indra/llmessage/lltransfersourcefile.h new file mode 100644 index 0000000000..ecaa6c908a --- /dev/null +++ b/indra/llmessage/lltransfersourcefile.h @@ -0,0 +1,58 @@ +/** + * @file lltransfersourcefile.h + * @brief Transfer system for sending a file. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTRANSFERSOURCEFILE_H +#define LL_LLTRANSFERSOURCEFILE_H + +#include "lltransfermanager.h" + +#include + +class LLTransferSourceParamsFile : public LLTransferSourceParams +{ +public: + LLTransferSourceParamsFile(); + virtual ~LLTransferSourceParamsFile() {} + /*virtual*/ void packParams(LLDataPacker &dp) const; + /*virtual*/ BOOL unpackParams(LLDataPacker &dp); + + void setFilename(const LLString &filename) { mFilename = filename; } + std::string getFilename() const { return mFilename; } + + void setDeleteOnCompletion(BOOL enabled) { mDeleteOnCompletion = enabled; } + BOOL getDeleteOnCompletion() { return mDeleteOnCompletion; } +protected: + std::string mFilename; + // ONLY DELETE THINGS OFF THE SIM IF THE FILENAME BEGINS IN 'TEMP' + BOOL mDeleteOnCompletion; +}; + +class LLTransferSourceFile : public LLTransferSource +{ +public: + LLTransferSourceFile(const LLUUID &transfer_id, const F32 priority); + virtual ~LLTransferSourceFile(); + +protected: + /*virtual*/ void initTransfer(); + /*virtual*/ F32 updatePriority(); + /*virtual*/ LLTSCode dataCallback(const S32 packet_id, + const S32 max_bytes, + U8 **datap, + S32 &returned_bytes, + BOOL &delete_returned); + /*virtual*/ void completionCallback(const LLTSCode status); + + /*virtual*/ BOOL unpackParams(LLDataPacker &dp); + +protected: + LLTransferSourceParamsFile mParams; + FILE *mFP; +}; + +#endif // LL_LLTRANSFERSOURCEFILE_H diff --git a/indra/llmessage/lltransfertargetfile.cpp b/indra/llmessage/lltransfertargetfile.cpp new file mode 100644 index 0000000000..92776e081d --- /dev/null +++ b/indra/llmessage/lltransfertargetfile.cpp @@ -0,0 +1,104 @@ +/** + * @file lltransfertargetfile.cpp + * @brief Transfer system for receiving a file. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lltransfertargetfile.h" +#include "llerror.h" + + + + +LLTransferTargetFile::LLTransferTargetFile(const LLUUID &uuid) : + LLTransferTarget(LLTTT_FILE, uuid), + mFP(NULL) +{ +} + +LLTransferTargetFile::~LLTransferTargetFile() +{ + if (mFP) + { + llerrs << "LLTransferTargetFile::~LLTransferTargetFile - Should have been cleaned up in completion callback" << llendl; + fclose(mFP); + mFP = NULL; + } +} + +void LLTransferTargetFile::applyParams(const LLTransferTargetParams ¶ms) +{ + if (params.getType() != mType) + { + llwarns << "Target parameter type doesn't match!" << llendl; + return; + } + + mParams = (LLTransferTargetParamsFile &)params; +} + +LLTSCode LLTransferTargetFile::dataCallback(const S32 packet_id, U8 *in_datap, const S32 in_size) +{ + //llinfos << "LLTransferTargetFile::dataCallback" << llendl; + //llinfos << "Packet: " << packet_id << llendl; + + if (!mFP) + { + mFP = LLFile::fopen(mParams.mFilename.c_str(), "wb"); /* Flawfinder: ignore */ + + if (!mFP) + { + llwarns << "Failure opening " << mParams.mFilename << " for write by LLTransferTargetFile" << llendl; + return LLTS_ERROR; + } + } + if (!in_size) + { + return LLTS_OK; + } + + S32 count = (S32)fwrite(in_datap, 1, in_size, mFP); + if (count != in_size) + { + llwarns << "Failure in LLTransferTargetFile::dataCallback!" << llendl; + return LLTS_ERROR; + } + return LLTS_OK; +} + +void LLTransferTargetFile::completionCallback(const LLTSCode status) +{ + llinfos << "LLTransferTargetFile::completionCallback" << llendl; + if (mFP) + { + fclose(mFP); + } + + // Still need to gracefully handle error conditions. + switch (status) + { + case LLTS_DONE: + break; + case LLTS_ABORT: + case LLTS_ERROR: + // We're aborting this transfer, we don't want to keep this file. + llwarns << "Aborting file transfer for " << mParams.mFilename << llendl; + if (mFP) + { + // Only need to remove file if we successfully opened it. + LLFile::remove(mParams.mFilename.c_str()); + } + default: + break; + } + + mFP = NULL; + if (mParams.mCompleteCallback) + { + mParams.mCompleteCallback(status, mParams.mUserData); + } +} diff --git a/indra/llmessage/lltransfertargetfile.h b/indra/llmessage/lltransfertargetfile.h new file mode 100644 index 0000000000..63cc21262b --- /dev/null +++ b/indra/llmessage/lltransfertargetfile.h @@ -0,0 +1,53 @@ +/** + * @file lltransfertargetfile.h + * @brief Transfer system for receiving a file. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTRANSFERTARGETFILE_H +#define LL_LLTRANSFERTARGETFILE_H + +#include "lltransfermanager.h" + +#include + +typedef void (*LLTTFCompleteCallback)(const LLTSCode status, void *user_data); + +class LLTransferTargetParamsFile : public LLTransferTargetParams +{ +public: + LLTransferTargetParamsFile() : LLTransferTargetParams(LLTTT_FILE) {} + void setFilename(const char *filename) { mFilename = filename; } + void setCallback(LLTTFCompleteCallback cb, void *user_data) { mCompleteCallback = cb; mUserData = user_data; } + + friend class LLTransferTargetFile; +protected: + LLString mFilename; + LLTTFCompleteCallback mCompleteCallback; + void * mUserData; +}; + + +class LLTransferTargetFile : public LLTransferTarget +{ +public: + LLTransferTargetFile(const LLUUID &uuid); + virtual ~LLTransferTargetFile(); + + static void requestTransfer(LLTransferTargetChannel *channelp, + const char *local_filename, + const LLTransferSourceParams &source_params, + LLTTFCompleteCallback callback); +protected: + /*virtual*/ void applyParams(const LLTransferTargetParams ¶ms); + /*virtual*/ LLTSCode dataCallback(const S32 packet_id, U8 *in_datap, const S32 in_size); + /*virtual*/ void completionCallback(const LLTSCode status); + + LLTransferTargetParamsFile mParams; + + FILE *mFP; +}; + +#endif // LL_LLTRANSFERTARGETFILE_H diff --git a/indra/llmessage/lltransfertargetvfile.cpp b/indra/llmessage/lltransfertargetvfile.cpp new file mode 100644 index 0000000000..9e323537d7 --- /dev/null +++ b/indra/llmessage/lltransfertargetvfile.cpp @@ -0,0 +1,187 @@ +/** + * @file lltransfertargetvfile.cpp + * @brief Transfer system for receiving a vfile. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lltransfertargetvfile.h" +#include "llerror.h" + +#include "llvfile.h" + +//static +std::list LLTransferTargetVFile::sCallbackQueue; + +//static +void LLTransferTargetVFile::updateQueue(bool shutdown) +{ + for(std::list::iterator iter = sCallbackQueue.begin(); + iter != sCallbackQueue.end(); ) + { + std::list::iterator curiter = iter++; + LLTransferTargetParamsVFile* params = *curiter; + LLVFSThread::status_t s = LLVFile::getVFSThread()->getRequestStatus(params->mHandle); + if (s == LLVFSThread::STATUS_COMPLETE || s == LLVFSThread::STATUS_EXPIRED) + { + params->mCompleteCallback(params->mErrCode, params->mUserDatap); + delete params; + iter = sCallbackQueue.erase(curiter); + } + else if (shutdown) + { + delete params; + iter = sCallbackQueue.erase(curiter); + } + } +} + + +LLTransferTargetParamsVFile::LLTransferTargetParamsVFile() : + LLTransferTargetParams(LLTTT_VFILE), + mAssetType(LLAssetType::AT_NONE), + mCompleteCallback(NULL), + mUserDatap(NULL), + mErrCode(0), + mHandle(LLVFSThread::nullHandle()) +{ +} + +void LLTransferTargetParamsVFile::setAsset(const LLUUID &asset_id, const LLAssetType::EType asset_type) +{ + mAssetID = asset_id; + mAssetType = asset_type; +} + +void LLTransferTargetParamsVFile::setCallback(LLTTVFCompleteCallback cb, void *user_data) +{ + mCompleteCallback = cb; + mUserDatap = user_data; +} + + +LLTransferTargetVFile::LLTransferTargetVFile(const LLUUID &uuid) : + LLTransferTarget(LLTTT_VFILE, uuid), + mNeedsCreate(TRUE) +{ + mTempID.generate(); +} + + +LLTransferTargetVFile::~LLTransferTargetVFile() +{ +} + + +void LLTransferTargetVFile::applyParams(const LLTransferTargetParams ¶ms) +{ + if (params.getType() != mType) + { + llwarns << "Target parameter type doesn't match!" << llendl; + return; + } + + mParams = (LLTransferTargetParamsVFile &)params; +} + + +LLTSCode LLTransferTargetVFile::dataCallback(const S32 packet_id, U8 *in_datap, const S32 in_size) +{ + //llinfos << "LLTransferTargetFile::dataCallback" << llendl; + //llinfos << "Packet: " << packet_id << llendl; + + LLVFile vf(gAssetStorage->mVFS, mTempID, mParams.getAssetType(), LLVFile::APPEND); + if (mNeedsCreate) + { + vf.setMaxSize(mSize); + mNeedsCreate = FALSE; + } + + if (!in_size) + { + return LLTS_OK; + } + + if (!vf.write(in_datap, in_size)) + { + llwarns << "Failure in LLTransferTargetVFile::dataCallback!" << llendl; + return LLTS_ERROR; + } + return LLTS_OK; +} + + +void LLTransferTargetVFile::completionCallback(const LLTSCode status) +{ + //llinfos << "LLTransferTargetVFile::completionCallback" << llendl; + + if (!gAssetStorage) + { + llwarns << "Aborting vfile transfer after asset storage shut down!" << llendl; + return; + } + LLVFSThread::handle_t handle = LLVFSThread::nullHandle(); + + // Still need to gracefully handle error conditions. + S32 err_code = 0; + switch (status) + { + case LLTS_DONE: + if (!mNeedsCreate) + { + handle = LLVFile::getVFSThread()->rename(gAssetStorage->mVFS, + mTempID, mParams.getAssetType(), + mParams.getAssetID(), mParams.getAssetType(), + LLVFSThread::AUTO_DELETE); + } + err_code = LL_ERR_NOERR; + // llinfos << "Successful vfile transfer for " << mParams.getAssetID() << llendl; + break; + case LLTS_ERROR: + case LLTS_ABORT: + case LLTS_UNKNOWN_SOURCE: + default: + { + // We're aborting this transfer, we don't want to keep this file. + llwarns << "Aborting vfile transfer for " << mParams.getAssetID() << llendl; + LLVFile vf(gAssetStorage->mVFS, mTempID, mParams.getAssetType(), LLVFile::APPEND); + vf.remove(); + } + break; + } + + switch (status) + { + case LLTS_DONE: + err_code = LL_ERR_NOERR; + break; + case LLTS_UNKNOWN_SOURCE: + err_code = LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE; + break; + case LLTS_INSUFFICIENT_PERMISSIONS: + err_code = LL_ERR_INSUFFICIENT_PERMISSIONS; + break; + case LLTS_ERROR: + case LLTS_ABORT: + default: + err_code = LL_ERR_ASSET_REQUEST_FAILED; + break; + } + if (mParams.mCompleteCallback) + { + if (handle != LLVFSThread::nullHandle()) + { + LLTransferTargetParamsVFile* params = new LLTransferTargetParamsVFile(mParams); + params->mErrCode = err_code; + params->mHandle = handle; + sCallbackQueue.push_back(params); + } + else + { + mParams.mCompleteCallback(err_code, mParams.mUserDatap); + } + } +} diff --git a/indra/llmessage/lltransfertargetvfile.h b/indra/llmessage/lltransfertargetvfile.h new file mode 100644 index 0000000000..7614021179 --- /dev/null +++ b/indra/llmessage/lltransfertargetvfile.h @@ -0,0 +1,70 @@ +/** + * @file lltransfertargetvfile.h + * @brief Transfer system for receiving a vfile. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTRANSFERTARGETVFILE_H +#define LL_LLTRANSFERTARGETVFILE_H + +#include "lltransfermanager.h" +#include "llassetstorage.h" +#include "llvfile.h" + +class LLVFile; + +// Lame, an S32 for now until I figure out the deal with how we want to do +// error codes. +typedef void (*LLTTVFCompleteCallback)(const S32 status, void *user_data); + +class LLTransferTargetParamsVFile : public LLTransferTargetParams +{ +public: + LLTransferTargetParamsVFile(); + + void setAsset(const LLUUID &asset_id, const LLAssetType::EType asset_type); + void setCallback(LLTTVFCompleteCallback cb, void *user_data); + + LLUUID getAssetID() const { return mAssetID; } + LLAssetType::EType getAssetType() const { return mAssetType; } + + friend class LLTransferTargetVFile; +protected: + LLUUID mAssetID; + LLAssetType::EType mAssetType; + + LLTTVFCompleteCallback mCompleteCallback; + void * mUserDatap; + S32 mErrCode; + LLVFSThread::handle_t mHandle; +}; + + +class LLTransferTargetVFile : public LLTransferTarget +{ +public: + LLTransferTargetVFile(const LLUUID &uuid); + virtual ~LLTransferTargetVFile(); + + static void requestTransfer(LLTransferTargetChannel *channelp, + const char *local_filename, + const LLTransferSourceParams &source_params, + LLTTVFCompleteCallback callback); + static void updateQueue(bool shutdown = false); + +protected: + /*virtual*/ void applyParams(const LLTransferTargetParams ¶ms); + /*virtual*/ LLTSCode dataCallback(const S32 packet_id, U8 *in_datap, const S32 in_size); + /*virtual*/ void completionCallback(const LLTSCode status); + + LLTransferTargetParamsVFile mParams; + + BOOL mNeedsCreate; + LLUUID mTempID; + + static std::list sCallbackQueue; +}; + +#endif // LL_LLTRANSFERTARGETFILE_H diff --git a/indra/llmessage/llurlrequest.cpp b/indra/llmessage/llurlrequest.cpp new file mode 100644 index 0000000000..ea0b13f703 --- /dev/null +++ b/indra/llmessage/llurlrequest.cpp @@ -0,0 +1,650 @@ +/** + * @file llurlrequest.cpp + * @author Phoenix + * @date 2005-04-28 + * @brief Implementation of the URLRequest class and related classes. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llurlrequest.h" + +#include +#include + +#include "llioutil.h" +#include "llmemtype.h" +#include "llpumpio.h" +#include "llsd.h" +#include "llstring.h" + +static const U32 HTTP_STATUS_PIPE_ERROR = 499; + +/** + * String constants + */ +const std::string CONTEXT_DEST_URI_SD_LABEL("dest_uri"); + + +static +size_t headerCallback(void* data, size_t size, size_t nmemb, void* user); + +/** + * class LLURLRequestDetail + */ +class LLURLRequestDetail +{ +public: + LLURLRequestDetail(); + ~LLURLRequestDetail(); + CURLM* mCurlMulti; + CURL* mCurl; + struct curl_slist* mHeaders; + char* mURL; + char mCurlErrorBuf[CURL_ERROR_SIZE + 1]; /* Flawfinder: ignore */ + bool mNeedToRemoveEasyHandle; + LLBufferArray* mResponseBuffer; + LLChannelDescriptors mChannels; + U8* mLastRead; + U32 mBodyLimit; + bool mIsBodyLimitSet; +}; + +LLURLRequestDetail::LLURLRequestDetail() : + mCurlMulti(NULL), + mCurl(NULL), + mHeaders(NULL), + mURL(NULL), + mNeedToRemoveEasyHandle(false), + mResponseBuffer(NULL), + mLastRead(NULL), + mBodyLimit(0), + mIsBodyLimitSet(false) + +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + mCurlErrorBuf[0] = '\0'; +} + +LLURLRequestDetail::~LLURLRequestDetail() +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + if(mCurl) + { + if(mNeedToRemoveEasyHandle && mCurlMulti) + { + curl_multi_remove_handle(mCurlMulti, mCurl); + mNeedToRemoveEasyHandle = false; + } + curl_easy_cleanup(mCurl); + mCurl = NULL; + } + if(mCurlMulti) + { + curl_multi_cleanup(mCurlMulti); + mCurlMulti = NULL; + } + if(mHeaders) + { + curl_slist_free_all(mHeaders); + mHeaders = NULL; + } + delete[] mURL; + mURL = NULL; + mResponseBuffer = NULL; + mLastRead = NULL; +} + + +/** + * class LLURLRequest + */ + +static std::string sCAFile(""); +static std::string sCAPath(""); + +LLURLRequest::LLURLRequest(LLURLRequest::ERequestAction action) : + mAction(action) +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + initialize(); +} + +LLURLRequest::LLURLRequest( + LLURLRequest::ERequestAction action, + const std::string& url) : + mAction(action) +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + initialize(); + setURL(url); +} + +LLURLRequest::~LLURLRequest() +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + delete mDetail; +} + +void LLURLRequest::setURL(const std::string& url) +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + if(mDetail->mURL) + { + // *NOTE: if any calls to set the url have been made to curl, + // this will probably lead to a crash. + delete[] mDetail->mURL; + mDetail->mURL = NULL; + } + if(!url.empty()) + { + mDetail->mURL = new char[url.size() + 1]; + url.copy(mDetail->mURL, url.size()); + mDetail->mURL[url.size()] = '\0'; + } +} + +void LLURLRequest::addHeader(const char* header) +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + mDetail->mHeaders = curl_slist_append(mDetail->mHeaders, header); +} + +void LLURLRequest::requestEncoding(const char* encoding) +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + curl_easy_setopt(mDetail->mCurl, CURLOPT_ENCODING, encoding); +} + +void LLURLRequest::setBodyLimit(U32 size) +{ + mDetail->mBodyLimit = size; + mDetail->mIsBodyLimitSet = true; +} + +void LLURLRequest::checkRootCertificate(bool check, const char* caBundle) +{ + curl_easy_setopt(mDetail->mCurl, CURLOPT_SSL_VERIFYPEER, (check? TRUE : FALSE)); + if (caBundle) + { + curl_easy_setopt(mDetail->mCurl, CURLOPT_CAINFO, caBundle); + } +} + +void LLURLRequest::setCallback(LLURLRequestComplete* callback) +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + mCompletionCallback = callback; + + curl_easy_setopt(mDetail->mCurl, CURLOPT_HEADERFUNCTION, &headerCallback); + curl_easy_setopt(mDetail->mCurl, CURLOPT_WRITEHEADER, callback); +} + +// virtual +LLIOPipe::EStatus LLURLRequest::handleError( + LLIOPipe::EStatus status, + LLPumpIO* pump) +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + if(mCompletionCallback && pump) + { + LLURLRequestComplete* complete = NULL; + complete = (LLURLRequestComplete*)mCompletionCallback.get(); + complete->httpStatus( + HTTP_STATUS_PIPE_ERROR, + LLIOPipe::lookupStatusString(status)); + complete->responseStatus(status); + pump->respond(complete); + mCompletionCallback = NULL; + } + return status; +} + +// virtual +LLIOPipe::EStatus LLURLRequest::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + //llinfos << "LLURLRequest::process_impl()" << llendl; + if(!buffer) return STATUS_ERROR; + switch(mState) + { + case STATE_INITIALIZED: + { + PUMP_DEBUG; + // We only need to wait for input if we are uploading + // something. + if(((HTTP_PUT == mAction) || (HTTP_POST == mAction)) && !eos) + { + // we're waiting to get all of the information + return STATUS_BREAK; + } + + // *FIX: bit of a hack, but it should work. The configure and + // callback method expect this information to be ready. + mDetail->mResponseBuffer = buffer.get(); + mDetail->mChannels = channels; + if(!configure()) + { + return STATUS_ERROR; + } + mState = STATE_WAITING_FOR_RESPONSE; + + // *FIX: Maybe we should just go to the next state now... + return STATUS_BREAK; + } + case STATE_WAITING_FOR_RESPONSE: + case STATE_PROCESSING_RESPONSE: + { + PUMP_DEBUG; + const S32 MAX_CALLS = 5; + S32 count = MAX_CALLS; + CURLMcode code; + LLIOPipe::EStatus status = STATUS_BREAK; + S32 queue; + do + { + code = curl_multi_perform(mDetail->mCurlMulti, &queue); + }while((CURLM_CALL_MULTI_PERFORM == code) && (queue > 0) && count--); + CURLMsg* curl_msg; + do + { + curl_msg = curl_multi_info_read(mDetail->mCurlMulti, &queue); + if(curl_msg && (curl_msg->msg == CURLMSG_DONE)) + { + mState = STATE_HAVE_RESPONSE; + + CURLcode result = curl_msg->data.result; + switch(result) + { + case CURLE_OK: + case CURLE_WRITE_ERROR: + // NB: The error indication means that we stopped the + // writing due the body limit being reached + if(mCompletionCallback && pump) + { + LLURLRequestComplete* complete = NULL; + complete = (LLURLRequestComplete*) + mCompletionCallback.get(); + complete->responseStatus( + result == CURLE_OK + ? STATUS_OK : STATUS_STOP); + LLPumpIO::links_t chain; + LLPumpIO::LLLinkInfo link; + link.mPipe = mCompletionCallback; + link.mChannels = LLBufferArray::makeChannelConsumer( + channels); + chain.push_back(link); + pump->respond(chain, buffer, context); + mCompletionCallback = NULL; + } + break; + case CURLE_COULDNT_CONNECT: + status = STATUS_NO_CONNECTION; + break; + default: + llwarns << "URLRequest Error: " << curl_msg->data.result + << ", " +#if LL_DARWIN + // curl_easy_strerror was added in libcurl 7.12.0. Unfortunately, the version in the Mac OS X 10.3.9 SDK is 7.10.2... + // There's a problem with the custom curl headers in our build that keeps me from #ifdefing this on the libcurl version number + // (the correct check would be #if LIBCURL_VERSION_NUM >= 0x070c00). We'll fix the header problem soon, but for now + // just punt and print the numeric error code on the Mac. + << curl_msg->data.result +#else // LL_DARWIN + << curl_easy_strerror(curl_msg->data.result) +#endif // LL_DARWIN + << ", " + << (mDetail->mURL ? mDetail->mURL : "") + << llendl; + status = STATUS_ERROR; + break; + } + curl_multi_remove_handle(mDetail->mCurlMulti, mDetail->mCurl); + mDetail->mNeedToRemoveEasyHandle = false; + } + }while(curl_msg && (queue > 0)); + return status; + } + case STATE_HAVE_RESPONSE: + PUMP_DEBUG; + // we already stuffed everything into channel in in the curl + // callback, so we are done. + eos = true; + return STATUS_DONE; + + default: + PUMP_DEBUG; + return STATUS_ERROR; + } +} + +void LLURLRequest::initialize() +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + mState = STATE_INITIALIZED; + mDetail = new LLURLRequestDetail; + mDetail->mCurl = curl_easy_init(); + mDetail->mCurlMulti = curl_multi_init(); + curl_easy_setopt(mDetail->mCurl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(mDetail->mCurl, CURLOPT_WRITEFUNCTION, &downCallback); + curl_easy_setopt(mDetail->mCurl, CURLOPT_WRITEDATA, this); + curl_easy_setopt(mDetail->mCurl, CURLOPT_READFUNCTION, &upCallback); + curl_easy_setopt(mDetail->mCurl, CURLOPT_READDATA, this); + curl_easy_setopt( + mDetail->mCurl, + CURLOPT_ERRORBUFFER, + mDetail->mCurlErrorBuf); + + if(sCAPath != std::string("")) + { + curl_easy_setopt(mDetail->mCurl, CURLOPT_CAPATH, sCAPath.c_str()); + } + if(sCAFile != std::string("")) + { + curl_easy_setopt(mDetail->mCurl, CURLOPT_CAINFO, sCAFile.c_str()); + } +} + +bool LLURLRequest::configure() +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + bool rv = false; + S32 bytes = mDetail->mResponseBuffer->countAfter( + mDetail->mChannels.in(), + NULL); + switch(mAction) + { + case HTTP_GET: + curl_easy_setopt(mDetail->mCurl, CURLOPT_HTTPGET, 1); + curl_easy_setopt(mDetail->mCurl, CURLOPT_FOLLOWLOCATION, 1); + rv = true; + break; + + case HTTP_PUT: + // Disable the expect http 1.1 extension. POST and PUT default + // to turning this on, and I am not too sure what it means. + addHeader("Expect:"); + + curl_easy_setopt(mDetail->mCurl, CURLOPT_UPLOAD, 1); + curl_easy_setopt(mDetail->mCurl, CURLOPT_INFILESIZE, bytes); + rv = true; + break; + + case HTTP_POST: + // Disable the expect http 1.1 extension. POST and PUT default + // to turning this on, and I am not too sure what it means. + addHeader("Expect:"); + + // Disable the content type http header. + // *FIX: what should it be? + addHeader("Content-Type:"); + + // Set the handle for an http post + curl_easy_setopt(mDetail->mCurl, CURLOPT_POST, 1); + curl_easy_setopt(mDetail->mCurl, CURLOPT_POSTFIELDS, NULL); + curl_easy_setopt(mDetail->mCurl, CURLOPT_POSTFIELDSIZE, bytes); + rv = true; + break; + + case HTTP_DELETE: + // Set the handle for an http post + curl_easy_setopt(mDetail->mCurl, CURLOPT_CUSTOMREQUEST, "DELETE"); + rv = true; + break; + + default: + llwarns << "Unhandled URLRequest action: " << mAction << llendl; + break; + } + if(rv) + { + if(mDetail->mHeaders) + { + curl_easy_setopt( + mDetail->mCurl, + CURLOPT_HTTPHEADER, + mDetail->mHeaders); + } + curl_easy_setopt(mDetail->mCurl, CURLOPT_URL, mDetail->mURL); + curl_multi_add_handle(mDetail->mCurlMulti, mDetail->mCurl); + mDetail->mNeedToRemoveEasyHandle = true; + } + return rv; +} + +// static +size_t LLURLRequest::downCallback( + void* data, + size_t size, + size_t nmemb, + void* user) +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + LLURLRequest* req = (LLURLRequest*)user; + if(STATE_WAITING_FOR_RESPONSE == req->mState) + { + req->mState = STATE_PROCESSING_RESPONSE; + } + U32 bytes = size * nmemb; + if (req->mDetail->mIsBodyLimitSet) + { + if (bytes > req->mDetail->mBodyLimit) + { + bytes = req->mDetail->mBodyLimit; + req->mDetail->mBodyLimit = 0; + } + else + { + req->mDetail->mBodyLimit -= bytes; + } + } + + req->mDetail->mResponseBuffer->append( + req->mDetail->mChannels.out(), + (U8*)data, + bytes); + return bytes; +} + +// static +size_t LLURLRequest::upCallback( + void* data, + size_t size, + size_t nmemb, + void* user) +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + LLURLRequest* req = (LLURLRequest*)user; + S32 bytes = llmin( + (S32)(size * nmemb), + req->mDetail->mResponseBuffer->countAfter( + req->mDetail->mChannels.in(), + req->mDetail->mLastRead)); + req->mDetail->mLastRead = req->mDetail->mResponseBuffer->readAfter( + req->mDetail->mChannels.in(), + req->mDetail->mLastRead, + (U8*)data, + bytes); + return bytes; +} + +static +size_t headerCallback(void* data, size_t size, size_t nmemb, void* user) +{ + const char* headerLine = (const char*)data; + size_t headerLen = size * nmemb; + LLURLRequestComplete* complete = (LLURLRequestComplete*)user; + + // FIXME: This should be a utility in llstring.h: isascii() + for (size_t i = 0; i < headerLen; ++i) + { + if (headerLine[i] < 0) + { + return headerLen; + } + } + + size_t sep; + for (sep = 0; sep < headerLen && headerLine[sep] != ':'; ++sep) { } + + if (sep < headerLen && complete) + { + std::string key(headerLine, sep); + std::string value(headerLine + sep + 1, headerLen - sep - 1); + + key = utf8str_tolower(utf8str_trim(key)); + value = utf8str_trim(value); + + complete->header(key, value); + } + else + { + std::string s(headerLine, headerLen); + + std::string::iterator end = s.end(); + std::string::iterator pos1 = std::find(s.begin(), end, ' '); + if (pos1 != end) ++pos1; + std::string::iterator pos2 = std::find(pos1, end, ' '); + if (pos2 != end) ++pos2; + std::string::iterator pos3 = std::find(pos2, end, '\r'); + + std::string version(s.begin(), pos1); + std::string status(pos1, pos2); + std::string reason(pos2, pos3); + + int statusCode = atoi(status.c_str()); + if (statusCode > 0) + { + complete->httpStatus((U32)statusCode, reason); + } + } + + return headerLen; +} + +//static +void LLURLRequest::setCertificateAuthorityFile(const std::string& file_name) +{ + sCAFile = file_name; +} + +//static +void LLURLRequest::setCertificateAuthorityPath(const std::string& path) +{ + sCAPath = path; +} + +/** + * LLContextURLExtractor + */ +// virtual +LLIOPipe::EStatus LLContextURLExtractor::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + // The destination host is in the context. + if(context.isUndefined() || !mRequest) + { + return STATUS_PRECONDITION_NOT_MET; + } + + // copy in to out, since this just extract the URL and does not + // actually change the data. + LLChangeChannel change(channels.in(), channels.out()); + std::for_each(buffer->beginSegment(), buffer->endSegment(), change); + + // find the context url + if(context.has(CONTEXT_DEST_URI_SD_LABEL)) + { + mRequest->setURL(context[CONTEXT_DEST_URI_SD_LABEL]); + return STATUS_DONE; + } + return STATUS_ERROR; +} + + +/** + * LLURLRequestComplete + */ +LLURLRequestComplete::LLURLRequestComplete() : + mRequestStatus(LLIOPipe::STATUS_ERROR) +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); +} + +// virtual +LLURLRequestComplete::~LLURLRequestComplete() +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); +} + +//virtual +void LLURLRequestComplete::header(const std::string& header, const std::string& value) +{ +} + +//virtual +void LLURLRequestComplete::httpStatus(U32 status, const std::string& reason) +{ +} + +//virtual +void LLURLRequestComplete::complete(const LLChannelDescriptors& channels, + const buffer_ptr_t& buffer) +{ + if(STATUS_OK == mRequestStatus) + { + response(channels, buffer); + } + else + { + noResponse(); + } +} + +//virtual +void LLURLRequestComplete::response(const LLChannelDescriptors& channels, + const buffer_ptr_t& buffer) +{ + llwarns << "LLURLRequestComplete::response default implementation called" + << llendl; +} + +//virtual +void LLURLRequestComplete::noResponse() +{ + llwarns << "LLURLRequestComplete::noResponse default implementation called" + << llendl; +} + +void LLURLRequestComplete::responseStatus(LLIOPipe::EStatus status) +{ + LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + mRequestStatus = status; +} + +// virtual +LLIOPipe::EStatus LLURLRequestComplete::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + complete(channels, buffer); + return STATUS_OK; +} diff --git a/indra/llmessage/llurlrequest.h b/indra/llmessage/llurlrequest.h new file mode 100644 index 0000000000..38c801cb10 --- /dev/null +++ b/indra/llmessage/llurlrequest.h @@ -0,0 +1,395 @@ +/** + * @file llurlrequest.h + * @author Phoenix + * @date 2005-04-21 + * @brief Declaration of url based requests on pipes. + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLURLREQUEST_H +#define LL_LLURLREQUEST_H + +/** + * This file holds the declaration of useful classes for dealing with + * url based client requests. + */ + +#include +#include "lliopipe.h" +#include "llchainio.h" + +class LLURLRequestDetail; + +class LLURLRequestComplete; + +/** + * @class LLURLRequest + * @brief Class to handle url based requests. + * @see LLIOPipe + * + * Currently, this class is implemented on top of curl. From the + * vantage of a programmer using this class, you do not care so much, + * but it's useful to know since in order to accomplish 'non-blocking' + * behavior, we have to use a more expensive curl interface which can + * still block if the server enters a half-accepted state. It would be + * worth the time and effort to eventually port this to a raw client + * socket. + */ +class LLURLRequest : public LLIOPipe +{ +public: + /** + * @brief This enumeration is for specifying the type of request. + */ + enum ERequestAction + { + INVALID, + HTTP_GET, + HTTP_PUT, + HTTP_POST, + HTTP_DELETE, + REQUEST_ACTION_COUNT + }; + + /** + * @brief Constructor. + * + * @param action One of the ERequestAction enumerations. + */ + LLURLRequest(ERequestAction action); + + /** + * @brief Constructor. + * + * @param action One of the ERequestAction enumerations. + * @param url The url of the request. It should already be encoded. + */ + LLURLRequest(ERequestAction action, const std::string& url); + + /** + * @brief Destructor. + */ + virtual ~LLURLRequest(); + + /* @name Instance methods + */ + //@{ + /** + * @brief Set the url for the request + * + * This method assumes the url is encoded appropriately for the + * request. + * The url must be set somehow before the first call to process(), + * or the url will not be set correctly. + * + */ + void setURL(const std::string& url); + + /** + * @brief Add a header to the http post. + * + * The header must be correctly formatted for HTTP requests. This + * provides a raw interface if you know what kind of request you + * will be making during construction of this instance. All + * required headers will be automatically constructed, so this is + * usually useful for encoding parameters. + */ + void addHeader(const char* header); + + /** + * @brief Check remote server certificate signed by a known root CA. + * + * Set whether request will check that remote server + * certificates are signed by a known root CA when using HTTPS. + * Use the supplied root certificate bundle if supplied, else use + * the standard bundle as found by libcurl and openssl. + */ + void checkRootCertificate(bool check, const char* caBundle = NULL); + + /** + * @brief Request a particular response encoding if available. + * + * This call is a shortcut for requesting a particular encoding + * from the server, eg, 'gzip'. + */ + void requestEncoding(const char* encoding); + + /** + * @brief Return at most size bytes of body. + * + * If the body had more bytes than this limit, they will not be + * returned and the connection closed. In this case, STATUS_STOP + * will be passed to responseStatus(); + */ + void setBodyLimit(U32 size); + + /** + * @brief Set a completion callback for this URLRequest. + * + * The callback is added to this URLRequet's pump when either the + * entire buffer is known or an error like timeout or connection + * refused has happened. In the case of a complete transfer, this + * object builds a response chain such that the callback and the + * next process consumer get to read the output. + * + * This setup is a little fragile since the url request consumer + * might not just read the data - it may do a channel change, + * which invalidates the input to the callback, but it works well + * in practice. + */ + void setCallback(LLURLRequestComplete* callback); + //@} + + /** + * @ brief Set certificate authority file used to verify HTTPS certs. + */ + static void setCertificateAuthorityFile(const std::string& file_name); + + /** + * @ brief Set certificate authority path used to verify HTTPS certs. + */ + static void setCertificateAuthorityPath(const std::string& path); + + /* @name LLIOPipe virtual implementations + */ +public: + /** + * @brief Give this pipe a chance to handle a generated error + */ + virtual EStatus handleError(EStatus status, LLPumpIO* pump); + +protected: + /** + * @brief Process the data in buffer + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: + enum EState + { + STATE_INITIALIZED, + STATE_WAITING_FOR_RESPONSE, + STATE_PROCESSING_RESPONSE, + STATE_HAVE_RESPONSE, + }; + EState mState; + ERequestAction mAction; + LLURLRequestDetail* mDetail; + LLIOPipe::ptr_t mCompletionCallback; + +private: + /** + * @brief Initialize the object. Called during construction. + */ + void initialize(); + + /** + * @brief Handle action specific url request configuration. + * + * @return Returns true if this is configured. + */ + bool configure(); + + /** + * @brief Download callback method. + */ + static size_t downCallback( + void* data, + size_t size, + size_t nmemb, + void* user); + + /** + * @brief Upload callback method. + */ + static size_t upCallback( + void* data, + size_t size, + size_t nmemb, + void* user); + + /** + * @brief Declaration of unimplemented method to prevent copy + * construction. + */ + LLURLRequest(const LLURLRequest&); +}; + + +/** + * @class LLContextURLExtractor + * @brief This class unpacks the url out of a agent usher service so + * it can be packed into a LLURLRequest object. + * @see LLIOPipe + * + * This class assumes that the context is a map that contains an entry + * named CONTEXT_DEST_URI_SD_LABEL. + */ +class LLContextURLExtractor : public LLIOPipe +{ +public: + LLContextURLExtractor(LLURLRequest* req) : mRequest(req) {} + ~LLContextURLExtractor() {} + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data in buffer + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: + LLURLRequest* mRequest; +}; + + +/** + * @class LLURLRequestComplete + * @brief Class which can optionally be used with an LLURLRequest to + * get notification when the url request is complete. + */ +class LLURLRequestComplete : public LLIOPipe +{ +public: + + virtual void header(const std::string& header, const std::string& value); + ///< Called once for each header received, prior to httpStatus + + virtual void httpStatus(U32 status, const std::string& reason); + ///< Always called on request completion, prior to complete + + virtual void complete( + const LLChannelDescriptors& channels, + const buffer_ptr_t& buffer); + + /** + * @brief This method is called when we got a valid response. + * + * It is up to class implementers to do something useful here. + */ + virtual void response( + const LLChannelDescriptors& channels, + const buffer_ptr_t& buffer); + + /** + * @brief This method is called if there was no response. + * + * It is up to class implementers to do something useful here. + */ + virtual void noResponse(); + + /** + * @brief This method will be called by the LLURLRequest object. + * + * If this is set to STATUS_OK or STATUS_STOP, then the transfer + * is asssumed to have worked. This will lead to calling response() + * on the next call to process(). Otherwise, this object will call + * noResponse() on the next call to process. + * @param status The status of the URLRequest. + */ + void responseStatus(EStatus status); + + // constructor & destructor. + LLURLRequestComplete(); + virtual ~LLURLRequestComplete(); + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data in buffer + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + + // value to note if we actually got the response. This value + // depends on correct useage from the LLURLRequest instance. + EStatus mRequestStatus; +}; + + +/** + * @class LLURLRequestClientFactory + * @brief Template class to build url request based client chains + * + * This class eases construction of a basic sd rpc client. Here is an + * example of it's use: + * + * class LLUsefulService : public LLService { ... }
+ * LLService::registerCreator(
+ * "useful",
+ * LLService::creator_t(new LLURLRequestClientFactory))
+ *
+ * + * This class should work, but I never got around to using/testing it. + * + */ +#if 0 +template +class LLURLRequestClientFactory : public LLChainIOFactory +{ +public: + LLURLRequestClientFactory(LLURLRequest::ERequestAction action) {} + LLURLRequestClientFactory( + LLURLRequest::ERequestAction action, + const std::string& fixed_url) : + mAction(action), + mURL(fixed_url) + { + } + virtual bool build(LLPumpIO::chain_t& chain, LLSD context) const + { + lldebugs << "LLURLRequestClientFactory::build" << llendl; + LLIOPipe::ptr_t service(new Client); + chain.push_back(service); + LLURLRequest* http(new LLURLRequest(mAction)); + LLIOPipe::ptr_t http_pipe(http); + // *FIX: how do we know the content type? + //http->addHeader("Content-Type: text/llsd"); + if(mURL.empty()) + { + chain.push_back(LLIOPipe::ptr_t(new LLContextURLExtractor(http))); + } + else + { + http->setURL(mURL); + } + chain.push_back(http_pipe); + chain.push_back(service); + return true; + } + +protected: + LLURLRequest::ERequestAction mAction; + std::string mURL; +}; +#endif + +/** + * External constants + */ +extern const std::string CONTEXT_DEST_URI_SD_LABEL; + +#endif // LL_LLURLREQUEST_H diff --git a/indra/llmessage/lluseroperation.cpp b/indra/llmessage/lluseroperation.cpp new file mode 100644 index 0000000000..f7506c955c --- /dev/null +++ b/indra/llmessage/lluseroperation.cpp @@ -0,0 +1,161 @@ +/** + * @file lluseroperation.cpp + * @brief LLUserOperation class definition. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lluseroperation.h" + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- + +LLUserOperationMgr* gUserOperationMgr = NULL; + +///---------------------------------------------------------------------------- +/// Class LLUserOperation +///---------------------------------------------------------------------------- + +LLUserOperation::LLUserOperation(const LLUUID& agent_id) +: mAgentID(agent_id), + mTimer() +{ + mTransactionID.generate(); +} + +LLUserOperation::LLUserOperation(const LLUUID& agent_id, + const LLUUID& transaction_id) : + mAgentID(agent_id), + mTransactionID(transaction_id), + mTimer() +{ +} + +// protected constructor which is used by base classes that determine +// transaction, agent, et. after construction. +LLUserOperation::LLUserOperation() : + mTimer() +{ +} + +LLUserOperation::~LLUserOperation() +{ +} + + +BOOL LLUserOperation::isExpired() +{ + const F32 EXPIRE_TIME_SECS = 10.f; + return mTimer.getElapsedTimeF32() > EXPIRE_TIME_SECS; +} + +void LLUserOperation::expire() +{ + // by default, do do anything. +} + +///---------------------------------------------------------------------------- +/// Class LLUserOperationMgr +///---------------------------------------------------------------------------- + +LLUserOperationMgr::LLUserOperationMgr() +{ +} + + +LLUserOperationMgr::~LLUserOperationMgr() +{ + if (mUserOperationList.size() > 0) + { + llwarns << "Exiting with user operations pending." << llendl; + } +} + + +void LLUserOperationMgr::addOperation(LLUserOperation* op) +{ + if(!op) + { + llwarns << "Tried to add null op" << llendl; + return; + } + LLUUID id = op->getTransactionID(); + llassert(mUserOperationList.count(id) == 0); + mUserOperationList[id] = op; +} + + +LLUserOperation* LLUserOperationMgr::findOperation(const LLUUID& tid) +{ + user_operation_list_t::iterator iter = mUserOperationList.find(tid); + if (iter != mUserOperationList.end()) + return iter->second; + else + return NULL; +} + + +BOOL LLUserOperationMgr::deleteOperation(LLUserOperation* op) +{ + size_t rv = 0; + if(op) + { + LLUUID id = op->getTransactionID(); + rv = mUserOperationList.erase(id); + delete op; + op = NULL; + } + return rv ? TRUE : FALSE; +} + +void LLUserOperationMgr::deleteExpiredOperations() +{ + const S32 MAX_OPS_CONSIDERED = 2000; + S32 ops_left = MAX_OPS_CONSIDERED; + LLUserOperation* op = NULL; + user_operation_list_t::iterator it; + if(mLastOperationConsidered.isNull()) + { + it = mUserOperationList.begin(); + } + else + { + it = mUserOperationList.lower_bound(mLastOperationConsidered); + } + while((ops_left--) && (it != mUserOperationList.end())) + { + op = (*it).second; + if(op && op->isExpired()) + { + lldebugs << "expiring: " << (*it).first << llendl; + op->expire(); + mUserOperationList.erase(it++); + delete op; + } + else if(op) + { + ++it; + } + else + { + mUserOperationList.erase(it++); + } + } + if(it != mUserOperationList.end()) + { + mLastOperationConsidered = (*it).first; + } + else + { + mLastOperationConsidered.setNull(); + } +} + + +///---------------------------------------------------------------------------- +/// Local function definitions +///---------------------------------------------------------------------------- diff --git a/indra/llmessage/lluseroperation.h b/indra/llmessage/lluseroperation.h new file mode 100644 index 0000000000..ac6590abf9 --- /dev/null +++ b/indra/llmessage/lluseroperation.h @@ -0,0 +1,75 @@ +/** + * @file lluseroperation.h + * @brief LLUserOperation class header file - used for message based + * transaction. For example, money transactions. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLUSEROPERATION_H +#define LL_LLUSEROPERATION_H + +#include "lluuid.h" +#include "llframetimer.h" + +#include + +class LLUserOperation +{ +public: + LLUserOperation(const LLUUID& agent_id); + LLUserOperation(const LLUUID& agent_id, const LLUUID& transaction_id); + virtual ~LLUserOperation(); + + const LLUUID& getTransactionID() const { return mTransactionID; } + const LLUUID& getAgentID() const { return mAgentID; } + + // Operation never got necessary data, so expired + virtual BOOL isExpired(); + + // Send request to the dataserver + virtual void sendRequest() = 0; + + // Run the operation. This will only be called in the case of an + // actual success or failure of the operation. + virtual BOOL execute(BOOL transaction_success) = 0; + + // This method is called when the user op has expired, and is + // about to be deleted by the manager. This gives the user op the + // ability to nack someone when the user op is never evaluated + virtual void expire(); + +protected: + LLUserOperation(); + +protected: + LLUUID mAgentID; + LLUUID mTransactionID; + LLFrameTimer mTimer; +}; + + +class LLUserOperationMgr +{ +public: + LLUserOperationMgr(); + ~LLUserOperationMgr(); + + void addOperation(LLUserOperation* op); + LLUserOperation* findOperation(const LLUUID& transaction_id); + BOOL deleteOperation(LLUserOperation* op); + + // Call this method every once in a while to clean up old + // transactions. + void deleteExpiredOperations(); + +private: + typedef std::map user_operation_list_t; + user_operation_list_t mUserOperationList; + LLUUID mLastOperationConsidered; +}; + +extern LLUserOperationMgr* gUserOperationMgr; + +#endif // LL_LLUSEROPERATION_H diff --git a/indra/llmessage/llvehicleparams.h b/indra/llmessage/llvehicleparams.h new file mode 100644 index 0000000000..bd49d3f6ae --- /dev/null +++ b/indra/llmessage/llvehicleparams.h @@ -0,0 +1,105 @@ +/** + * @file llvehicleparams.h + * @brief For parameter names that must be shared between the + * scripting language and the LLVehicleAction class on the simulator. + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_VEHICLE_PARAMS_H +#define LL_VEHICLE_PARAMS_H + +/** + * The idea is that the various parameters that control vehicle + * behavior can be tweeked by name using general-purpose script calls. + */ + +typedef enum e_vehicle_param +{ + VEHICLE_TYPE_NONE, // TYPE_0 + VEHICLE_TYPE_SLED, + VEHICLE_TYPE_CAR, + VEHICLE_TYPE_BOAT, + VEHICLE_TYPE_AIRPLANE, + VEHICLE_TYPE_BALLOON, // TYPE_5 + VEHICLE_TYPE_6, + VEHICLE_TYPE_7, + VEHICLE_TYPE_8, + VEHICLE_TYPE_9, + VEHICLE_TYPE_10, + VEHICLE_TYPE_11, + VEHICLE_TYPE_12, + VEHICLE_TYPE_13, + VEHICLE_TYPE_14, + VEHICLE_TYPE_15, + + // vector parameters + VEHICLE_LINEAR_FRICTION_TIMESCALE, + VEHICLE_ANGULAR_FRICTION_TIMESCALE, + VEHICLE_LINEAR_MOTOR_DIRECTION, + VEHICLE_ANGULAR_MOTOR_DIRECTION, + VEHICLE_LINEAR_MOTOR_OFFSET, + VEHICLE_VECTOR_PARAM_5, + VEHICLE_VECTOR_PARAM_6, + VEHICLE_VECTOR_PARAM_7, + + // floating point parameters + VEHICLE_HOVER_HEIGHT, + VEHICLE_HOVER_EFFICIENCY, + VEHICLE_HOVER_TIMESCALE, + VEHICLE_BUOYANCY, + + VEHICLE_LINEAR_DEFLECTION_EFFICIENCY, + VEHICLE_LINEAR_DEFLECTION_TIMESCALE, + VEHICLE_LINEAR_MOTOR_TIMESCALE, + VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, + + VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY, + VEHICLE_ANGULAR_DEFLECTION_TIMESCALE, + VEHICLE_ANGULAR_MOTOR_TIMESCALE, + VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE, + + VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY, + VEHICLE_VERTICAL_ATTRACTION_TIMESCALE, + + VEHICLE_BANKING_EFFICIENCY, + VEHICLE_BANKING_MIX, + VEHICLE_BANKING_TIMESCALE, + + VEHICLE_FLOAT_PARAM_17, + VEHICLE_FLOAT_PARAM_18, + VEHICLE_FLOAT_PARAM_19, + + // rotation parameters + VEHICLE_REFERENCE_FRAME, + VEHICLE_ROTATION_PARAM_1, + VEHICLE_ROTATION_PARAM_2, + VEHICLE_ROTATION_PARAM_3, + +} EVehicleParam; + + +// some flags that effect how the vehicle moves + +// zeros world-z component of linear deflection +const U32 VEHICLE_FLAG_NO_DEFLECTION_UP = 1 << 0; + +// spring-loads roll only +const U32 VEHICLE_FLAG_LIMIT_ROLL_ONLY = 1 << 1; + +// hover flags +const U32 VEHICLE_FLAG_HOVER_WATER_ONLY = 1 << 2; +const U32 VEHICLE_FLAG_HOVER_TERRAIN_ONLY = 1 << 3; +const U32 VEHICLE_FLAG_HOVER_GLOBAL_HEIGHT = 1 << 4; +const U32 VEHICLE_FLAG_HOVER_UP_ONLY = 1 << 5; + +// caps world-z component of linear motor to prevent +// climbing up into the sky +const U32 VEHICLE_FLAG_LIMIT_MOTOR_UP = 1 << 6; + +const U32 VEHICLE_FLAG_MOUSELOOK_STEER = 1 << 7; +const U32 VEHICLE_FLAG_MOUSELOOK_BANK = 1 << 8; +const U32 VEHICLE_FLAG_CAMERA_DECOUPLED = 1 << 9; + +#endif diff --git a/indra/llmessage/llxfer.cpp b/indra/llmessage/llxfer.cpp new file mode 100644 index 0000000000..154696eb4e --- /dev/null +++ b/indra/llmessage/llxfer.cpp @@ -0,0 +1,350 @@ +/** + * @file llxfer.cpp + * @brief implementation of LLXfer class for a single xfer. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llxfer.h" +#include "lluuid.h" +#include "llerror.h" +#include "llmath.h" +#include "u64.h" + +//number of bytes sent in each message +const U32 LL_XFER_CHUNK_SIZE = 1000; + +const U32 LLXfer::XFER_FILE = 1; +const U32 LLXfer::XFER_VFILE = 2; +const U32 LLXfer::XFER_MEM = 3; + +/////////////////////////////////////////////////////////// + +LLXfer::LLXfer (S32 chunk_size) +{ + init(chunk_size); +} + +/////////////////////////////////////////////////////////// + +LLXfer::~LLXfer () +{ + free(); +} + +/////////////////////////////////////////////////////////// + +void LLXfer::init (S32 chunk_size) +{ + mID = 0; + + mPacketNum = -1; // there's a preincrement before sending the zeroth packet + mXferSize = 0; + + mStatus = e_LL_XFER_UNINITIALIZED; + mNext = NULL; + mWaitingForACK = FALSE; + + mCallback = NULL; + mCallbackDataHandle = NULL; + + mBufferContainsEOF = FALSE; + mBuffer = NULL; + mBufferLength = 0; + mBufferStartOffset = 0; + + mRetries = 0; + + if (chunk_size < 1) + { + chunk_size = LL_XFER_CHUNK_SIZE; + } + mChunkSize = chunk_size; +} + +/////////////////////////////////////////////////////////// + +void LLXfer::free () +{ + if (mBuffer) + { + delete[] mBuffer; + mBuffer = NULL; + } +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer::startSend (U64 xfer_id, const LLHost &remote_host) +{ + llwarns << "undifferentiated LLXfer::startSend for " << getName() << llendl; + return (-1); +} + +/////////////////////////////////////////////////////////// + +void LLXfer::setXferSize (S32 xfer_size) +{ + mXferSize = xfer_size; +// cout << "starting transfer of size: " << xfer_size << endl; +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer::startDownload() +{ + llwarns << "undifferentiated LLXfer::startDownload for " << getName() + << llendl; + return (-1); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer::receiveData (char *datap, S32 data_size) +{ + S32 retval = 0; + + if (((S32) mBufferLength + data_size) > getMaxBufferSize()) + { + retval = flush(); + } + + if (!retval) + { + if (datap != NULL) + { + memcpy(&mBuffer[mBufferLength],datap,data_size); + mBufferLength += data_size; + } + else + { + llerrs << "NULL data passed in receiveData" << llendl; + } + } + + return (retval); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer::flush() +{ + // only files have somewhere to flush to + // if we get called with a flush it means we've blown past our + // allocated buffer size + + return (-1); +} + + +/////////////////////////////////////////////////////////// + +S32 LLXfer::suck(S32 start_position) +{ + llwarns << "Attempted to send a packet outside the buffer bounds in LLXfer::suck()" << llendl; + return (-1); +} + +/////////////////////////////////////////////////////////// + +void LLXfer::sendPacket(S32 packet_num) +{ + char fdata_buf[LL_XFER_LARGE_PAYLOAD+4]; /* Flawfinder: ignore */ + S32 fdata_size = mChunkSize; + BOOL last_packet = FALSE; + S32 num_copy = 0; + + // if the desired packet is not in our current buffered excerpt from the file. . . + if (((U32)packet_num*fdata_size < mBufferStartOffset) + || ((U32)llmin((U32)mXferSize,(U32)((U32)(packet_num+1)*fdata_size)) > mBufferStartOffset + mBufferLength)) + + { + if (suck(packet_num*fdata_size)) // returns non-zero on failure + { + abort(LL_ERR_EOF); + return; + } + } + + S32 desired_read_position = 0; + + desired_read_position = packet_num * fdata_size - mBufferStartOffset; + + fdata_size = llmin((S32)mBufferLength-desired_read_position, mChunkSize); + + if (fdata_size < 0) + { + llwarns << "negative data size in xfer send, aborting" << llendl; + abort(LL_ERR_EOF); + return; + } + + if (((U32)(desired_read_position + fdata_size) >= (U32)mBufferLength) && (mBufferContainsEOF)) + { + last_packet = TRUE; + } + + if (packet_num) + { + num_copy = llmin(fdata_size, (S32)sizeof(fdata_buf)); + num_copy = llmin(num_copy, (S32)(sizeof(mBuffer) - desired_read_position)); + if (num_copy > 0) + { + memcpy(fdata_buf,&mBuffer[desired_read_position],num_copy); + } + } + else // if we're the first packet, encode size as an additional S32 at start of data + { + num_copy = llmin(fdata_size, (S32)(sizeof(fdata_buf)-sizeof(S32))); + num_copy = llmin(num_copy, (S32)(sizeof(mBuffer) - desired_read_position)); + if (num_copy > 0) + { + memcpy(fdata_buf+sizeof(S32),&mBuffer[desired_read_position],fdata_size); + } + fdata_size += sizeof(S32); + htonmemcpy(fdata_buf,&mXferSize, MVT_S32, sizeof(S32)); + } + + S32 encoded_packetnum = encodePacketNum(packet_num,last_packet); + + if (fdata_size) + { + // send the packet + gMessageSystem->newMessageFast(_PREHASH_SendXferPacket); + gMessageSystem->nextBlockFast(_PREHASH_XferID); + + gMessageSystem->addU64Fast(_PREHASH_ID, mID); + gMessageSystem->addU32Fast(_PREHASH_Packet, encoded_packetnum); + + gMessageSystem->nextBlockFast(_PREHASH_DataPacket); + gMessageSystem->addBinaryDataFast(_PREHASH_Data, &fdata_buf,fdata_size); + + gMessageSystem->sendMessage(mRemoteHost); + + ACKTimer.reset(); + mWaitingForACK = TRUE; + } + if (last_packet) + { + mStatus = e_LL_XFER_COMPLETE; + } + else + { + mStatus = e_LL_XFER_IN_PROGRESS; + } +} + +/////////////////////////////////////////////////////////// + +void LLXfer::sendNextPacket() +{ + mRetries = 0; + sendPacket(++mPacketNum); +} + +/////////////////////////////////////////////////////////// + +void LLXfer::resendLastPacket() +{ + mRetries++; + sendPacket(mPacketNum); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer::processEOF() +{ + S32 retval = 0; + + mStatus = e_LL_XFER_COMPLETE; + + if (LL_ERR_NOERR == mCallbackResult) + { + llinfos << "xfer from " << mRemoteHost << " complete: " << getName() + << llendl; + } + else + { + llinfos << "xfer from " << mRemoteHost << " failed, code " + << mCallbackResult << ": " << getName() << llendl; + } + + if (mCallback) + { + mCallback(mCallbackDataHandle,mCallbackResult); + } + + return(retval); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer::encodePacketNum(S32 packet_num, BOOL is_EOF) +{ + if (is_EOF) + { + packet_num |= 0x80000000; + } + return packet_num; +} + +/////////////////////////////////////////////////////////// + +void LLXfer::abort (S32 result_code) +{ + mCallbackResult = result_code; + + llinfos << "Aborting xfer from " << mRemoteHost << " named " << getName() + << " - error: " << result_code << llendl; + + gMessageSystem->newMessageFast(_PREHASH_AbortXfer); + gMessageSystem->nextBlockFast(_PREHASH_XferID); + gMessageSystem->addU64Fast(_PREHASH_ID, mID); + gMessageSystem->addS32Fast(_PREHASH_Result, result_code); + + gMessageSystem->sendMessage(mRemoteHost); + + mStatus = e_LL_XFER_ABORTED; +} + + +/////////////////////////////////////////////////////////// + +const char * LLXfer::getName() +{ + static char tmp_str[256]; /* Flawfinder: ignore */ + + return (U64_to_str(mID, tmp_str, sizeof(tmp_str))); +} + +/////////////////////////////////////////////////////////// + +U32 LLXfer::getXferTypeTag() +{ + return 0; +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer::getMaxBufferSize () +{ + return(mXferSize); +} + + +std::ostream& operator<< (std::ostream& os, LLXfer &hh) +{ + os << hh.getName() ; + return os; +} + + + + + + + + diff --git a/indra/llmessage/llxfer.h b/indra/llmessage/llxfer.h new file mode 100644 index 0000000000..3fd12df7a5 --- /dev/null +++ b/indra/llmessage/llxfer.h @@ -0,0 +1,98 @@ +/** + * @file llxfer.h + * @brief definition of LLXfer class for a single xfer + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLXFER_H +#define LL_LLXFER_H + +#include "message.h" +#include "lltimer.h" + +const S32 LL_XFER_LARGE_PAYLOAD = 7680; + +typedef enum ELLXferStatus { + e_LL_XFER_UNINITIALIZED, + e_LL_XFER_REGISTERED, // a buffer which has been registered as available for a request + e_LL_XFER_PENDING, // a transfer which has been requested but is waiting for a free slot + e_LL_XFER_IN_PROGRESS, + e_LL_XFER_COMPLETE, + e_LL_XFER_ABORTED, + e_LL_XFER_NONE +} ELLXferStatus; + +class LLXfer +{ + private: + protected: + S32 mChunkSize; + + public: + LLXfer *mNext; + U64 mID; + S32 mPacketNum; + + LLHost mRemoteHost; + S32 mXferSize; + + char *mBuffer; + U32 mBufferLength; + U32 mBufferStartOffset; + BOOL mBufferContainsEOF; + + ELLXferStatus mStatus; + + BOOL mWaitingForACK; + + void (*mCallback)(void **,S32); + void **mCallbackDataHandle; + S32 mCallbackResult; + + LLTimer ACKTimer; + S32 mRetries; + + static const U32 XFER_FILE; + static const U32 XFER_VFILE; + static const U32 XFER_MEM; + + private: + protected: + public: + LLXfer (S32 chunk_size); + virtual ~LLXfer(); + + void init(S32 chunk_size); + virtual void free(); + + virtual S32 startSend (U64 xfer_id, const LLHost &remote_host); + virtual void sendPacket(S32 packet_num); + virtual void sendNextPacket(); + virtual void resendLastPacket(); + virtual S32 processEOF(); + virtual S32 startDownload(); + virtual S32 receiveData (char *datap, S32 data_size); + virtual void abort(S32); + + virtual S32 suck(S32 start_position); + virtual S32 flush(); + + virtual S32 encodePacketNum(S32 packet_num, BOOL is_eof); + virtual void setXferSize (S32 data_size); + virtual S32 getMaxBufferSize(); + + virtual const char *getName(); + + virtual U32 getXferTypeTag(); + + friend std::ostream& operator<< (std::ostream& os, LLXfer &hh); + +}; + +#endif + + + + diff --git a/indra/llmessage/llxfer_file.cpp b/indra/llmessage/llxfer_file.cpp new file mode 100644 index 0000000000..da72467c76 --- /dev/null +++ b/indra/llmessage/llxfer_file.cpp @@ -0,0 +1,416 @@ +/** + * @file llxfer_file.cpp + * @brief implementation of LLXfer_File class for a single xfer (file) + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#if !LL_WINDOWS +#include +#include +#endif + +#include "llxfer_file.h" +#include "lluuid.h" +#include "llerror.h" +#include "llmath.h" +#include "llstring.h" +#include "lldir.h" + +// size of chunks read from/written to disk +const U32 LL_MAX_XFER_FILE_BUFFER = 65536; + +// local function to copy a file +S32 copy_file(const char* from, const char* to); + +/////////////////////////////////////////////////////////// + +LLXfer_File::LLXfer_File (S32 chunk_size) +: LLXfer(chunk_size) +{ + init(LLString::null, FALSE, chunk_size); +} + +LLXfer_File::LLXfer_File (const LLString& local_filename, BOOL delete_local_on_completion, S32 chunk_size) +: LLXfer(chunk_size) +{ + init(local_filename, delete_local_on_completion, chunk_size); +} + +/////////////////////////////////////////////////////////// + +LLXfer_File::~LLXfer_File () +{ + free(); +} + +/////////////////////////////////////////////////////////// + +void LLXfer_File::init (const LLString& local_filename, BOOL delete_local_on_completion, S32 chunk_size) +{ + + mFp = NULL; + mLocalFilename[0] = 0; + mRemoteFilename[0] = 0; + mRemotePath = LL_PATH_NONE; + mTempFilename[0] = 0; + mDeleteLocalOnCompletion = FALSE; + mDeleteRemoteOnCompletion = FALSE; + + if (!local_filename.empty()) + { + strncpy(mLocalFilename, local_filename.c_str(), LL_MAX_PATH); /* Flawfinder : ignore */ + + // You can only automatically delete .tmp file as a safeguard against nasty messages. + mDeleteLocalOnCompletion = (delete_local_on_completion && (strstr(mLocalFilename,".tmp") == &mLocalFilename[strlen(mLocalFilename)-4])); /* Flawfinder : ignore */ + } +} + +/////////////////////////////////////////////////////////// + +void LLXfer_File::free () +{ + if (mFp) + { + fclose(mFp); + mFp = NULL; + } + + LLFile::remove(mTempFilename); + + if (mDeleteLocalOnCompletion) + { + lldebugs << "Removing file: " << mLocalFilename << llendl; + LLFile::remove(mLocalFilename); + } + else + { + lldebugs << "Keeping local file: " << mLocalFilename << llendl; + } + + LLXfer::free(); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer_File::initializeRequest(U64 xfer_id, + const LLString& local_filename, + const LLString& remote_filename, + ELLPath remote_path, + const LLHost& remote_host, + BOOL delete_remote_on_completion, + void (*callback)(void**,S32), + void** user_data) +{ + S32 retval = 0; // presume success + + mID = xfer_id; + strncpy(mLocalFilename, local_filename.c_str(), LL_MAX_PATH); /* Flawfinder : ignore */ + strncpy(mRemoteFilename,remote_filename.c_str(), LL_MAX_PATH); /* Flawfinder : ignore */ + mRemotePath = remote_path; + mRemoteHost = remote_host; + mDeleteRemoteOnCompletion = delete_remote_on_completion; + + snprintf(mTempFilename, sizeof(mTempFilename), "%s",gDirUtilp->getTempFilename().c_str()); /* Flawfinder : ignore */ + + mCallback = callback; + mCallbackDataHandle = user_data; + mCallbackResult = LL_ERR_NOERR; + + llinfos << "Requesting xfer from " << remote_host << " for file: " << mLocalFilename << llendl; + + if (mBuffer) + { + delete(mBuffer); + mBuffer = NULL; + } + + mBuffer = new char[LL_MAX_XFER_FILE_BUFFER]; + mBufferLength = 0; + + mPacketNum = 0; + + mStatus = e_LL_XFER_PENDING; + return retval; +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer_File::startDownload() +{ + S32 retval = 0; // presume success + mFp = LLFile::fopen(mTempFilename,"w+b"); /* Flawfinder : ignore */ + if (mFp) + { + fclose(mFp); + mFp = NULL; + + gMessageSystem->newMessageFast(_PREHASH_RequestXfer); + gMessageSystem->nextBlockFast(_PREHASH_XferID); + gMessageSystem->addU64Fast(_PREHASH_ID, mID); + gMessageSystem->addStringFast(_PREHASH_Filename, mRemoteFilename); + gMessageSystem->addU8("FilePath", (U8) mRemotePath); + gMessageSystem->addBOOL("DeleteOnCompletion", mDeleteRemoteOnCompletion); + gMessageSystem->addBOOL("UseBigPackets", BOOL(mChunkSize == LL_XFER_LARGE_PAYLOAD)); + gMessageSystem->addUUIDFast(_PREHASH_VFileID, LLUUID::null); + gMessageSystem->addS16Fast(_PREHASH_VFileType, -1); + + gMessageSystem->sendReliable(mRemoteHost); + mStatus = e_LL_XFER_IN_PROGRESS; + } + else + { + llwarns << "Couldn't create file to be received!" << llendl; + retval = -1; + } + + return (retval); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer_File::startSend (U64 xfer_id, const LLHost &remote_host) +{ + S32 retval = LL_ERR_NOERR; // presume success + + mRemoteHost = remote_host; + mID = xfer_id; + mPacketNum = -1; + +// cout << "Sending file: " << mLocalFilename << endl; + + delete [] mBuffer; + mBuffer = new char[LL_MAX_XFER_FILE_BUFFER]; + + mBufferLength = 0; + mBufferStartOffset = 0; + + mFp = LLFile::fopen(mLocalFilename,"rb"); /* Flawfinder : ignore */ + if (mFp) + { + fseek(mFp,0,SEEK_END); + + S32 file_size = ftell(mFp); + if (file_size <= 0) + { + return LL_ERR_FILE_EMPTY; + } + setXferSize(file_size); + + fseek(mFp,0,SEEK_SET); + } + else + { + llinfos << "Warning: " << mLocalFilename << " not found." << llendl; + return (LL_ERR_FILE_NOT_FOUND); + } + + mStatus = e_LL_XFER_PENDING; + + return (retval); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer_File::getMaxBufferSize () +{ + return(LL_MAX_XFER_FILE_BUFFER); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer_File::suck(S32 start_position) +{ + S32 retval = 0; + + if (mFp) + { + // grab a buffer from the right place in the file + fseek (mFp,start_position,SEEK_SET); + + mBufferLength = (U32)fread(mBuffer,1,LL_MAX_XFER_FILE_BUFFER,mFp); + mBufferStartOffset = start_position; + + if (feof(mFp)) + { + mBufferContainsEOF = TRUE; + } + else + { + mBufferContainsEOF = FALSE; + } + } + else + { + retval = -1; + } + + return (retval); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer_File::flush() +{ + S32 retval = 0; + if (mBufferLength) + { + if (mFp) + { + llerrs << "Overwriting open file pointer!" << llendl; + } + mFp = LLFile::fopen(mTempFilename,"a+b"); /* Flawfinder : ignore */ + + if (mFp) + { + fwrite(mBuffer,1,mBufferLength,mFp); +// llinfos << "******* wrote " << mBufferLength << " bytes of file xfer" << llendl; + fclose(mFp); + mFp = NULL; + + mBufferLength = 0; + } + else + { + llwarns << "LLXfer_File::flush() unable to open " << mTempFilename << " for writing!" << llendl; + retval = LL_ERR_CANNOT_OPEN_FILE; + } + } + return (retval); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer_File::processEOF() +{ + S32 retval = 0; + mStatus = e_LL_XFER_COMPLETE; + + S32 flushval = flush(); + + // If we have no other errors, our error becomes the error generated by + // flush. + if (!mCallbackResult) + { + mCallbackResult = flushval; + } + + LLFile::remove(mLocalFilename); + + if (!mCallbackResult) + { + if (LLFile::rename(mTempFilename,mLocalFilename)) + { +#if !LL_WINDOWS + S32 error_number = errno; + llinfos << "Rename failure (" << error_number << ") - " + << mTempFilename << " to " << mLocalFilename << llendl; + if(EXDEV == error_number) + { + if(copy_file(mTempFilename, mLocalFilename) == 0) + { + llinfos << "Rename across mounts; copying+unlinking the file instead." << llendl; + unlink(mTempFilename); + } + else + { + llwarns << "Copy failure - " << mTempFilename << " to " + << mLocalFilename << llendl; + } + } + else + { + //FILE* fp = LLFile::fopen(mTempFilename, "r"); + //llwarns << "File " << mTempFilename << " does " + // << (!fp ? "not" : "" ) << " exit." << llendl; + //if(fp) fclose(fp); + //fp = LLFile::fopen(mLocalFilename, "r"); + //llwarns << "File " << mLocalFilename << " does " + // << (!fp ? "not" : "" ) << " exit." << llendl; + //if(fp) fclose(fp); + llwarns << "Rename fatally failed, can only handle EXDEV (" + << EXDEV << ")" << llendl; + } +#else + llwarns << "Rename failure - " << mTempFilename << " to " + << mLocalFilename << llendl; +#endif + } + } + + if (mFp) + { + fclose(mFp); + mFp = NULL; + } + + retval = LLXfer::processEOF(); + + return(retval); +} + +/////////////////////////////////////////////////////////// + +BOOL LLXfer_File::matchesLocalFilename(const LLString& filename) +{ + return (filename == mLocalFilename); +} + +/////////////////////////////////////////////////////////// + +BOOL LLXfer_File::matchesRemoteFilename(const LLString& filename, ELLPath remote_path) +{ + return ((filename == mRemoteFilename) && (remote_path == mRemotePath)); +} + + +/////////////////////////////////////////////////////////// + +const char * LLXfer_File::getName() +{ + return (mLocalFilename); +} + +/////////////////////////////////////////////////////////// + +// hacky - doesn't matter what this is +// as long as it's different from the other classes +U32 LLXfer_File::getXferTypeTag() +{ + return LLXfer::XFER_FILE; +} + +/////////////////////////////////////////////////////////// + +#if !LL_WINDOWS + +// This is really close to, but not quite a general purpose copy +// function. It does not really spam enough information, but is useful +// for this cpp file, because this should never be called in a +// production environment. +S32 copy_file(const char* from, const char* to) +{ + S32 rv = 0; + FILE* in = LLFile::fopen(from, "rb"); + FILE* out = LLFile::fopen(to, "wb"); + if(in && out) + { + S32 read = 0; + const S32 COPY_BUFFER_SIZE = 16384; + U8 buffer[COPY_BUFFER_SIZE]; + while(((read = fread(buffer, 1, sizeof(buffer), in)) > 0) + && (fwrite(buffer, 1, read, out) == (U32)read)); /* Flawfinder : ignore */ + if(ferror(in) || ferror(out)) rv = -2; + } + else + { + rv = -1; + } + if(in) fclose(in); + if(out) fclose(out); + return rv; +} +#endif diff --git a/indra/llmessage/llxfer_file.h b/indra/llmessage/llxfer_file.h new file mode 100644 index 0000000000..b3d1ccbfbe --- /dev/null +++ b/indra/llmessage/llxfer_file.h @@ -0,0 +1,68 @@ +/** + * @file llxfer_file.h + * @brief definition of LLXfer_File class for a single xfer_file. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLXFER_FILE_H +#define LL_LLXFER_FILE_H + +#include + +#include "llxfer.h" +#include "lldir.h" + +class LLXfer_File : public LLXfer +{ + protected: + FILE *mFp; + char mLocalFilename[LL_MAX_PATH]; /* Flawfinder : ignore */ + char mRemoteFilename[LL_MAX_PATH]; /* Flawfinder : ignore */ + ELLPath mRemotePath; + char mTempFilename[LL_MAX_PATH]; /* Flawfinder : ignore */ + + BOOL mDeleteLocalOnCompletion; + BOOL mDeleteRemoteOnCompletion; + + public: + LLXfer_File (S32 chunk_size); + LLXfer_File (const LLString& local_filename, BOOL delete_local_on_completion, S32 chunk_size); + virtual ~LLXfer_File(); + + virtual void init(const LLString& local_filename, BOOL delete_local_on_completion, S32 chunk_size); + virtual void free(); + + virtual S32 initializeRequest(U64 xfer_id, + const LLString& local_filename, + const LLString& remote_filename, + ELLPath remote_path, + const LLHost& remote_host, + BOOL delete_remote_on_completion, + void (*callback)(void**,S32), + void** user_data); + virtual S32 startDownload(); + + virtual S32 processEOF(); + + virtual S32 startSend (U64 xfer_id, const LLHost &remote_host); + + virtual S32 suck(S32 start_position); + virtual S32 flush(); + + virtual BOOL matchesLocalFilename(const LLString& filename); + virtual BOOL matchesRemoteFilename(const LLString& filename, ELLPath remote_path); + + virtual S32 getMaxBufferSize(); + + virtual U32 getXferTypeTag(); + + virtual const char *getName(); +}; + +#endif + + + + diff --git a/indra/llmessage/llxfer_mem.cpp b/indra/llmessage/llxfer_mem.cpp new file mode 100644 index 0000000000..8f48247e20 --- /dev/null +++ b/indra/llmessage/llxfer_mem.cpp @@ -0,0 +1,199 @@ +/** + * @file llxfer_mem.cpp + * @brief implementation of LLXfer_Mem class for a single xfer + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llxfer_mem.h" +#include "lluuid.h" +#include "llerror.h" +#include "llmath.h" + +/////////////////////////////////////////////////////////// + +LLXfer_Mem::LLXfer_Mem () +: LLXfer(-1) +{ + init(); +} + +/////////////////////////////////////////////////////////// + +LLXfer_Mem::~LLXfer_Mem () +{ + free(); +} + +/////////////////////////////////////////////////////////// + +void LLXfer_Mem::init () +{ + mRemoteFilename[0] = '\0'; + mRemotePath = LL_PATH_NONE; + mDeleteRemoteOnCompletion = FALSE; +} + +/////////////////////////////////////////////////////////// + +void LLXfer_Mem::free () +{ + LLXfer::free(); +} + +/////////////////////////////////////////////////////////// + +void LLXfer_Mem::setXferSize (S32 xfer_size) +{ + mXferSize = xfer_size; + + delete[] mBuffer; + mBuffer = new char[xfer_size]; + + mBufferLength = 0; + mBufferStartOffset = 0; + mBufferContainsEOF = TRUE; + +// cout << "starting transfer of size: " << xfer_size << endl; +} + +/////////////////////////////////////////////////////////// + +U64 LLXfer_Mem::registerXfer(U64 xfer_id, const void *datap, const S32 length) +{ + mID = xfer_id; + + if (datap) + { + setXferSize(length); + if (mBuffer) + { + memcpy(mBuffer,datap,length); /* Flawfinder : ignore */ + mBufferLength = length; + } + else + { + xfer_id = 0; + } + } + + mStatus = e_LL_XFER_REGISTERED; + return (xfer_id); +} + +S32 LLXfer_Mem::startSend (U64 xfer_id, const LLHost &remote_host) +{ + S32 retval = LL_ERR_NOERR; // presume success + + if (mXferSize <= 0) + { + return LL_ERR_FILE_EMPTY; + } + + mRemoteHost = remote_host; + mID = xfer_id; + mPacketNum = -1; + +// cout << "Sending file: " << getName() << endl; + + mStatus = e_LL_XFER_PENDING; + + return (retval); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer_Mem::processEOF() +{ + S32 retval = 0; + + mStatus = e_LL_XFER_COMPLETE; + + llinfos << "xfer complete: " << getName() << llendl; + + if (mCallback) + { + mCallback((void *)mBuffer,mBufferLength,mCallbackDataHandle,mCallbackResult); + } + + return(retval); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer_Mem::initializeRequest(U64 xfer_id, + const std::string& remote_filename, + ELLPath remote_path, + const LLHost& remote_host, + BOOL delete_remote_on_completion, + void (*callback)(void*,S32,void**,S32), + void** user_data) +{ + S32 retval = 0; // presume success + + mRemoteHost = remote_host; + + // create a temp filename string using a GUID + mID = xfer_id; + mCallback = callback; + mCallbackDataHandle = user_data; + mCallbackResult = LL_ERR_NOERR; + + strncpy(mRemoteFilename, remote_filename.c_str(), LL_MAX_PATH); /* Flawfinder : ignore */ + mRemotePath = remote_path; + mDeleteRemoteOnCompletion = delete_remote_on_completion; + + llinfos << "Requesting file: " << remote_filename << llendl; + + delete [] mBuffer; + mBuffer = NULL; + + mBufferLength = 0; + mPacketNum = 0; + mStatus = e_LL_XFER_PENDING; + return retval; +} + +////////////////////////////////////////////////////////// + +S32 LLXfer_Mem::startDownload() +{ + S32 retval = 0; // presume success + gMessageSystem->newMessageFast(_PREHASH_RequestXfer); + gMessageSystem->nextBlockFast(_PREHASH_XferID); + gMessageSystem->addU64Fast(_PREHASH_ID, mID); + gMessageSystem->addStringFast(_PREHASH_Filename, mRemoteFilename); + gMessageSystem->addU8("FilePath", (U8) mRemotePath); + gMessageSystem->addBOOL("DeleteOnCompletion", mDeleteRemoteOnCompletion); + gMessageSystem->addBOOL("UseBigPackets", BOOL(mChunkSize == LL_XFER_LARGE_PAYLOAD)); + gMessageSystem->addUUIDFast(_PREHASH_VFileID, LLUUID::null); + gMessageSystem->addS16Fast(_PREHASH_VFileType, -1); + + gMessageSystem->sendReliable(mRemoteHost); + mStatus = e_LL_XFER_IN_PROGRESS; + + return (retval); +} + +////////////////////////////////////////////////////////// + +U32 LLXfer_Mem::getXferTypeTag() +{ + return LLXfer::XFER_MEM; +} + + + + + + + + + + + + + diff --git a/indra/llmessage/llxfer_mem.h b/indra/llmessage/llxfer_mem.h new file mode 100644 index 0000000000..d7cbdc4f85 --- /dev/null +++ b/indra/llmessage/llxfer_mem.h @@ -0,0 +1,61 @@ +/** + * @file llxfer_mem.h + * @brief definition of LLXfer_Mem class for a single xfer + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLXFER_MEM_H +#define LL_LLXFER_MEM_H + +#include + +#include "message.h" +#include "lltimer.h" +#include "llxfer.h" +#include "lldir.h" + +class LLXfer_Mem : public LLXfer +{ + private: + protected: + void (*mCallback)(void *, S32, void **,S32); + char mRemoteFilename[LL_MAX_PATH]; /* Flawfinder : ignore */ + ELLPath mRemotePath; + BOOL mDeleteRemoteOnCompletion; + + public: + + private: + protected: + public: + LLXfer_Mem (); + virtual ~LLXfer_Mem(); + + virtual void init(); + virtual void free(); + + virtual S32 startSend (U64 xfer_id, const LLHost &remote_host); + virtual U64 registerXfer(U64 xfer_id, const void *datap, const S32 length); + virtual void setXferSize (S32 data_size); + + virtual S32 initializeRequest(U64 xfer_id, + const std::string& remote_filename, + ELLPath remote_path, + const LLHost& remote_host, + BOOL delete_remote_on_completion, + void (*callback)(void*,S32,void**,S32), + void** user_data); + virtual S32 startDownload(); + + virtual S32 processEOF(); + + virtual U32 getXferTypeTag(); +}; + +#endif + + + + diff --git a/indra/llmessage/llxfer_vfile.cpp b/indra/llmessage/llxfer_vfile.cpp new file mode 100644 index 0000000000..5030556eb0 --- /dev/null +++ b/indra/llmessage/llxfer_vfile.cpp @@ -0,0 +1,320 @@ +/** + * @file llxfer_vfile.cpp + * @brief implementation of LLXfer_VFile class for a single xfer (vfile). + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llxfer_vfile.h" +#include "lluuid.h" +#include "llerror.h" +#include "llmath.h" +#include "llvfile.h" +#include "llvfs.h" +#include "lldir.h" + +// size of chunks read from/written to disk +const U32 LL_MAX_XFER_FILE_BUFFER = 65536; + +/////////////////////////////////////////////////////////// + +LLXfer_VFile::LLXfer_VFile () +: LLXfer(-1) +{ + init(NULL, LLUUID::null, LLAssetType::AT_NONE); +} + +LLXfer_VFile::LLXfer_VFile (LLVFS *vfs, const LLUUID &local_id, LLAssetType::EType type) +: LLXfer(-1) +{ + init(vfs, local_id, type); +} + +/////////////////////////////////////////////////////////// + +LLXfer_VFile::~LLXfer_VFile () +{ + free(); +} + +/////////////////////////////////////////////////////////// + +void LLXfer_VFile::init (LLVFS *vfs, const LLUUID &local_id, LLAssetType::EType type) +{ + + mVFS = vfs; + mLocalID = local_id; + mType = type; + + mVFile = NULL; + + char id_string[UUID_STR_LENGTH]; /* Flawfinder : ignore */ + mLocalID.toString(id_string); + + snprintf(mName, sizeof(mName), "VFile %s:%s", id_string, LLAssetType::lookup(mType)); /* Flawfinder : ignore */ +} + +/////////////////////////////////////////////////////////// + +void LLXfer_VFile::free () +{ + LLVFile file(mVFS, mTempID, mType, LLVFile::WRITE); + file.remove(); + + delete mVFile; + mVFile = NULL; + + LLXfer::free(); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer_VFile::initializeRequest(U64 xfer_id, + LLVFS* vfs, + const LLUUID& local_id, + const LLUUID& remote_id, + LLAssetType::EType type, + const LLHost& remote_host, + void (*callback)(void**,S32), + void** user_data) +{ + S32 retval = 0; // presume success + + mRemoteHost = remote_host; + + mVFS = vfs; + mLocalID = local_id; + mRemoteID = remote_id; + mType = type; + + mID = xfer_id; + mCallback = callback; + mCallbackDataHandle = user_data; + mCallbackResult = LL_ERR_NOERR; + + char id_string[UUID_STR_LENGTH]; /* Flawfinder : ignore */ + mLocalID.toString(id_string); + + snprintf(mName, sizeof(mName), "VFile %s:%s", id_string, LLAssetType::lookup(mType)); /* Flawfinder : ignore */ + + llinfos << "Requesting " << mName << llendl; + + if (mBuffer) + { + delete[] mBuffer; + mBuffer = NULL; + } + + mBuffer = new char[LL_MAX_XFER_FILE_BUFFER]; + + mBufferLength = 0; + mPacketNum = 0; + mTempID.generate(); + mStatus = e_LL_XFER_PENDING; + return retval; +} + +////////////////////////////////////////////////////////// + +S32 LLXfer_VFile::startDownload() +{ + S32 retval = 0; // presume success + LLVFile file(mVFS, mTempID, mType, LLVFile::APPEND); + + gMessageSystem->newMessageFast(_PREHASH_RequestXfer); + gMessageSystem->nextBlockFast(_PREHASH_XferID); + gMessageSystem->addU64Fast(_PREHASH_ID, mID); + gMessageSystem->addStringFast(_PREHASH_Filename, ""); + gMessageSystem->addU8("FilePath", (U8) LL_PATH_NONE); + gMessageSystem->addBOOL("DeleteOnCompletion", FALSE); + gMessageSystem->addBOOL("UseBigPackets", BOOL(mChunkSize == LL_XFER_LARGE_PAYLOAD)); + gMessageSystem->addUUIDFast(_PREHASH_VFileID, mRemoteID); + gMessageSystem->addS16Fast(_PREHASH_VFileType, (S16)mType); + + gMessageSystem->sendReliable(mRemoteHost); + mStatus = e_LL_XFER_IN_PROGRESS; + + return (retval); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer_VFile::startSend (U64 xfer_id, const LLHost &remote_host) +{ + S32 retval = LL_ERR_NOERR; // presume success + + mRemoteHost = remote_host; + mID = xfer_id; + mPacketNum = -1; + +// cout << "Sending file: " << mLocalFilename << endl; + + delete [] mBuffer; + mBuffer = new char[LL_MAX_XFER_FILE_BUFFER]; + + mBufferLength = 0; + mBufferStartOffset = 0; + + delete mVFile; + mVFile = NULL; + if(mVFS->getExists(mLocalID, mType)) + { + mVFile = new LLVFile(mVFS, mLocalID, mType, LLVFile::READ); + + if (mVFile->getSize() <= 0) + { + delete mVFile; + mVFile = NULL; + + return LL_ERR_FILE_EMPTY; + } + } + + if(mVFile) + { + setXferSize(mVFile->getSize()); + mStatus = e_LL_XFER_PENDING; + } + else + { + retval = LL_ERR_FILE_NOT_FOUND; + } + + return (retval); +} + +/////////////////////////////////////////////////////////// +void LLXfer_VFile::setXferSize (S32 xfer_size) +{ + LLXfer::setXferSize(xfer_size); + + // Don't do this on the server side, where we have a persistent mVFile + // It would be nice if LLXFers could tell which end of the pipe they were + if (! mVFile) + { + LLVFile file(mVFS, mTempID, mType, LLVFile::APPEND); + file.setMaxSize(xfer_size); + } +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer_VFile::getMaxBufferSize () +{ + return(LL_MAX_XFER_FILE_BUFFER); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer_VFile::suck(S32 start_position) +{ + S32 retval = 0; + + if (mVFile) + { + // grab a buffer from the right place in the file + if (! mVFile->seek(start_position, 0)) + { + llwarns << "VFile Xfer Can't seek to position " << start_position << ", file length " << mVFile->getSize() << llendl; + llwarns << "While sending file " << mLocalID << llendl; + return -1; + } + + if (mVFile->read((U8*)mBuffer, LL_MAX_XFER_FILE_BUFFER)) /* Flawfinder : ignore */ + { + mBufferLength = mVFile->getLastBytesRead(); + mBufferStartOffset = start_position; + + mBufferContainsEOF = mVFile->eof(); + } + else + { + retval = -1; + } + } + else + { + retval = -1; + } + + return (retval); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer_VFile::flush() +{ + S32 retval = 0; + if (mBufferLength) + { + LLVFile file(mVFS, mTempID, mType, LLVFile::APPEND); + + file.write((U8*)mBuffer, mBufferLength); + + mBufferLength = 0; + } + return (retval); +} + +/////////////////////////////////////////////////////////// + +S32 LLXfer_VFile::processEOF() +{ + S32 retval = 0; + mStatus = e_LL_XFER_COMPLETE; + + flush(); + + if (!mCallbackResult) + { + LLVFile file(mVFS, mTempID, mType, LLVFile::WRITE); + if (! file.rename(mLocalID, mType)) + { + llinfos << "copy from temp file failed: unable to rename to " << mLocalID << llendl; + } + + } + + if (mVFile) + { + delete mVFile; + mVFile = NULL; + } + + retval = LLXfer::processEOF(); + + return(retval); +} + +//////////////////////////////////////////////////////////// + +BOOL LLXfer_VFile::matchesLocalFile(const LLUUID &id, LLAssetType::EType type) +{ + return (id == mLocalID && type == mType); +} + +////////////////////////////////////////////////////////// + +BOOL LLXfer_VFile::matchesRemoteFile(const LLUUID &id, LLAssetType::EType type) +{ + return (id == mRemoteID && type == mType); +} + +////////////////////////////////////////////////////////// + +const char * LLXfer_VFile::getName() +{ + return mName; +} + +////////////////////////////////////////////////////////// + +// hacky - doesn't matter what this is +// as long as it's different from the other classes +U32 LLXfer_VFile::getXferTypeTag() +{ + return LLXfer::XFER_VFILE; +} diff --git a/indra/llmessage/llxfer_vfile.h b/indra/llmessage/llxfer_vfile.h new file mode 100644 index 0000000000..3d9d8de7a7 --- /dev/null +++ b/indra/llmessage/llxfer_vfile.h @@ -0,0 +1,74 @@ +/** + * @file llxfer_vfile.h + * @brief definition of LLXfer_VFile class for a single xfer_vfile. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLXFER_VFILE_H +#define LL_LLXFER_VFILE_H + +#include + +#include "llxfer.h" +#include "llassetstorage.h" + +class LLVFS; +class LLVFile; + +class LLXfer_VFile : public LLXfer +{ + protected: + LLUUID mLocalID; + LLUUID mRemoteID; + LLUUID mTempID; + LLAssetType::EType mType; + + LLVFile *mVFile; + + LLVFS *mVFS; + + char mName[MAX_STRING]; /* Flawfinder : ignore */ + + public: + LLXfer_VFile (); + LLXfer_VFile (LLVFS *vfs, const LLUUID &local_id, LLAssetType::EType type); + virtual ~LLXfer_VFile(); + + virtual void init(LLVFS *vfs, const LLUUID &local_id, LLAssetType::EType type); + virtual void free(); + + virtual S32 initializeRequest(U64 xfer_id, + LLVFS *vfs, + const LLUUID &local_id, + const LLUUID &remote_id, + const LLAssetType::EType type, + const LLHost &remote_host, + void (*callback)(void **,S32), + void **user_data); + virtual S32 startDownload(); + + virtual S32 processEOF(); + + virtual S32 startSend (U64 xfer_id, const LLHost &remote_host); + + virtual S32 suck(S32 start_position); + virtual S32 flush(); + + virtual BOOL matchesLocalFile(const LLUUID &id, LLAssetType::EType type); + virtual BOOL matchesRemoteFile(const LLUUID &id, LLAssetType::EType type); + + virtual void setXferSize(S32 xfer_size); + virtual S32 getMaxBufferSize(); + + virtual U32 getXferTypeTag(); + + virtual const char *getName(); +}; + +#endif + + + + diff --git a/indra/llmessage/llxfermanager.cpp b/indra/llmessage/llxfermanager.cpp new file mode 100644 index 0000000000..e2d8cd30b3 --- /dev/null +++ b/indra/llmessage/llxfermanager.cpp @@ -0,0 +1,1133 @@ +/** + * @file llxfermanager.cpp + * @brief implementation of LLXferManager class for a collection of xfers + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llxfermanager.h" + +#include "llxfer.h" +#include "llxfer_file.h" +#include "llxfer_mem.h" +#include "llxfer_vfile.h" + +#include "llerror.h" +#include "lluuid.h" +#include "u64.h" + +const F32 LL_XFER_REGISTRATION_TIMEOUT = 60.0f; // timeout if a registered transfer hasn't been requested in 60 seconds +const F32 LL_PACKET_TIMEOUT = 3.0f; // packet timeout at 3 s +const S32 LL_PACKET_RETRY_LIMIT = 10; // packet retransmission limit + +const S32 LL_DEFAULT_MAX_SIMULTANEOUS_XFERS = 10; +const S32 LL_DEFAULT_MAX_REQUEST_FIFO_XFERS = 1000; + +#define LL_XFER_PROGRESS_MESSAGES 0 +#define LL_XFER_TEST_REXMIT 0 + + +/////////////////////////////////////////////////////////// + +LLXferManager::LLXferManager (LLVFS *vfs) +{ + init(vfs); +} + +/////////////////////////////////////////////////////////// + +LLXferManager::~LLXferManager () +{ + free(); +} + +/////////////////////////////////////////////////////////// + +void LLXferManager::init (LLVFS *vfs) +{ + mSendList = NULL; + mReceiveList = NULL; + + setMaxOutgoingXfersPerCircuit(LL_DEFAULT_MAX_SIMULTANEOUS_XFERS); + setMaxIncomingXfers(LL_DEFAULT_MAX_REQUEST_FIFO_XFERS); + + mVFS = vfs; + + // Turn on or off ack throttling + mUseAckThrottling = FALSE; + setAckThrottleBPS(100000); +} + +/////////////////////////////////////////////////////////// + +void LLXferManager::free () +{ + LLXfer *xferp; + LLXfer *delp; + + mOutgoingHosts.deleteAllData(); + + delp = mSendList; + while (delp) + { + xferp = delp->mNext; + delete delp; + delp = xferp; + } + mSendList = NULL; + + delp = mReceiveList; + while (delp) + { + xferp = delp->mNext; + delete delp; + delp = xferp; + } + mReceiveList = NULL; +} + +/////////////////////////////////////////////////////////// + +void LLXferManager::setMaxIncomingXfers(S32 max_num) +{ + mMaxIncomingXfers = max_num; +} + +/////////////////////////////////////////////////////////// + +void LLXferManager::setMaxOutgoingXfersPerCircuit(S32 max_num) +{ + mMaxOutgoingXfersPerCircuit = max_num; +} + +void LLXferManager::setUseAckThrottling(const BOOL use) +{ + mUseAckThrottling = use; +} + +void LLXferManager::setAckThrottleBPS(const F32 bps) +{ + // Let's figure out the min we can set based on the ack retry rate + // and number of simultaneous. + + // Assuming we're running as slow as possible, this is the lowest ack + // rate we can use. + F32 min_bps = (1000.f * 8.f* mMaxIncomingXfers) / LL_PACKET_TIMEOUT; + + // Set + F32 actual_rate = llmax(min_bps*1.1f, bps); + llinfos << "LLXferManager ack throttle min rate: " << min_bps << llendl; + llinfos << "LLXferManager ack throttle actual rate: " << actual_rate << llendl; + mAckThrottle.setRate(actual_rate); +} + + +/////////////////////////////////////////////////////////// + +void LLXferManager::updateHostStatus() +{ + LLXfer *xferp; + LLHostStatus *host_statusp = NULL; + + mOutgoingHosts.deleteAllData(); + + for (xferp = mSendList; xferp; xferp = xferp->mNext) + { + for (host_statusp = mOutgoingHosts.getFirstData(); host_statusp; host_statusp = mOutgoingHosts.getNextData()) + { + if (host_statusp->mHost == xferp->mRemoteHost) + { + break; + } + } + if (!host_statusp) + { + host_statusp = new LLHostStatus(); + if (host_statusp) + { + host_statusp->mHost = xferp->mRemoteHost; + mOutgoingHosts.addData(host_statusp); + } + } + if (host_statusp) + { + if (xferp->mStatus == e_LL_XFER_PENDING) + { + host_statusp->mNumPending++; + } + else if (xferp->mStatus == e_LL_XFER_IN_PROGRESS) + { + host_statusp->mNumActive++; + } + } + + } +} + +/////////////////////////////////////////////////////////// + +void LLXferManager::printHostStatus() +{ + LLHostStatus *host_statusp = NULL; + if (mOutgoingHosts.getFirstData()) + { + llinfos << "Outgoing Xfers:" << llendl; + + for (host_statusp = mOutgoingHosts.getFirstData(); host_statusp; host_statusp = mOutgoingHosts.getNextData()) + { + llinfos << " " << host_statusp->mHost << " active: " << host_statusp->mNumActive << " pending: " << host_statusp->mNumPending << llendl; + } + } +} + +/////////////////////////////////////////////////////////// + +LLXfer *LLXferManager::findXfer (U64 id, LLXfer *list_head) +{ + LLXfer *xferp; + for (xferp = list_head; xferp; xferp = xferp->mNext) + { + if (xferp->mID == id) + { + return(xferp); + } + } + return(NULL); +} + + +/////////////////////////////////////////////////////////// + +void LLXferManager::removeXfer (LLXfer *delp, LLXfer **list_head) +{ + LLXfer *xferp; + + if (delp) + { + if (*list_head == delp) + { + *list_head = delp->mNext; + delete (delp); + } + else + { + xferp = *list_head; + while (xferp->mNext) + { + if (xferp->mNext == delp) + { + xferp->mNext = delp->mNext; + delete (delp); + continue; + } + xferp = xferp->mNext; + } + } + } +} + +/////////////////////////////////////////////////////////// + +U32 LLXferManager::numActiveListEntries(LLXfer *list_head) +{ + U32 num_entries = 0; + + while (list_head) + { + if ((list_head->mStatus == e_LL_XFER_IN_PROGRESS)) + { + num_entries++; + } + list_head = list_head->mNext; + } + return(num_entries); +} + +/////////////////////////////////////////////////////////// + +S32 LLXferManager::numPendingXfers(const LLHost &host) +{ + LLHostStatus *host_statusp = NULL; + + for (host_statusp = mOutgoingHosts.getFirstData(); host_statusp; host_statusp = mOutgoingHosts.getNextData()) + { + if (host_statusp->mHost == host) + { + return (host_statusp->mNumPending); + } + } + return 0; +} + +/////////////////////////////////////////////////////////// + +S32 LLXferManager::numActiveXfers(const LLHost &host) +{ + LLHostStatus *host_statusp = NULL; + + for (host_statusp = mOutgoingHosts.getFirstData(); host_statusp; host_statusp = mOutgoingHosts.getNextData()) + { + if (host_statusp->mHost == host) + { + return (host_statusp->mNumActive); + } + } + return 0; +} + +/////////////////////////////////////////////////////////// + +void LLXferManager::changeNumActiveXfers(const LLHost &host, S32 delta) +{ + LLHostStatus *host_statusp = NULL; + + for (host_statusp = mOutgoingHosts.getFirstData(); host_statusp; host_statusp = mOutgoingHosts.getNextData()) + { + if (host_statusp->mHost == host) + { + host_statusp->mNumActive += delta; + } + } +} + +/////////////////////////////////////////////////////////// + +void LLXferManager::registerCallbacks(LLMessageSystem *msgsystem) +{ + msgsystem->setHandlerFuncFast(_PREHASH_ConfirmXferPacket, process_confirm_packet, NULL); + msgsystem->setHandlerFuncFast(_PREHASH_RequestXfer, process_request_xfer, NULL); + msgsystem->setHandlerFuncFast(_PREHASH_SendXferPacket, continue_file_receive, NULL); + msgsystem->setHandlerFuncFast(_PREHASH_AbortXfer, process_abort_xfer, NULL); +} + +/////////////////////////////////////////////////////////// + +U64 LLXferManager::getNextID () +{ + LLUUID a_guid; + + a_guid.generate(); + + + return(*((U64*)(a_guid.mData))); +} + +/////////////////////////////////////////////////////////// + +S32 LLXferManager::encodePacketNum(S32 packet_num, BOOL is_EOF) +{ + if (is_EOF) + { + packet_num |= 0x80000000; + } + return packet_num; +} + +/////////////////////////////////////////////////////////// + +S32 LLXferManager::decodePacketNum(S32 packet_num) +{ + return(packet_num & 0x0FFFFFFF); +} + +/////////////////////////////////////////////////////////// + +BOOL LLXferManager::isLastPacket(S32 packet_num) +{ + return(packet_num & 0x80000000); +} + +/////////////////////////////////////////////////////////// + +U64 LLXferManager::registerXfer(const void *datap, const S32 length) +{ + LLXfer *xferp; + U64 xfer_id = getNextID(); + + xferp = (LLXfer *) new LLXfer_Mem(); + if (xferp) + { + xferp->mNext = mSendList; + mSendList = xferp; + + xfer_id = ((LLXfer_Mem *)xferp)->registerXfer(xfer_id, datap,length); + + if (!xfer_id) + { + removeXfer(xferp,&mSendList); + } + } + else + { + llerrs << "Xfer allocation error" << llendl; + xfer_id = 0; + } + + return(xfer_id); +} + +/////////////////////////////////////////////////////////// + +void LLXferManager::requestFile(const char* local_filename, + const char* remote_filename, + ELLPath remote_path, + const LLHost& remote_host, + BOOL delete_remote_on_completion, + void (*callback)(void**,S32), + void** user_data, + BOOL is_priority, + BOOL use_big_packets) +{ + LLXfer *xferp; + + for (xferp = mReceiveList; xferp ; xferp = xferp->mNext) + { + if (xferp->getXferTypeTag() == LLXfer::XFER_FILE + && (((LLXfer_File*)xferp)->matchesLocalFilename(local_filename)) + && (((LLXfer_File*)xferp)->matchesRemoteFilename(remote_filename, remote_path)) + && (remote_host == xferp->mRemoteHost) + && (callback == xferp->mCallback) + && (user_data == xferp->mCallbackDataHandle)) + + { + // cout << "requested a xfer already in progress" << endl; + return; + } + } + + S32 chunk_size = use_big_packets ? LL_XFER_LARGE_PAYLOAD : -1; + xferp = (LLXfer *) new LLXfer_File(chunk_size); + if (xferp) + { + addToList(xferp, mReceiveList, is_priority); + + // Remove any file by the same name that happens to be lying + // around. + // Note: according to AaronB, this is here to deal with locks on files that were + // in transit during a crash, + if(delete_remote_on_completion && + (strstr(remote_filename,".tmp") == &remote_filename[strlen(remote_filename)-4])) /* Flawfinder : ignore */ + { + LLFile::remove(local_filename); + } + ((LLXfer_File *)xferp)->initializeRequest( + getNextID(), + local_filename, + remote_filename, + remote_path, + remote_host, + delete_remote_on_completion, + callback,user_data); + startPendingDownloads(); + } + else + { + llerrs << "Xfer allocation error" << llendl; + } +} + +void LLXferManager::requestFile(const char* remote_filename, + ELLPath remote_path, + const LLHost& remote_host, + BOOL delete_remote_on_completion, + void (*callback)(void*,S32,void**,S32), + void** user_data, + BOOL is_priority) +{ + LLXfer *xferp; + + xferp = (LLXfer *) new LLXfer_Mem(); + if (xferp) + { + addToList(xferp, mReceiveList, is_priority); + ((LLXfer_Mem *)xferp)->initializeRequest(getNextID(), + remote_filename, + remote_path, + remote_host, + delete_remote_on_completion, + callback, user_data); + startPendingDownloads(); + } + else + { + llerrs << "Xfer allocation error" << llendl; + } +} + +void LLXferManager::requestVFile(const LLUUID& local_id, + const LLUUID& remote_id, + LLAssetType::EType type, LLVFS* vfs, + const LLHost& remote_host, + void (*callback)(void**, S32), + void** user_data, + BOOL is_priority) +{ + LLXfer *xferp; + + for (xferp = mReceiveList; xferp ; xferp = xferp->mNext) + { + if (xferp->getXferTypeTag() == LLXfer::XFER_VFILE + && (((LLXfer_VFile*)xferp)->matchesLocalFile(local_id, type)) + && (((LLXfer_VFile*)xferp)->matchesRemoteFile(remote_id, type)) + && (remote_host == xferp->mRemoteHost) + && (callback == xferp->mCallback) + && (user_data == xferp->mCallbackDataHandle)) + + { + // cout << "requested a xfer already in progress" << endl; + return; + } + } + + xferp = (LLXfer *) new LLXfer_VFile(); + if (xferp) + { + addToList(xferp, mReceiveList, is_priority); + ((LLXfer_VFile *)xferp)->initializeRequest(getNextID(), + vfs, + local_id, + remote_id, + type, + remote_host, + callback, + user_data); + startPendingDownloads(); + } + else + { + llerrs << "Xfer allocation error" << llendl; + } + +} + +/* +void LLXferManager::requestXfer( + const char *local_filename, + BOOL delete_remote_on_completion, + U64 xfer_id, + const LLHost &remote_host, + void (*callback)(void **,S32), + void **user_data) +{ + LLXfer *xferp; + + for (xferp = mReceiveList; xferp ; xferp = xferp->mNext) + { + if (xferp->getXferTypeTag() == LLXfer::XFER_FILE + && (((LLXfer_File*)xferp)->matchesLocalFilename(local_filename)) + && (xfer_id == xferp->mID) + && (remote_host == xferp->mRemoteHost) + && (callback == xferp->mCallback) + && (user_data == xferp->mCallbackDataHandle)) + + { + // cout << "requested a xfer already in progress" << endl; + return; + } + } + + xferp = (LLXfer *) new LLXfer_File(); + if (xferp) + { + xferp->mNext = mReceiveList; + mReceiveList = xferp; + + ((LLXfer_File *)xferp)->initializeRequest(xfer_id,local_filename,"",LL_PATH_NONE,remote_host,delete_remote_on_completion,callback,user_data); + startPendingDownloads(); + } + else + { + llerrs << "Xfer allcoation error" << llendl; + } +} + +void LLXferManager::requestXfer(U64 xfer_id, const LLHost &remote_host, BOOL delete_remote_on_completion, void (*callback)(void *,S32,void **,S32),void **user_data) +{ + LLXfer *xferp; + + xferp = (LLXfer *) new LLXfer_Mem(); + if (xferp) + { + xferp->mNext = mReceiveList; + mReceiveList = xferp; + + ((LLXfer_Mem *)xferp)->initializeRequest(xfer_id,"",LL_PATH_NONE,remote_host,delete_remote_on_completion,callback,user_data); + startPendingDownloads(); + } + else + { + llerrs << "Xfer allcoation error" << llendl; + } +} +*/ +/////////////////////////////////////////////////////////// + +void LLXferManager::processReceiveData (LLMessageSystem *mesgsys, void ** /*user_data*/) +{ + // there's sometimes an extra 4 bytes added to an xfer payload + const S32 BUF_SIZE = LL_XFER_LARGE_PAYLOAD + 4; + char fdata_buf[LL_XFER_LARGE_PAYLOAD + 4]; /* Flawfinder : ignore */ + S32 fdata_size; + U64 id; + S32 packetnum; + LLXfer * xferp; + + mesgsys->getU64Fast(_PREHASH_XferID, _PREHASH_ID, id); + mesgsys->getS32Fast(_PREHASH_XferID, _PREHASH_Packet, packetnum); + + fdata_size = mesgsys->getSizeFast(_PREHASH_DataPacket,_PREHASH_Data); + mesgsys->getBinaryDataFast(_PREHASH_DataPacket, _PREHASH_Data, fdata_buf, 0, 0, BUF_SIZE); + + xferp = findXfer(id, mReceiveList); + + if (!xferp) + { + char U64_BUF[MAX_STRING]; /* Flawfinder : ignore */ + llwarns << "received xfer data from " << mesgsys->getSender() + << " for non-existent xfer id: " + << U64_to_str(id, U64_BUF, sizeof(U64_BUF)) << llendl; + return; + } + + S32 xfer_size; + + if (decodePacketNum(packetnum) != xferp->mPacketNum) // is the packet different from what we were expecting? + { + // confirm it if it was a resend of the last one, since the confirmation might have gotten dropped + if (decodePacketNum(packetnum) == (xferp->mPacketNum - 1)) + { + llinfos << "Reconfirming xfer " << xferp->mRemoteHost << ":" << xferp->getName() << " packet " << packetnum << llendl; sendConfirmPacket(mesgsys, id, decodePacketNum(packetnum), mesgsys->getSender()); + } + else + { + llinfos << "Ignoring xfer " << xferp->mRemoteHost << ":" << xferp->getName() << " recv'd packet " << packetnum << "; expecting " << xferp->mPacketNum << llendl; + } + return; + } + + S32 result = 0; + + if (xferp->mPacketNum == 0) // first packet has size encoded as additional S32 at beginning of data + { + ntohmemcpy(&xfer_size,fdata_buf,MVT_S32,sizeof(S32)); + +// do any necessary things on first packet ie. allocate memory + xferp->setXferSize(xfer_size); + + // adjust buffer start and size + result = xferp->receiveData(&(fdata_buf[sizeof(S32)]),fdata_size-(sizeof(S32))); + } + else + { + result = xferp->receiveData(fdata_buf,fdata_size); + } + + if (result == LL_ERR_CANNOT_OPEN_FILE) + { + xferp->abort(LL_ERR_CANNOT_OPEN_FILE); + removeXfer(xferp,&mReceiveList); + startPendingDownloads(); + return; + } + + xferp->mPacketNum++; // expect next packet + + if (!mUseAckThrottling) + { + // No throttling, confirm right away + sendConfirmPacket(mesgsys, id, decodePacketNum(packetnum), mesgsys->getSender()); + } + else + { + // Throttling, put on queue to be confirmed later. + LLXferAckInfo ack_info; + ack_info.mID = id; + ack_info.mPacketNum = decodePacketNum(packetnum); + ack_info.mRemoteHost = mesgsys->getSender(); + mXferAckQueue.push(ack_info); + } + + if (isLastPacket(packetnum)) + { + xferp->processEOF(); + removeXfer(xferp,&mReceiveList); + startPendingDownloads(); + } +} + +/////////////////////////////////////////////////////////// + +void LLXferManager::sendConfirmPacket (LLMessageSystem *mesgsys, U64 id, S32 packetnum, const LLHost &remote_host) +{ +#if LL_XFER_PROGRESS_MESSAGES + if (!(packetnum % 50)) + { + cout << "confirming xfer packet #" << packetnum << endl; + } +#endif + mesgsys->newMessageFast(_PREHASH_ConfirmXferPacket); + mesgsys->nextBlockFast(_PREHASH_XferID); + mesgsys->addU64Fast(_PREHASH_ID, id); + mesgsys->addU32Fast(_PREHASH_Packet, packetnum); + + mesgsys->sendMessage(remote_host); +} + +/////////////////////////////////////////////////////////// + +void LLXferManager::processFileRequest (LLMessageSystem *mesgsys, void ** /*user_data*/) +{ + + U64 id; + char local_filename[MAX_STRING]; /* Flawfinder : ignore */ + ELLPath local_path = LL_PATH_NONE; + S32 result = LL_ERR_NOERR; + LLUUID uuid; + LLAssetType::EType type; + S16 type_s16; + BOOL b_use_big_packets; + + mesgsys->getBOOL("XferID", "UseBigPackets", b_use_big_packets); + + mesgsys->getU64Fast(_PREHASH_XferID, _PREHASH_ID, id); + char U64_BUF[MAX_STRING]; /* Flawfinder : ignore */ + llinfos << "xfer request id: " << U64_to_str(id, U64_BUF, sizeof(U64_BUF)) + << " to " << mesgsys->getSender() << llendl; + + mesgsys->getStringFast(_PREHASH_XferID, _PREHASH_Filename, MAX_STRING, local_filename); + + U8 local_path_u8; + mesgsys->getU8("XferID", "FilePath", local_path_u8); + if( local_path_u8 < (U8)LL_PATH_COUNT ) + { + local_path = (ELLPath)local_path_u8; + } + else + { + llwarns << "Invalid file path in LLXferManager::processFileRequest() " << (U32)local_path_u8 << llendl; + } + + mesgsys->getUUIDFast(_PREHASH_XferID, _PREHASH_VFileID, uuid); + mesgsys->getS16Fast(_PREHASH_XferID, _PREHASH_VFileType, type_s16); + type = (LLAssetType::EType)type_s16; + + LLXfer *xferp; + + if (uuid != LLUUID::null) + { + if(NULL == LLAssetType::lookup(type)) + { + llwarns << "Invalid type for xfer request: " << uuid << ":" + << type_s16 << " to " << mesgsys->getSender() << llendl; + return; + } + + llinfos << "starting vfile transfer: " << uuid << "," << LLAssetType::lookup(type) << " to " << mesgsys->getSender() << llendl; + + if (! mVFS) + { + llwarns << "Attempt to send VFile w/o available VFS" << llendl; + return; + } + + xferp = (LLXfer *)new LLXfer_VFile(mVFS, uuid, type); + if (xferp) + { + xferp->mNext = mSendList; + mSendList = xferp; + result = xferp->startSend(id,mesgsys->getSender()); + } + else + { + llerrs << "Xfer allcoation error" << llendl; + } + } + else if (strlen(local_filename)) /* Flawfinder : ignore */ + { + std::string expanded_filename = gDirUtilp->getExpandedFilename( local_path, local_filename ); + llinfos << "starting file transfer: " << expanded_filename << " to " << mesgsys->getSender() << llendl; + + BOOL delete_local_on_completion = FALSE; + mesgsys->getBOOL("XferID", "DeleteOnCompletion", delete_local_on_completion); + + // -1 chunk_size causes it to use the default + xferp = (LLXfer *)new LLXfer_File(expanded_filename, delete_local_on_completion, b_use_big_packets ? LL_XFER_LARGE_PAYLOAD : -1); + + if (xferp) + { + xferp->mNext = mSendList; + mSendList = xferp; + result = xferp->startSend(id,mesgsys->getSender()); + } + else + { + llerrs << "Xfer allcoation error" << llendl; + } + } + else + { + char U64_BUF[MAX_STRING]; /* Flawfinder : ignore */ + llinfos << "starting memory transfer: " + << U64_to_str(id, U64_BUF, sizeof(U64_BUF)) << " to " + << mesgsys->getSender() << llendl; + + xferp = findXfer(id, mSendList); + + if (xferp) + { + result = xferp->startSend(id,mesgsys->getSender()); + } + else + { + llinfos << "Warning: " << U64_BUF << " not found." << llendl; + result = LL_ERR_FILE_NOT_FOUND; + } + } + + if (result) + { + if (xferp) + { + xferp->abort(result); + removeXfer(xferp,&mSendList); + } + else // can happen with a memory transfer not found + { + llinfos << "Aborting xfer to " << mesgsys->getSender() << " with error: " << result << llendl; + + mesgsys->newMessageFast(_PREHASH_AbortXfer); + mesgsys->nextBlockFast(_PREHASH_XferID); + mesgsys->addU64Fast(_PREHASH_ID, id); + mesgsys->addS32Fast(_PREHASH_Result, result); + + mesgsys->sendMessage(mesgsys->getSender()); + } + } + else if(xferp && (numActiveXfers(xferp->mRemoteHost) < mMaxOutgoingXfersPerCircuit)) + { + xferp->sendNextPacket(); + changeNumActiveXfers(xferp->mRemoteHost,1); +// llinfos << "***STARTING XFER IMMEDIATELY***" << llendl; + } + else + { + if(xferp) + { + llinfos << " queueing xfer request, " << numPendingXfers(xferp->mRemoteHost) << " ahead of this one" << llendl; + } + else + { + llwarns << "LLXferManager::processFileRequest() - no xfer found!" + << llendl; + } + } +} + +/////////////////////////////////////////////////////////// + +void LLXferManager::processConfirmation (LLMessageSystem *mesgsys, void ** /*user_data*/) +{ + U64 id = 0; + S32 packetNum = 0; + + mesgsys->getU64Fast(_PREHASH_XferID, _PREHASH_ID, id); + mesgsys->getS32Fast(_PREHASH_XferID, _PREHASH_Packet, packetNum); + + LLXfer* xferp = findXfer(id, mSendList); + if (xferp) + { +// cout << "confirmed packet #" << packetNum << " ping: "<< xferp->ACKTimer.getElapsedTimeF32() << endl; + xferp->mWaitingForACK = FALSE; + if (xferp->mStatus == e_LL_XFER_IN_PROGRESS) + { + xferp->sendNextPacket(); + } + else + { + removeXfer(xferp, &mSendList); + } + } +} + +/////////////////////////////////////////////////////////// + +void LLXferManager::retransmitUnackedPackets () +{ + LLXfer *xferp; + LLXfer *delp; + xferp = mReceiveList; + while(xferp) + { + if (xferp->mStatus == e_LL_XFER_IN_PROGRESS) + { + // if the circuit dies, abort + if (! gMessageSystem->mCircuitInfo.isCircuitAlive( xferp->mRemoteHost )) + { + llinfos << "Xfer found in progress on dead circuit, aborting" << llendl; + xferp->mCallbackResult = LL_ERR_CIRCUIT_GONE; + xferp->processEOF(); + delp = xferp; + xferp = xferp->mNext; + removeXfer(delp,&mReceiveList); + continue; + } + + } + xferp = xferp->mNext; + } + + xferp = mSendList; + updateHostStatus(); + F32 et; + while (xferp) + { + if (xferp->mWaitingForACK && ( (et = xferp->ACKTimer.getElapsedTimeF32()) > LL_PACKET_TIMEOUT)) + { + if (xferp->mRetries > LL_PACKET_RETRY_LIMIT) + { + llinfos << "dropping xfer " << xferp->mRemoteHost << ":" << xferp->getName() << " packet retransmit limit exceeded, xfer dropped" << llendl; + xferp->abort(LL_ERR_TCP_TIMEOUT); + delp = xferp; + xferp = xferp->mNext; + removeXfer(delp,&mSendList); + } + else + { + llinfos << "resending xfer " << xferp->mRemoteHost << ":" << xferp->getName() << " packet unconfirmed after: "<< et << " sec, packet " << xferp->mPacketNum << llendl; + xferp->resendLastPacket(); + xferp = xferp->mNext; + } + } + else if ((xferp->mStatus == e_LL_XFER_REGISTERED) && ( (et = xferp->ACKTimer.getElapsedTimeF32()) > LL_XFER_REGISTRATION_TIMEOUT)) + { + llinfos << "registered xfer never requested, xfer dropped" << llendl; + xferp->abort(LL_ERR_TCP_TIMEOUT); + delp = xferp; + xferp = xferp->mNext; + removeXfer(delp,&mSendList); + } + else if (xferp->mStatus == e_LL_XFER_ABORTED) + { + llwarns << "Removing aborted xfer " << xferp->mRemoteHost << ":" << xferp->getName() << llendl; + delp = xferp; + xferp = xferp->mNext; + removeXfer(delp,&mSendList); + } + else if (xferp->mStatus == e_LL_XFER_PENDING) + { +// llinfos << "*** numActiveXfers = " << numActiveXfers(xferp->mRemoteHost) << " mMaxOutgoingXfersPerCircuit = " << mMaxOutgoingXfersPerCircuit << llendl; + if (numActiveXfers(xferp->mRemoteHost) < mMaxOutgoingXfersPerCircuit) + { +// llinfos << "bumping pending xfer to active" << llendl; + xferp->sendNextPacket(); + changeNumActiveXfers(xferp->mRemoteHost,1); + } + xferp = xferp->mNext; + } + else + { + xferp = xferp->mNext; + } + } + + // + // HACK - if we're using xfer confirm throttling, throttle our xfer confirms here + // so we don't blow through bandwidth. + // + + while (mXferAckQueue.getLength()) + { + if (mAckThrottle.checkOverflow(1000.0f*8.0f)) + { + break; + } + //llinfos << "Confirm packet queue length:" << mXferAckQueue.getLength() << llendl; + LLXferAckInfo ack_info; + mXferAckQueue.pop(ack_info); + //llinfos << "Sending confirm packet" << llendl; + sendConfirmPacket(gMessageSystem, ack_info.mID, ack_info.mPacketNum, ack_info.mRemoteHost); + mAckThrottle.throttleOverflow(1000.f*8.f); // Assume 1000 bytes/packet + } +} + + +/////////////////////////////////////////////////////////// + +void LLXferManager::processAbort (LLMessageSystem *mesgsys, void ** /*user_data*/) +{ + U64 id = 0; + S32 result_code = 0; + LLXfer * xferp; + + mesgsys->getU64Fast(_PREHASH_XferID, _PREHASH_ID, id); + mesgsys->getS32Fast(_PREHASH_XferID, _PREHASH_Result, result_code); + + xferp = findXfer(id, mReceiveList); + if (xferp) + { + xferp->mCallbackResult = result_code; + xferp->processEOF(); + removeXfer(xferp, &mReceiveList); + startPendingDownloads(); + } +} + +/////////////////////////////////////////////////////////// + +void LLXferManager::startPendingDownloads() +{ + // This method goes through the list, and starts pending + // operations until active downloads == mMaxIncomingXfers. I copy + // the pending xfers into a temporary data structure because the + // xfers are stored as an intrusive linked list where older + // requests get pushed toward the back. Thus, if we didn't do a + // stateful iteration, it would be possible for old requests to + // never start. + LLXfer* xferp = mReceiveList; + LLLinkedList pending_downloads; + S32 download_count = 0; + S32 pending_count = 0; + while(xferp) + { + if(xferp->mStatus == e_LL_XFER_PENDING) + { + ++pending_count; // getLength() is O(N), so track it here. + pending_downloads.addData(xferp); + } + else if(xferp->mStatus == e_LL_XFER_IN_PROGRESS) + { + ++download_count; + } + xferp = xferp->mNext; + } + + S32 start_count = mMaxIncomingXfers - download_count; + + lldebugs << "LLXferManager::startPendingDownloads() - XFER_IN_PROGRESS: " + << download_count << " XFER_PENDING: " << pending_count + << " startring " << llmin(start_count, pending_count) << llendl; + + if((start_count > 0) && (pending_count > 0)) + { + S32 result; + xferp = pending_downloads.getFirstData(); + while(start_count-- && xferp) + { + result = xferp->startDownload(); + if(result) + { + xferp->abort(result); + ++start_count; + } + xferp = pending_downloads.getNextData(); + } + } +} + +/////////////////////////////////////////////////////////// + +void LLXferManager::addToList(LLXfer* xferp, LLXfer*& head, BOOL is_priority) +{ + if(is_priority) + { + xferp->mNext = NULL; + LLXfer* next = head; + if(next) + { + while(next->mNext) + { + next = next->mNext; + } + next->mNext = xferp; + } + else + { + head = xferp; + } + } + else + { + xferp->mNext = head; + head = xferp; + } +} + +/////////////////////////////////////////////////////////// +// Globals and C routines +/////////////////////////////////////////////////////////// + +LLXferManager *gXferManager = NULL; + + +void start_xfer_manager(LLVFS *vfs) +{ + gXferManager = new LLXferManager(vfs); +} + +void cleanup_xfer_manager() +{ + if (gXferManager) + { + delete(gXferManager); + gXferManager = NULL; + } +} + +void process_confirm_packet (LLMessageSystem *mesgsys, void **user_data) +{ + gXferManager->processConfirmation(mesgsys,user_data); +} + +void process_request_xfer(LLMessageSystem *mesgsys, void **user_data) +{ + gXferManager->processFileRequest(mesgsys,user_data); +} + +void continue_file_receive(LLMessageSystem *mesgsys, void **user_data) +{ +#if LL_TEST_XFER_REXMIT + if (frand(1.f) > 0.05f) + { +#endif + gXferManager->processReceiveData(mesgsys,user_data); +#if LL_TEST_XFER_REXMIT + } + else + { + cout << "oops! dropped a xfer packet" << endl; + } +#endif +} + +void process_abort_xfer(LLMessageSystem *mesgsys, void **user_data) +{ + gXferManager->processAbort(mesgsys,user_data); +} + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/indra/llmessage/llxfermanager.h b/indra/llmessage/llxfermanager.h new file mode 100644 index 0000000000..eca3684df5 --- /dev/null +++ b/indra/llmessage/llxfermanager.h @@ -0,0 +1,187 @@ +/** + * @file llxfermanager.h + * @brief definition of LLXferManager class for a keeping track of + * multiple xfers + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLXFERMANAGER_H +#define LL_LLXFERMANAGER_H + +/** + * this manager keeps both a send list and a receive list; anything with a + * LLXferManager can send and receive files via messages + */ + +//Forward declaration to avoid circular dependencies +class LLXfer; +class LLVFS; + +#include "llxfer.h" +#include "message.h" +#include "llassetstorage.h" +#include "linked_lists.h" +#include "lldir.h" +#include "lllinkedqueue.h" +#include "llthrottle.h" + +class LLHostStatus +{ + public: + LLHost mHost; + S32 mNumActive; + S32 mNumPending; + + LLHostStatus() {mNumActive = 0; mNumPending = 0;}; + virtual ~LLHostStatus(){}; +}; + +// Class stores ack information, to be put on list so we can throttle xfer rate. +class LLXferAckInfo +{ +public: + LLXferAckInfo(U32 dummy = 0) + { + mID = 0; + mPacketNum = -1; + } + + U64 mID; + S32 mPacketNum; + LLHost mRemoteHost; +}; + +class LLXferManager +{ + private: + LLVFS *mVFS; + + protected: + S32 mMaxOutgoingXfersPerCircuit; + S32 mMaxIncomingXfers; + + BOOL mUseAckThrottling; // Use ack throttling to cap file xfer bandwidth + LLLinkedQueue mXferAckQueue; + LLThrottle mAckThrottle; + public: + + // This enumeration is useful in the requestFile() to specify if + // an xfer must happen asap. + enum + { + LOW_PRIORITY = FALSE, + HIGH_PRIORITY = TRUE, + }; + + LLXfer *mSendList; + LLXfer *mReceiveList; + + LLLinkedList mOutgoingHosts; + + private: + protected: + // implementation methods + virtual void startPendingDownloads(); + virtual void addToList(LLXfer* xferp, LLXfer*& head, BOOL is_priority); + + public: + LLXferManager(LLVFS *vfs); + virtual ~LLXferManager(); + + virtual void init(LLVFS *vfs); + virtual void free(); + + void setUseAckThrottling(const BOOL use); + void setAckThrottleBPS(const F32 bps); + +// list management routines + virtual LLXfer *findXfer(U64 id, LLXfer *list_head); + virtual void removeXfer (LLXfer *delp, LLXfer **list_head); + virtual U32 numActiveListEntries(LLXfer *list_head); + virtual S32 numActiveXfers(const LLHost &host); + virtual S32 numPendingXfers(const LLHost &host); + virtual void changeNumActiveXfers(const LLHost &host, S32 delta); + + virtual void setMaxOutgoingXfersPerCircuit (S32 max_num); + virtual void setMaxIncomingXfers(S32 max_num); + virtual void updateHostStatus(); + virtual void printHostStatus(); + +// general utility routines + virtual void registerCallbacks(LLMessageSystem *mesgsys); + virtual U64 getNextID (); + virtual S32 encodePacketNum(S32 packet_num, BOOL is_eof); + virtual S32 decodePacketNum(S32 packet_num); + virtual BOOL isLastPacket(S32 packet_num); + + virtual U64 registerXfer(const void *datap, const S32 length); + +// file requesting routines +// .. to file + virtual void requestFile(const char* local_filename, + const char* remote_filename, + ELLPath remote_path, + const LLHost& remote_host, + BOOL delete_remote_on_completion, + void (*callback)(void**,S32), void** user_data, + BOOL is_priority = FALSE, + BOOL use_big_packets = FALSE); + +// .. to memory + virtual void requestFile(const char* remote_filename, + ELLPath remote_path, + const LLHost &remote_host, + BOOL delete_remote_on_completion, + void (*callback)(void*, S32, void**, S32), + void** user_data, + BOOL is_priority = FALSE); + +// vfile requesting +// .. to vfile + virtual void requestVFile(const LLUUID &local_id, const LLUUID& remote_id, + LLAssetType::EType type, LLVFS* vfs, + const LLHost& remote_host, + void (*callback)(void**, S32), void** user_data, + BOOL is_priority = FALSE); + +/* +// xfer request (may be memory or file) +// .. to file + virtual void requestXfer(const char *local_filename, U64 xfer_id, + BOOL delete_remote_on_completion, + const LLHost &remote_host, void (*callback)(void **,S32),void **user_data); +// .. to memory + virtual void requestXfer(U64 xfer_id, + const LLHost &remote_host, + BOOL delete_remote_on_completion, + void (*callback)(void *, S32, void **, S32),void **user_data); +*/ + + virtual void processReceiveData (LLMessageSystem *mesgsys, void **user_data); + virtual void sendConfirmPacket (LLMessageSystem *mesgsys, U64 id, S32 packetnum, const LLHost &remote_host); + +// file sending routines + virtual void processFileRequest (LLMessageSystem *mesgsys, void **user_data); + virtual void processConfirmation (LLMessageSystem *mesgsys, void **user_data); + virtual void retransmitUnackedPackets (); + +// error handling + virtual void processAbort (LLMessageSystem *mesgsys, void **user_data); +}; + +extern LLXferManager* gXferManager; + +// initialization and garbage collection +void start_xfer_manager(LLVFS *vfs); +void cleanup_xfer_manager(); + +// message system callbacks +void process_confirm_packet (LLMessageSystem *mesgsys, void **user_data); +void process_request_xfer (LLMessageSystem *mesgsys, void **user_data); +void continue_file_receive(LLMessageSystem *mesgsys, void **user_data); +void process_abort_xfer (LLMessageSystem *mesgsys, void **user_data); +#endif + + diff --git a/indra/llmessage/llxorcipher.cpp b/indra/llmessage/llxorcipher.cpp new file mode 100644 index 0000000000..1fbbfec9e0 --- /dev/null +++ b/indra/llmessage/llxorcipher.cpp @@ -0,0 +1,108 @@ +/** + * @file llxorcipher.cpp + * @brief Implementation of LLXORCipher + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llcrypto.h" +#include "llerror.h" + +///---------------------------------------------------------------------------- +/// Class LLXORCipher +///---------------------------------------------------------------------------- + +LLXORCipher::LLXORCipher(const U8* pad, U32 pad_len) : + mPad(NULL), + mHead(NULL), + mPadLen(0) +{ + init(pad, pad_len); +} + +// Destroys the object +LLXORCipher::~LLXORCipher() +{ + init(NULL, 0); +} + +LLXORCipher::LLXORCipher(const LLXORCipher& cipher) : + mPad(NULL), + mHead(NULL), + mPadLen(0) +{ + init(cipher.mPad, cipher.mPadLen); +} + +LLXORCipher& LLXORCipher::operator=(const LLXORCipher& cipher) +{ + if(this == &cipher) return *this; + init(cipher.mPad, cipher.mPadLen); + return *this; +} + +BOOL LLXORCipher::encrypt(const U8* src, U32 src_len, U8* dst, U32 dst_len) +{ + if(!src || !src_len || !dst || !dst_len || !mPad) return FALSE; + U8* pad_end = mPad + mPadLen; + while(src_len--) + { + *dst++ = *src++ ^ *mHead++; + if(mHead >= pad_end) mHead = mPad; + } + return TRUE; +} + +BOOL LLXORCipher::decrypt(const U8* src, U32 src_len, U8* dst, U32 dst_len) +{ + // xor is a symetric cipher, thus, just call the other function. + return encrypt(src, src_len, dst, dst_len); +} + +U32 LLXORCipher::requiredEncryptionSpace(U32 len) +{ + return len; +} + +void LLXORCipher::init(const U8* pad, U32 pad_len) +{ + if(mPad) + { + delete [] mPad; + mPad = NULL; + mPadLen = 0; + } + if(pad && pad_len) + { + mPadLen = pad_len; + mPad = new U8[mPadLen]; + if (mPad != NULL) + { + memcpy(mPad, pad, mPadLen); /* Flawfinder : ignore */ + } + } + mHead = mPad; +} + +#ifdef _DEBUG +// static +BOOL LLXORCipher::testHarness() +{ + const U32 PAD_LEN = 3; + const U8 PAD[] = "abc"; + const S32 MSG_LENGTH = 12; + const char MESSAGE[MSG_LENGTH+1] = "gesundheight"; /* Flawfinder : ignore */ + U8 encrypted[MSG_LENGTH]; + U8 decrypted[MSG_LENGTH]; + + LLXORCipher cipher(PAD, PAD_LEN); + cipher.encrypt((U8*)MESSAGE, MSG_LENGTH, encrypted, MSG_LENGTH); + cipher.decrypt(encrypted, MSG_LENGTH, decrypted, MSG_LENGTH); + + if(0 != memcmp((void*)MESSAGE, decrypted, MSG_LENGTH)) return FALSE; + return TRUE; +} +#endif diff --git a/indra/llmessage/machine.h b/indra/llmessage/machine.h new file mode 100644 index 0000000000..b5efe717d8 --- /dev/null +++ b/indra/llmessage/machine.h @@ -0,0 +1,101 @@ +/** + * @file machine.h + * @brief LLMachine class header file + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_MACHINE_H +#define LL_MACHINE_H + +#include "llerror.h" +#include "net.h" +#include "llhost.h" + +typedef enum e_machine_type +{ + MT_NULL, + MT_SIMULATOR, + MT_VIEWER, + MT_SPACE_SERVER, + MT_OBJECT_REPOSITORY, + MT_PROXY, + MT_EOF +} EMachineType; + +const U32 ADDRESS_STRING_SIZE = 12; + +class LLMachine +{ +public: + LLMachine() + : mMachineType(MT_NULL), mControlPort(0) {} + + LLMachine(EMachineType machine_type, U32 ip, S32 port) + : mMachineType(machine_type), mControlPort(0), mHost(ip,port) {} + + LLMachine(EMachineType machine_type, const LLHost &host) + : mMachineType(machine_type) {mHost = host; mControlPort = 0;} + + ~LLMachine() {} + + // get functions + EMachineType getMachineType() const { return mMachineType; } + const U32 getMachineIP() const { return mHost.getAddress(); } + const S32 getMachinePort() const { return mHost.getPort(); } + const LLHost &getMachineHost() const { return mHost; } + // The control port is the listen port of the parent process that + // launched this machine. 0 means none or not known. + const S32 &getControlPort() const { return mControlPort; } + BOOL isValid() const { return (mHost.getPort() != 0); } // TRUE if corresponds to functioning machine + + // set functions + void setMachineType(EMachineType machine_type) { mMachineType = machine_type; } + void setMachineIP(U32 ip) { mHost.setAddress(ip); } + void setMachineHost(const LLHost &host) { mHost = host; } + + void setMachinePort(S32 port) + { + if (port < 0) + { + llinfos << "Can't assign a negative number to LLMachine::mPort" << llendl; + mHost.setPort(0); + } + else + { + mHost.setPort(port); + } + } + + void setControlPort( S32 port ) + { + if (port < 0) + { + llinfos << "Can't assign a negative number to LLMachine::mControlPort" << llendl; + mControlPort = 0; + } + else + { + mControlPort = port; + } + } + + + // member variables + +// Someday these should be made private. +// When they are, some of the code that breaks should +// become member functions of LLMachine -- Leviathan +//private: + + // I fixed the others, somebody should fix these! - djs + EMachineType mMachineType; + +protected: + + S32 mControlPort; + LLHost mHost; +}; + +#endif diff --git a/indra/llmessage/mean_collision_data.h b/indra/llmessage/mean_collision_data.h new file mode 100644 index 0000000000..7d3f90cde6 --- /dev/null +++ b/indra/llmessage/mean_collision_data.h @@ -0,0 +1,81 @@ +/** + * @file mean_collision_data.h + * @brief data type to log interactions between stuff and agents that + * might be community standards violations + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_MEAN_COLLISIONS_DATA_H +#define LL_MEAN_COLLISIONS_DATA_H + +#include +#include "lldbstrings.h" + +const F32 MEAN_COLLISION_TIMEOUT = 5.f; +const S32 MAX_MEAN_COLLISIONS = 5; + +typedef enum e_mean_collision_types +{ + MEAN_INVALID, + MEAN_BUMP, + MEAN_LLPUSHOBJECT, + MEAN_SELECTED_OBJECT_COLLIDE, + MEAN_SCRIPTED_OBJECT_COLLIDE, + MEAN_PHYSICAL_OBJECT_COLLIDE, + MEAN_EOF +} EMeanCollisionType; + +class LLMeanCollisionData +{ +public: + LLMeanCollisionData(const LLUUID &victim, const LLUUID &perp, time_t time, EMeanCollisionType type, F32 mag) + : mVictim(victim), mPerp(perp), mTime(time), mType(type), mMag(mag) + { mFirstName[0] = 0; mLastName[0] = 0; } + + LLMeanCollisionData(LLMeanCollisionData *mcd) + : mVictim(mcd->mVictim), mPerp(mcd->mPerp), mTime(mcd->mTime), mType(mcd->mType), mMag(mcd->mMag) + { + strncpy(mFirstName, mcd->mFirstName, sizeof(mFirstName) -1); /* Flawfinder: Ignore */ + mFirstName[sizeof(mFirstName) -1] = '\0'; + strncpy(mLastName, mcd->mLastName, sizeof(mLastName) -1); /* Flawfinder: Ignore */ + mLastName[sizeof(mLastName) -1] = '\0'; + } + + friend std::ostream& operator<<(std::ostream& s, const LLMeanCollisionData &a) + { + switch(a.mType) + { + case MEAN_BUMP: + s << "Mean Collision: " << a.mPerp << " bumped " << a.mVictim << " with a velocity of " << a.mMag << " at " << ctime(&a.mTime); + break; + case MEAN_LLPUSHOBJECT: + s << "Mean Collision: " << a.mPerp << " llPushObject-ed " << a.mVictim << " with a total force of " << a.mMag << " at "<< ctime(&a.mTime); + break; + case MEAN_SELECTED_OBJECT_COLLIDE: + s << "Mean Collision: " << a.mPerp << " dragged an object into " << a.mVictim << " with a velocity of " << a.mMag << " at "<< ctime(&a.mTime); + break; + case MEAN_SCRIPTED_OBJECT_COLLIDE: + s << "Mean Collision: " << a.mPerp << " smacked " << a.mVictim << " with a scripted object with velocity of " << a.mMag << " at "<< ctime(&a.mTime); + break; + case MEAN_PHYSICAL_OBJECT_COLLIDE: + s << "Mean Collision: " << a.mPerp << " smacked " << a.mVictim << " with a physical object with velocity of " << a.mMag << " at "<< ctime(&a.mTime); + break; + default: + break; + } + return s; + } + + LLUUID mVictim; + LLUUID mPerp; + time_t mTime; + EMeanCollisionType mType; + F32 mMag; + char mFirstName[DB_FIRST_NAME_BUF_SIZE]; /* Flawfinder: Ignore */ + char mLastName[DB_LAST_NAME_BUF_SIZE]; /* Flawfinder: Ignore */ +}; + + +#endif diff --git a/indra/llmessage/message.cpp b/indra/llmessage/message.cpp new file mode 100644 index 0000000000..cdafafc8db --- /dev/null +++ b/indra/llmessage/message.cpp @@ -0,0 +1,5876 @@ +/** + * @file message.cpp + * @brief LLMessageSystem class implementation + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "message.h" + +// system library includes +#if !LL_WINDOWS +// following header files required for inet_addr() +#include +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include "llapr.h" +#include "apr-1/apr_portable.h" +#include "apr-1/apr_network_io.h" +#include "apr-1/apr_poll.h" + +// linden library headers +#include "indra_constants.h" +#include "lldir.h" +#include "llerror.h" +#include "llfasttimer.h" +#include "llmd5.h" +#include "llsd.h" +#include "lltransfermanager.h" +#include "lluuid.h" +#include "llxfermanager.h" +#include "timing.h" +#include "llquaternion.h" +#include "u64.h" +#include "v3dmath.h" +#include "v3math.h" +#include "v4math.h" +#include "lltransfertargetvfile.h" + +// Constants +//const char* MESSAGE_LOG_FILENAME = "message.log"; +static const F32 CIRCUIT_DUMP_TIMEOUT = 30.f; +static const S32 TRUST_TIME_WINDOW = 3; + +class LLMsgVarData +{ +public: + LLMsgVarData() : mName(NULL), mSize(-1), mDataSize(-1), mData(NULL), mType(MVT_U8) + { + } + + LLMsgVarData(const char *name, EMsgVariableType type) : mSize(-1), mDataSize(-1), mData(NULL), mType(type) + { + mName = (char *)name; + } + + ~LLMsgVarData() + { + // copy constructor just copies the mData pointer, so only delete mData explicitly + } + + void deleteData() + { + delete[] mData; + mData = NULL; + } + + void addData(const void *indata, S32 size, EMsgVariableType type, S32 data_size = -1); + + char *getName() const { return mName; } + S32 getSize() const { return mSize; } + void *getData() { return (void*)mData; } + S32 getDataSize() const { return mDataSize; } + EMsgVariableType getType() const { return mType; } + +protected: + char *mName; + S32 mSize; + S32 mDataSize; + + U8 *mData; + EMsgVariableType mType; +}; + + +class LLMsgBlkData +{ +public: + LLMsgBlkData(const char *name, S32 blocknum) : mOffset(-1), mBlockNumber(blocknum), mTotalSize(-1) + { + mName = (char *)name; + } + + ~LLMsgBlkData() + { + for (msg_var_data_map_t::iterator iter = mMemberVarData.begin(); + iter != mMemberVarData.end(); iter++) + { + iter->deleteData(); + } + } + + void addVariable(const char *name, EMsgVariableType type) + { + LLMsgVarData tmp(name,type); + mMemberVarData[name] = tmp; + } + + void addData(char *name, const void *data, S32 size, EMsgVariableType type, S32 data_size = -1) + { + LLMsgVarData* temp = &mMemberVarData[name]; // creates a new entry if one doesn't exist + temp->addData(data, size, type, data_size); + } + + S32 mOffset; + S32 mBlockNumber; + typedef LLDynamicArrayIndexed msg_var_data_map_t; + msg_var_data_map_t mMemberVarData; + char *mName; + S32 mTotalSize; +}; + + +class LLMsgData +{ +public: + LLMsgData(const char *name) : mTotalSize(-1) + { + mName = (char *)name; + } + ~LLMsgData() + { + for_each(mMemberBlocks.begin(), mMemberBlocks.end(), DeletePairedPointer()); + } + + void addBlock(LLMsgBlkData *blockp) + { + mMemberBlocks[blockp->mName] = blockp; + } + + void addDataFast(char *blockname, char *varname, const void *data, S32 size, EMsgVariableType type, S32 data_size = -1); + +public: + S32 mOffset; + typedef std::map msg_blk_data_map_t; + msg_blk_data_map_t mMemberBlocks; + char *mName; + S32 mTotalSize; +}; + +inline void LLMsgVarData::addData(const void *data, S32 size, EMsgVariableType type, S32 data_size) +{ + mSize = size; + mDataSize = data_size; + if ( (type != MVT_VARIABLE) && (type != MVT_FIXED) + && (mType != MVT_VARIABLE) && (mType != MVT_FIXED)) + { + if (mType != type) + { + llwarns << "Type mismatch in addData for " << mName + << " message: " << gMessageSystem->getCurrentSMessageName() + << " block: " << gMessageSystem->getCurrentSBlockName() + << llendl; + } + } + if(size) + { + delete mData; // Delete it if it already exists + mData = new U8[size]; + htonmemcpy(mData, data, mType, size); + } +} + + + +inline void LLMsgData::addDataFast(char *blockname, char *varname, const void *data, S32 size, EMsgVariableType type, S32 data_size) +{ + // remember that if the blocknumber is > 0 then the number is appended to the name + char *namep = (char *)blockname; + LLMsgBlkData* block_data = mMemberBlocks[namep]; + if (block_data->mBlockNumber) + { + namep += block_data->mBlockNumber; + block_data->addData(varname, data, size, type, data_size); + } + else + { + block_data->addData(varname, data, size, type, data_size); + } +} + +// LLMessage* classes store the template of messages + + +class LLMessageVariable +{ +public: + LLMessageVariable() : mName(NULL), mType(MVT_NULL), mSize(-1) + { + } + + LLMessageVariable(char *name) : mType(MVT_NULL), mSize(-1) + { + mName = name; + } + + LLMessageVariable(char *name, const EMsgVariableType type, const S32 size) : mType(type), mSize(size) + { + mName = gMessageStringTable.getString(name); + } + + ~LLMessageVariable() {} + + friend std::ostream& operator<<(std::ostream& s, LLMessageVariable &msg); + + EMsgVariableType getType() const { return mType; } + S32 getSize() const { return mSize; } + char *getName() const { return mName; } +protected: + char *mName; + EMsgVariableType mType; + S32 mSize; +}; + + +typedef enum e_message_block_type +{ + MBT_NULL, + MBT_SINGLE, + MBT_MULTIPLE, + MBT_VARIABLE, + MBT_EOF +} EMsgBlockType; + +class LLMessageBlock +{ +public: + LLMessageBlock(char *name, EMsgBlockType type, S32 number = 1) : mType(type), mNumber(number), mTotalSize(0) + { + mName = gMessageStringTable.getString(name); + } + + ~LLMessageBlock() + { + for_each(mMemberVariables.begin(), mMemberVariables.end(), DeletePairedPointer()); + } + + void addVariable(char *name, const EMsgVariableType type, const S32 size) + { + LLMessageVariable** varp = &mMemberVariables[name]; + if (*varp != NULL) + { + llerrs << name << " has already been used as a variable name!" << llendl; + } + *varp = new LLMessageVariable(name, type, size); + if (((*varp)->getType() != MVT_VARIABLE) + &&(mTotalSize != -1)) + { + mTotalSize += (*varp)->getSize(); + } + else + { + mTotalSize = -1; + } + } + + EMsgVariableType getVariableType(char *name) + { + return (mMemberVariables[name])->getType(); + } + + S32 getVariableSize(char *name) + { + return (mMemberVariables[name])->getSize(); + } + + friend std::ostream& operator<<(std::ostream& s, LLMessageBlock &msg); + + typedef std::map message_variable_map_t; + message_variable_map_t mMemberVariables; + char *mName; + EMsgBlockType mType; + S32 mNumber; + S32 mTotalSize; +}; + + +enum EMsgFrequency +{ + MFT_NULL = 0, // value is size of message number in bytes + MFT_HIGH = 1, + MFT_MEDIUM = 2, + MFT_LOW = 4 +}; + +typedef enum e_message_trust +{ + MT_TRUST, + MT_NOTRUST +} EMsgTrust; + +enum EMsgEncoding +{ + ME_UNENCODED, + ME_ZEROCODED +}; + +class LLMessageTemplate +{ +public: + LLMessageTemplate(const char *name, U32 message_number, EMsgFrequency freq) + : + //mMemberBlocks(), + mName(NULL), + mFrequency(freq), + mTrust(MT_NOTRUST), + mEncoding(ME_ZEROCODED), + mMessageNumber(message_number), + mTotalSize(0), + mReceiveCount(0), + mReceiveBytes(0), + mReceiveInvalid(0), + mDecodeTimeThisFrame(0.f), + mTotalDecoded(0), + mTotalDecodeTime(0.f), + mMaxDecodeTimePerMsg(0.f), + mBanFromTrusted(false), + mBanFromUntrusted(false), + mHandlerFunc(NULL), + mUserData(NULL) + { + mName = gMessageStringTable.getString(name); + } + + ~LLMessageTemplate() + { + for_each(mMemberBlocks.begin(), mMemberBlocks.end(), DeletePairedPointer()); + } + + void addBlock(LLMessageBlock *blockp) + { + LLMessageBlock** member_blockp = &mMemberBlocks[blockp->mName]; + if (*member_blockp != NULL) + { + llerrs << "Block " << blockp->mName + << "has already been used as a block name!" << llendl; + } + *member_blockp = blockp; + if ( (mTotalSize != -1) + &&(blockp->mTotalSize != -1) + &&( (blockp->mType == MBT_SINGLE) + ||(blockp->mType == MBT_MULTIPLE))) + { + mTotalSize += blockp->mNumber*blockp->mTotalSize; + } + else + { + mTotalSize = -1; + } + } + + LLMessageBlock *getBlock(char *name) + { + return mMemberBlocks[name]; + } + + // Trusted messages can only be recieved on trusted circuits. + void setTrust(EMsgTrust t) + { + mTrust = t; + } + + EMsgTrust getTrust(void) + { + return mTrust; + } + + // controls for how the message should be encoded + void setEncoding(EMsgEncoding e) + { + mEncoding = e; + } + EMsgEncoding getEncoding() + { + return mEncoding; + } + + void setHandlerFunc(void (*handler_func)(LLMessageSystem *msgsystem, void **user_data), void **user_data) + { + mHandlerFunc = handler_func; + mUserData = user_data; + } + + BOOL callHandlerFunc(LLMessageSystem *msgsystem) + { + if (mHandlerFunc) + { + mHandlerFunc(msgsystem, mUserData); + return TRUE; + } + return FALSE; + } + + bool isBanned(bool trustedSource) + { + return trustedSource ? mBanFromTrusted : mBanFromUntrusted; + } + + friend std::ostream& operator<<(std::ostream& s, LLMessageTemplate &msg); + +public: + typedef std::map message_block_map_t; + message_block_map_t mMemberBlocks; + char *mName; + EMsgFrequency mFrequency; + EMsgTrust mTrust; + EMsgEncoding mEncoding; + U32 mMessageNumber; + S32 mTotalSize; + U32 mReceiveCount; // how many of this template have been received since last reset + U32 mReceiveBytes; // How many bytes received + U32 mReceiveInvalid; // How many "invalid" packets + F32 mDecodeTimeThisFrame; // Total seconds spent decoding this frame + U32 mTotalDecoded; // Total messages successfully decoded + F32 mTotalDecodeTime; // Total time successfully decoding messages + F32 mMaxDecodeTimePerMsg; + + bool mBanFromTrusted; + bool mBanFromUntrusted; + +private: + // message handler function (this is set by each application) + void (*mHandlerFunc)(LLMessageSystem *msgsystem, void **user_data); + void **mUserData; +}; + + + +// static +BOOL LLMessageSystem::mTimeDecodes = FALSE; + +// static, 50ms per message decode +F32 LLMessageSystem::mTimeDecodesSpamThreshold = 0.05f; + +// FIXME: This needs to be moved into a seperate file so that it never gets +// included in the viewer. 30 Sep 2002 mark +// NOTE: I don't think it's important that the messgage system tracks +// this since it must get set externally. 2004.08.25 Phoenix. +static std::string g_shared_secret; +std::string get_shared_secret(); + +class LLMessagePollInfo +{ +public: + apr_socket_t *mAPRSocketp; + apr_pollfd_t mPollFD; +}; + + +// LLMessageVariable functions and friends + +std::ostream& operator<<(std::ostream& s, LLMessageVariable &msg) +{ + s << "\t\t" << msg.mName << " ("; + switch (msg.mType) + { + case MVT_FIXED: + s << "Fixed, " << msg.mSize << " bytes total)\n"; + break; + case MVT_VARIABLE: + s << "Variable, " << msg.mSize << " bytes of size info)\n"; + break; + default: + s << "Unknown\n"; + break; + } + return s; +} + +// LLMessageBlock functions and friends + +std::ostream& operator<<(std::ostream& s, LLMessageBlock &msg) +{ + s << "\t" << msg.mName << " ("; + switch (msg.mType) + { + case MBT_SINGLE: + s << "Fixed"; + break; + case MBT_MULTIPLE: + s << "Multiple - " << msg.mNumber << " copies"; + break; + case MBT_VARIABLE: + s << "Variable"; + break; + default: + s << "Unknown"; + break; + } + if (msg.mTotalSize != -1) + { + s << ", " << msg.mTotalSize << " bytes each, " << msg.mNumber*msg.mTotalSize << " bytes total)\n"; + } + else + { + s << ")\n"; + } + + + for (LLMessageBlock::message_variable_map_t::iterator iter = msg.mMemberVariables.begin(); + iter != msg.mMemberVariables.end(); iter++) + { + LLMessageVariable& ci = *(iter->second); + s << ci; + } + + return s; +} + +// LLMessageTemplate functions and friends + +std::ostream& operator<<(std::ostream& s, LLMessageTemplate &msg) +{ + switch (msg.mFrequency) + { + case MFT_HIGH: + s << "========================================\n" << "Message #" << msg.mMessageNumber << "\n" << msg.mName << " ("; + s << "High"; + break; + case MFT_MEDIUM: + s << "========================================\n" << "Message #"; + s << (msg.mMessageNumber & 0xFF) << "\n" << msg.mName << " ("; + s << "Medium"; + break; + case MFT_LOW: + s << "========================================\n" << "Message #"; + s << (msg.mMessageNumber & 0xFFFF) << "\n" << msg.mName << " ("; + s << "Low"; + break; + default: + s << "Unknown"; + break; + } + + if (msg.mTotalSize != -1) + { + s << ", " << msg.mTotalSize << " bytes total)\n"; + } + else + { + s << ")\n"; + } + + for (LLMessageTemplate::message_block_map_t::iterator iter = msg.mMemberBlocks.begin(); + iter != msg.mMemberBlocks.end(); iter++) + { + LLMessageBlock* ci = iter->second; + s << *ci; + } + + return s; +} + +// LLMessageList functions and friends + +// Lets support a small subset of regular expressions here +// Syntax is a string made up of: +// a - checks against alphanumeric ([A-Za-z0-9]) +// c - checks against character ([A-Za-z]) +// f - checks against first variable character ([A-Za-z_]) +// v - checks against variable ([A-Za-z0-9_]) +// s - checks against sign of integer ([-0-9]) +// d - checks against integer digit ([0-9]) +// * - repeat last check + +// checks 'a' +BOOL b_return_alphanumeric_ok(char c) +{ + if ( ( (c < 'A') + ||(c > 'Z')) + &&( (c < 'a') + ||(c > 'z')) + &&( (c < '0') + ||(c > '9'))) + { + return FALSE; + } + return TRUE; +} + +// checks 'c' +BOOL b_return_character_ok(char c) +{ + if ( ( (c < 'A') + ||(c > 'Z')) + &&( (c < 'a') + ||(c > 'z'))) + { + return FALSE; + } + return TRUE; +} + +// checks 'f' +BOOL b_return_first_variable_ok(char c) +{ + if ( ( (c < 'A') + ||(c > 'Z')) + &&( (c < 'a') + ||(c > 'z')) + &&(c != '_')) + { + return FALSE; + } + return TRUE; +} + +// checks 'v' +BOOL b_return_variable_ok(char c) +{ + if ( ( (c < 'A') + ||(c > 'Z')) + &&( (c < 'a') + ||(c > 'z')) + &&( (c < '0') + ||(c > '9')) + &&(c != '_')) + { + return FALSE; + } + return TRUE; +} + +// checks 's' +BOOL b_return_signed_integer_ok(char c) +{ + if ( ( (c < '0') + ||(c > '9')) + &&(c != '-')) + { + return FALSE; + } + return TRUE; +} + +// checks 'd' +BOOL b_return_integer_ok(char c) +{ + if ( (c < '0') + ||(c > '9')) + { + return FALSE; + } + return TRUE; +} + +BOOL (*gParseCheckCharacters[])(char c) = +{ + b_return_alphanumeric_ok, + b_return_character_ok, + b_return_first_variable_ok, + b_return_variable_ok, + b_return_signed_integer_ok, + b_return_integer_ok +}; + +S32 get_checker_number(char checker) +{ + switch(checker) + { + case 'a': + return 0; + case 'c': + return 1; + case 'f': + return 2; + case 'v': + return 3; + case 's': + return 4; + case 'd': + return 5; + case '*': + return 9999; + default: + return -1; + } +} + +// check token based on passed simplified regular expression +BOOL b_check_token(char *token, char *regexp) +{ + S32 tptr, rptr = 0; + S32 current_checker, next_checker = 0; + + current_checker = get_checker_number(regexp[rptr++]); + + if (current_checker == -1) + { + llerrs << "Invalid regular expression value!" << llendl; + return FALSE; + } + + if (current_checker == 9999) + { + llerrs << "Regular expression can't start with *!" << llendl; + return FALSE; + } + + for (tptr = 0; token[tptr]; tptr++) + { + if (current_checker == -1) + { + llerrs << "Input exceeds regular expression!\nDid you forget a *?" << llendl; + return FALSE; + } + + if (!gParseCheckCharacters[current_checker](token[tptr])) + { + return FALSE; + } + if (next_checker != 9999) + { + next_checker = get_checker_number(regexp[rptr++]); + if (next_checker != 9999) + { + current_checker = next_checker; + } + } + } + return TRUE; +} + +// C variable can be made up of upper or lower case letters, underscores, or numbers, but can't start with a number +BOOL b_variable_ok(char *token) +{ + if (!b_check_token(token, "fv*")) + { + llerrs << "Token '" << token << "' isn't a variable!" << llendl; + return FALSE; + } + return TRUE; +} + +// An integer is made up of the digits 0-9 and may be preceded by a '-' +BOOL b_integer_ok(char *token) +{ + if (!b_check_token(token, "sd*")) + { + llerrs << "Token isn't an integer!" << llendl; + return FALSE; + } + return TRUE; +} + +// An integer is made up of the digits 0-9 +BOOL b_positive_integer_ok(char *token) +{ + if (!b_check_token(token, "d*")) + { + llerrs << "Token isn't an integer!" << llendl; + return FALSE; + } + return TRUE; +} + +void LLMessageSystem::init() +{ + // initialize member variables + mVerboseLog = FALSE; + + mbError = FALSE; + mErrorCode = 0; + mIncomingCompressedSize = 0; + mSendReliable = FALSE; + + mbSBuilt = FALSE; + mbSClear = TRUE; + + mUnackedListDepth = 0; + mUnackedListSize = 0; + mDSMaxListDepth = 0; + + mCurrentRMessageData = NULL; + mCurrentRMessageTemplate = NULL; + + mCurrentSMessageData = NULL; + mCurrentSMessageTemplate = NULL; + mCurrentSMessageName = NULL; + + mCurrentRecvPacketID = 0; + + mNumberHighFreqMessages = 0; + mNumberMediumFreqMessages = 0; + mNumberLowFreqMessages = 0; + mPacketsIn = mPacketsOut = 0; + mBytesIn = mBytesOut = 0; + mCompressedPacketsIn = mCompressedPacketsOut = 0; + mReliablePacketsIn = mReliablePacketsOut = 0; + + mCompressedBytesIn = 0; + mCompressedBytesOut = 0; + mUncompressedBytesIn = 0; + mUncompressedBytesOut = 0; + mTotalBytesIn = 0; + mTotalBytesOut = 0; + + mDroppedPackets = 0; // total dropped packets in + mResentPackets = 0; // total resent packets out + mFailedResendPackets = 0; // total resend failure packets out + mOffCircuitPackets = 0; // total # of off-circuit packets rejected + mInvalidOnCircuitPackets = 0; // total # of on-circuit packets rejected + + mOurCircuitCode = 0; + + mMessageFileChecksum = 0; + mMessageFileVersionNumber = 0.f; +} + +LLMessageSystem::LLMessageSystem() +{ + init(); + + mSystemVersionMajor = 0; + mSystemVersionMinor = 0; + mSystemVersionPatch = 0; + mSystemVersionServer = 0; + mVersionFlags = 0x0; + + // default to not accepting packets from not alive circuits + mbProtected = TRUE; + + mSendPacketFailureCount = 0; + mCircuitPrintFreq = 0.f; // seconds + + // initialize various bits of net info + mSocket = 0; + mPort = 0; + + mPollInfop = NULL; + + mResendDumpTime = 0; + mMessageCountTime = 0; + mCircuitPrintTime = 0; + mCurrentMessageTimeSeconds = 0; + + // Constants for dumping output based on message processing time/count + mNumMessageCounts = 0; + mMaxMessageCounts = 0; // >= 0 means dump warnings + mMaxMessageTime = 0.f; + + mTrueReceiveSize = 0; + + // Error if checking this state, subclass methods which aren't implemented are delegated + // to properly constructed message system. + mbError = TRUE; +} + +// Read file and build message templates +LLMessageSystem::LLMessageSystem(const char *filename, U32 port, + S32 version_major, + S32 version_minor, + S32 version_patch) +{ + init(); + + mSystemVersionMajor = version_major; + mSystemVersionMinor = version_minor; + mSystemVersionPatch = version_patch; + mSystemVersionServer = 0; + mVersionFlags = 0x0; + + // default to not accepting packets from not alive circuits + mbProtected = TRUE; + + mSendPacketFailureCount = 0; + + mCircuitPrintFreq = 60.f; // seconds + + loadTemplateFile(filename); + + // initialize various bits of net info + mSocket = 0; + mPort = port; + + S32 error = start_net(mSocket, mPort); + if (error != 0) + { + mbError = TRUE; + mErrorCode = error; + } + //llinfos << << "*** port: " << mPort << llendl; + + // + // Create the data structure that we can poll on + // + if (!gAPRPoolp) + { + llerrs << "No APR pool before message system initialization!" << llendl; + ll_init_apr(); + } + apr_socket_t *aprSocketp = NULL; + apr_os_sock_put(&aprSocketp, (apr_os_sock_t*)&mSocket, gAPRPoolp); + + mPollInfop = new LLMessagePollInfo; + mPollInfop->mAPRSocketp = aprSocketp; + mPollInfop->mPollFD.p = gAPRPoolp; + mPollInfop->mPollFD.desc_type = APR_POLL_SOCKET; + mPollInfop->mPollFD.reqevents = APR_POLLIN; + mPollInfop->mPollFD.rtnevents = 0; + mPollInfop->mPollFD.desc.s = aprSocketp; + mPollInfop->mPollFD.client_data = NULL; + + F64 mt_sec = getMessageTimeSeconds(); + mResendDumpTime = mt_sec; + mMessageCountTime = mt_sec; + mCircuitPrintTime = mt_sec; + mCurrentMessageTimeSeconds = mt_sec; + + // Constants for dumping output based on message processing time/count + mNumMessageCounts = 0; + mMaxMessageCounts = 200; // >= 0 means dump warnings + mMaxMessageTime = 1.f; + + mTrueReceiveSize = 0; +} + +// Read file and build message templates +void LLMessageSystem::loadTemplateFile(const char* filename) +{ + if(!filename) + { + llerrs << "No template filename specified" << llendl; + } + + char token[MAX_MESSAGE_INTERNAL_NAME_SIZE]; /* Flawfinder: ignore */ + + // state variables + BOOL b_template_start = TRUE; + BOOL b_template_end = FALSE; + BOOL b_template = FALSE; + BOOL b_block_start = FALSE; + BOOL b_block_end = FALSE; + BOOL b_block = FALSE; + BOOL b_variable_start = FALSE; + BOOL b_variable_end = FALSE; + BOOL b_variable = FALSE; + //BOOL b_in_comment_block = FALSE; // not yet used + + // working temp variables + LLMessageTemplate *templatep = NULL; + char template_name[MAX_MESSAGE_INTERNAL_NAME_SIZE]; /* Flawfinder: ignore */ + + LLMessageBlock *blockp = NULL; + char block_name[MAX_MESSAGE_INTERNAL_NAME_SIZE]; /* Flawfinder: ignore */ + + LLMessageVariable var; + char var_name[MAX_MESSAGE_INTERNAL_NAME_SIZE]; /* Flawfinder: ignore */ + char formatString[MAX_MESSAGE_INTERNAL_NAME_SIZE]; + + FILE* messagefilep = NULL; + mMessageFileChecksum = 0; + mMessageFileVersionNumber = 0.f; + S32 checksum_offset = 0; + char* checkp = NULL; + + snprintf(formatString, sizeof(formatString), "%%%ds", MAX_MESSAGE_INTERNAL_NAME_SIZE); + messagefilep = LLFile::fopen(filename, "r"); + if (messagefilep) + { +// mName = gMessageStringTable.getString(filename); + + fseek(messagefilep, 0L, SEEK_SET ); + while(fscanf(messagefilep, formatString, token) != EOF) + { + // skip comments + if (token[0] == '/') + { + // skip to end of line + while (token[0] != 10) + fscanf(messagefilep, "%c", token); + continue; + } + + checkp = token; + + while (*checkp) + { + mMessageFileChecksum += ((U32)*checkp++) << checksum_offset; + checksum_offset = (checksum_offset + 8) % 32; + } + + // what are we looking for + if (!strcmp(token, "{")) + { + // is that a legit option? + if (b_template_start) + { + // yup! + b_template_start = FALSE; + + // remember that it could be only a signal message, so name is all that it contains + b_template_end = TRUE; + + // start working on it! + b_template = TRUE; + } + else if (b_block_start) + { + // yup! + b_block_start = FALSE; + b_template_end = FALSE; + + // start working on it! + b_block = TRUE; + } + else if (b_variable_start) + { + // yup! + b_variable_start = FALSE; + b_block_end = FALSE; + + // start working on it! + b_variable = TRUE; + } + else + { + llerrs << "Detcted unexpected token '" << token + << "' while parsing template." << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + } + + if (!strcmp(token, "}")) + { + // is that a legit option? + if (b_template_end) + { + // yup! + b_template_end = FALSE; + b_template = FALSE; + b_block_start = FALSE; + + // add data! + // we've gotten a complete variable! hooray! + // add it! + addTemplate(templatep); + + //llinfos << "Read template: "templatep->mNametemp_str + // << llendl; + + // look for next one! + b_template_start = TRUE; + } + else if (b_block_end) + { + // yup! + b_block_end = FALSE; + b_variable_start = FALSE; + + // add data! + // we've gotten a complete variable! hooray! + // add it to template + + templatep->addBlock(blockp); + + // start working on it! + b_template_end = TRUE; + b_block_start = TRUE; + } + else if (b_variable_end) + { + // yup! + b_variable_end = FALSE; + + // add data! + // we've gotten a complete variable! hooray! + // add it to block + blockp->addVariable(var.getName(), var.getType(), var.getSize()); + + // start working on it! + b_variable_start = TRUE; + b_block_end = TRUE; + } + else + { + llerrs << "Detcted unexpected token '" << token + << "' while parsing template." << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + } + + // now, are we looking to start a template? + if (b_template) + { + + b_template = FALSE; + + // name first + if (fscanf(messagefilep, formatString, template_name) == EOF) + { + // oops, file ended + llerrs << "Expected message template name, but file ended" + << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + // debugging to help figure out busted templates + //llinfos << template_name << llendl; + + // is name a legit C variable name + if (!b_variable_ok(template_name)) + { + // nope! + llerrs << "Not legal message template name: " + << template_name << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + checkp = template_name; + while (*checkp) + { + mMessageFileChecksum += ((U32)*checkp++) << checksum_offset; + checksum_offset = (checksum_offset + 8) % 32; + } + + // ok, now get Frequency ("High", "Medium", or "Low") + if (fscanf(messagefilep, formatString, token) == EOF) + { + // oops, file ended + llerrs << "Expected message template frequency, found EOF." + << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + checkp = token; + while (*checkp) + { + mMessageFileChecksum += ((U32)*checkp++) << checksum_offset; + checksum_offset = (checksum_offset + 8) % 32; + } + + // which one is it? + if (!strcmp(token, "High")) + { + if (++mNumberHighFreqMessages == 255) + { + // oops, too many High Frequency messages!! + llerrs << "Message " << template_name + << " exceeded 254 High frequency messages!" + << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + // ok, we can create a template! + // message number is just mNumberHighFreqMessages + templatep = new LLMessageTemplate(template_name, mNumberHighFreqMessages, MFT_HIGH); + //lldebugs << "Template " << template_name << " # " + // << std::hex << mNumberHighFreqMessages + // << std::dec << " high" + // << llendl; + } + else if (!strcmp(token, "Medium")) + { + if (++mNumberMediumFreqMessages == 255) + { + // oops, too many Medium Frequency messages!! + llerrs << "Message " << template_name + << " exceeded 254 Medium frequency messages!" + << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + // ok, we can create a template! + // message number is ((255 << 8) | mNumberMediumFreqMessages) + templatep = new LLMessageTemplate(template_name, (255 << 8) | mNumberMediumFreqMessages, MFT_MEDIUM); + //lldebugs << "Template " << template_name << " # " + // << std::hex << mNumberMediumFreqMessages + // << std::dec << " medium" + // << llendl; + } + else if (!strcmp(token, "Low")) + { + if (++mNumberLowFreqMessages == 65535) + { + // oops, too many High Frequency messages!! + llerrs << "Message " << template_name + << " exceeded 65534 Low frequency messages!" + << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + // ok, we can create a template! + // message number is ((255 << 24) | (255 << 16) | mNumberLowFreqMessages) + templatep = new LLMessageTemplate(template_name, (255 << 24) | (255 << 16) | mNumberLowFreqMessages, MFT_LOW); + //lldebugs << "Template " << template_name << " # " + // << std::hex << mNumberLowFreqMessages + // << std::dec << " low" + // << llendl; + } + else if (!strcmp(token, "Fixed")) + { + U32 message_num = 0; + if (fscanf(messagefilep, formatString, token) == EOF) + { + // oops, file ended + llerrs << "Expected message template number (fixed)," + << " found EOF." << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + checkp = token; + while (*checkp) + { + mMessageFileChecksum += ((U32)*checkp++) << checksum_offset; + checksum_offset = (checksum_offset + 8) % 32; + } + + message_num = strtoul(token,NULL,0); + + // ok, we can create a template! + // message number is ((255 << 24) | (255 << 16) | mNumberLowFreqMessages) + templatep = new LLMessageTemplate(template_name, message_num, MFT_LOW); + } + else + { + // oops, bad frequency line + llerrs << "Bad frequency! " << token + << " isn't High, Medium, or Low" << llendl + mbError = TRUE; + fclose(messagefilep); + return; + } + + // Now get trust ("Trusted", "NotTrusted") + if (fscanf(messagefilep, formatString, token) == EOF) + { + // File ended + llerrs << "Expected message template " + "trust, but file ended." + << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + checkp = token; + while (*checkp) + { + mMessageFileChecksum += ((U32) *checkp++) << checksum_offset; + checksum_offset = (checksum_offset + 8) % 32; + } + + if (strcmp(token, "Trusted") == 0) + { + templatep->setTrust(MT_TRUST); + } + else if (strcmp(token, "NotTrusted") == 0) + { + templatep->setTrust(MT_NOTRUST); + } + else + { + // bad trust token + llerrs << "bad trust: " << token + << " isn't Trusted or NotTrusted" + << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + // get encoding + if (fscanf(messagefilep, formatString, token) == EOF) + { + // File ended + llerrs << "Expected message encoding, but file ended." + << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + checkp = token; + while(*checkp) + { + mMessageFileChecksum += ((U32) *checkp++) << checksum_offset; + checksum_offset = (checksum_offset + 8) % 32; + } + + if(0 == strcmp(token, "Unencoded")) + { + templatep->setEncoding(ME_UNENCODED); + } + else if(0 == strcmp(token, "Zerocoded")) + { + templatep->setEncoding(ME_ZEROCODED); + } + else + { + // bad trust token + llerrs << "bad encoding: " << token + << " isn't Unencoded or Zerocoded" << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + // ok, now we need to look for a block + b_block_start = TRUE; + continue; + } + + // now, are we looking to start a template? + if (b_block) + { + b_block = FALSE; + // ok, need to pull header info + + // name first + if (fscanf(messagefilep, formatString, block_name) == EOF) + { + // oops, file ended + llerrs << "Expected block name, but file ended" << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + checkp = block_name; + while (*checkp) + { + mMessageFileChecksum += ((U32)*checkp++) << checksum_offset; + checksum_offset = (checksum_offset + 8) % 32; + } + + // is name a legit C variable name + if (!b_variable_ok(block_name)) + { + // nope! + llerrs << block_name << "is not a legal block name" + << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + // now, block type ("Single", "Multiple", or "Variable") + if (fscanf(messagefilep, formatString, token) == EOF) + { + // oops, file ended + llerrs << "Expected block type, but file ended." << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + checkp = token; + while (*checkp) + { + mMessageFileChecksum += ((U32)*checkp++) << checksum_offset; + checksum_offset = (checksum_offset + 8) % 32; + } + + // which one is it? + if (!strcmp(token, "Single")) + { + // ok, we can create a block + blockp = new LLMessageBlock(block_name, MBT_SINGLE); + } + else if (!strcmp(token, "Multiple")) + { + // need to get the number of repeats + if (fscanf(messagefilep, formatString, token) == EOF) + { + // oops, file ended + llerrs << "Expected block multiple count," + " but file ended." << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + checkp = token; + while (*checkp) + { + mMessageFileChecksum += ((U32)*checkp++) << checksum_offset; + checksum_offset = (checksum_offset + 8) % 32; + } + + // is it a legal integer + if (!b_positive_integer_ok(token)) + { + // nope! + llerrs << token << "is not a legal integer for" + " block multiple count" << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + // ok, we can create a block + blockp = new LLMessageBlock(block_name, MBT_MULTIPLE, atoi(token)); + } + else if (!strcmp(token, "Variable")) + { + // ok, we can create a block + blockp = new LLMessageBlock(block_name, MBT_VARIABLE); + } + else + { + // oops, bad block type + llerrs << "Bad block type! " << token + << " isn't Single, Multiple, or Variable" << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + // ok, now we need to look for a variable + b_variable_start = TRUE; + continue; + } + + // now, are we looking to start a template? + if (b_variable) + { + b_variable = FALSE; + // ok, need to pull header info + + // name first + if (fscanf(messagefilep, formatString, var_name) == EOF) + { + // oops, file ended + llerrs << "Expected variable name, but file ended." + << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + checkp = var_name; + while (*checkp) + { + mMessageFileChecksum += ((U32)*checkp++) << checksum_offset; + checksum_offset = (checksum_offset + 8) % 32; + } + + // is name a legit C variable name + if (!b_variable_ok(var_name)) + { + // nope! + llerrs << var_name << " is not a legal variable name" + << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + // now, variable type ("Fixed" or "Variable") + if (fscanf(messagefilep, formatString, token) == EOF) + { + // oops, file ended + llerrs << "Expected variable type, but file ended" + << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + checkp = token; + while (*checkp) + { + mMessageFileChecksum += ((U32)*checkp++) << checksum_offset; + checksum_offset = (checksum_offset + 8) % 32; + } + + + // which one is it? + if (!strcmp(token, "U8")) + { + var = LLMessageVariable(var_name, MVT_U8, 1); + } + else if (!strcmp(token, "U16")) + { + var = LLMessageVariable(var_name, MVT_U16, 2); + } + else if (!strcmp(token, "U32")) + { + var = LLMessageVariable(var_name, MVT_U32, 4); + } + else if (!strcmp(token, "U64")) + { + var = LLMessageVariable(var_name, MVT_U64, 8); + } + else if (!strcmp(token, "S8")) + { + var = LLMessageVariable(var_name, MVT_S8, 1); + } + else if (!strcmp(token, "S16")) + { + var = LLMessageVariable(var_name, MVT_S16, 2); + } + else if (!strcmp(token, "S32")) + { + var = LLMessageVariable(var_name, MVT_S32, 4); + } + else if (!strcmp(token, "S64")) + { + var = LLMessageVariable(var_name, MVT_S64, 8); + } + else if (!strcmp(token, "F32")) + { + var = LLMessageVariable(var_name, MVT_F32, 4); + } + else if (!strcmp(token, "F64")) + { + var = LLMessageVariable(var_name, MVT_F64, 8); + } + else if (!strcmp(token, "LLVector3")) + { + var = LLMessageVariable(var_name, MVT_LLVector3, 12); + } + else if (!strcmp(token, "LLVector3d")) + { + var = LLMessageVariable(var_name, MVT_LLVector3d, 24); + } + else if (!strcmp(token, "LLVector4")) + { + var = LLMessageVariable(var_name, MVT_LLVector4, 16); + } + else if (!strcmp(token, "LLQuaternion")) + { + var = LLMessageVariable(var_name, MVT_LLQuaternion, 12); + } + else if (!strcmp(token, "LLUUID")) + { + var = LLMessageVariable(var_name, MVT_LLUUID, 16); + } + else if (!strcmp(token, "BOOL")) + { + var = LLMessageVariable(var_name, MVT_BOOL, 1); + } + else if (!strcmp(token, "IPADDR")) + { + var = LLMessageVariable(var_name, MVT_IP_ADDR, 4); + } + else if (!strcmp(token, "IPPORT")) + { + var = LLMessageVariable(var_name, MVT_IP_PORT, 2); + } + else if (!strcmp(token, "Fixed")) + { + // need to get the variable size + if (fscanf(messagefilep, formatString, token) == EOF) + { + // oops, file ended + llerrs << "Expected variable size, but file ended" + << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + checkp = token; + while (*checkp) + { + mMessageFileChecksum += ((U32)*checkp++) << checksum_offset; + checksum_offset = (checksum_offset + 8) % 32; + } + + // is it a legal integer + if (!b_positive_integer_ok(token)) + { + // nope! + llerrs << token << " is not a legal integer for" + " variable size" << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + // ok, we can create a block + var = LLMessageVariable(var_name, MVT_FIXED, atoi(token)); + } + else if (!strcmp(token, "Variable")) + { + // need to get the variable size + if (fscanf(messagefilep, formatString, token) == EOF) + { + // oops, file ended + llerrs << "Expected variable size, but file ended" + << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + checkp = token; + while (*checkp) + { + mMessageFileChecksum += ((U32)*checkp++) << checksum_offset; + checksum_offset = (checksum_offset + 8) % 32; + } + + // is it a legal integer + if (!b_positive_integer_ok(token)) + { + // nope! + llerrs << token << "is not a legal integer" + " for variable size" << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + // ok, we can create a block + var = LLMessageVariable(var_name, MVT_VARIABLE, atoi(token)); + } + else + { + // oops, bad variable type + llerrs << "Bad variable type! " << token + << " isn't Fixed or Variable" << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + // we got us a variable! + b_variable_end = TRUE; + continue; + } + + // do we have a version number stuck in the file? + if (!strcmp(token, "version")) + { + // version number + if (fscanf(messagefilep, formatString, token) == EOF) + { + // oops, file ended + llerrs << "Expected version number, but file ended" + << llendl; + mbError = TRUE; + fclose(messagefilep); + return; + } + + checkp = token; + while (*checkp) + { + mMessageFileChecksum += ((U32)*checkp++) << checksum_offset; + checksum_offset = (checksum_offset + 8) % 32; + } + + mMessageFileVersionNumber = (F32)atof(token); + +// llinfos << "### Message template version " << mMessageFileVersionNumber << " ###" << llendl; + continue; + } + } + + llinfos << "Message template checksum = " << std::hex << mMessageFileChecksum << std::dec << llendl; + } + else + { + llwarns << "Failed to open template: " << filename << llendl; + mbError = TRUE; + return; + } + fclose(messagefilep); +} + + +LLMessageSystem::~LLMessageSystem() +{ + mMessageTemplates.clear(); // don't delete templates. + for_each(mMessageNumbers.begin(), mMessageNumbers.end(), DeletePairedPointer()); + mMessageNumbers.clear(); + + if (!mbError) + { + end_net(); + } + + delete mCurrentRMessageData; + mCurrentRMessageData = NULL; + + delete mCurrentSMessageData; + mCurrentSMessageData = NULL; + + delete mPollInfop; + mPollInfop = NULL; +} + +void LLMessageSystem::clearReceiveState() +{ + mReceiveSize = -1; + mCurrentRecvPacketID = 0; + mCurrentRMessageTemplate = NULL; + delete mCurrentRMessageData; + mCurrentRMessageData = NULL; + mIncomingCompressedSize = 0; + mLastSender.invalidate(); +} + + +BOOL LLMessageSystem::poll(F32 seconds) +{ + S32 num_socks; + apr_status_t status; + status = apr_poll(&(mPollInfop->mPollFD), 1, &num_socks,(U64)(seconds*1000000.f)); + if (status != APR_TIMEUP) + { + ll_apr_warn_status(status); + } + if (num_socks) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +// Returns TRUE if a valid, on-circuit message has been received. +BOOL LLMessageSystem::checkMessages( S64 frame_count ) +{ + BOOL valid_packet = FALSE; + + LLTransferTargetVFile::updateQueue(); + + if (!mNumMessageCounts) + { + // This is the first message being handled after a resetReceiveCounts, we must be starting + // the message processing loop. Reset the timers. + mCurrentMessageTimeSeconds = totalTime() * SEC_PER_USEC; + mMessageCountTime = getMessageTimeSeconds(); + } + + // loop until either no packets or a valid packet + // i.e., burn through packets from unregistered circuits + do + { + clearReceiveState(); + + BOOL recv_reliable = FALSE; + BOOL recv_resent = FALSE; + S32 acks = 0; + S32 true_rcv_size = 0; + + U8* buffer = mTrueReceiveBuffer; + + mTrueReceiveSize = mPacketRing.receivePacket(mSocket, (char *)mTrueReceiveBuffer); + // If you want to dump all received packets into SecondLife.log, uncomment this + //dumpPacketToLog(); + + mReceiveSize = mTrueReceiveSize; + mLastSender = mPacketRing.getLastSender(); + + if (mReceiveSize < (S32) LL_MINIMUM_VALID_PACKET_SIZE) + { + // A receive size of zero is OK, that means that there are no more packets available. + // Ones that are non-zero but below the minimum packet size are worrisome. + if (mReceiveSize > 0) + { + llwarns << "Invalid (too short) packet discarded " << mReceiveSize << llendl; + callExceptionFunc(MX_PACKET_TOO_SHORT); + } + // no data in packet receive buffer + valid_packet = FALSE; + } + else + { + LLHost host; + LLCircuitData *cdp; + + // note if packet acks are appended. + if(buffer[0] & LL_ACK_FLAG) + { + acks += buffer[--mReceiveSize]; + true_rcv_size = mReceiveSize; + mReceiveSize -= acks * sizeof(TPACKETID); + } + + // process the message as normal + + mIncomingCompressedSize = zeroCodeExpand(&buffer,&mReceiveSize); + mCurrentRecvPacketID = buffer[1] + ((buffer[0] & 0x0f ) * 256); + if (sizeof(TPACKETID) == 4) + { + mCurrentRecvPacketID *= 256; + mCurrentRecvPacketID += buffer[2]; + mCurrentRecvPacketID *= 256; + mCurrentRecvPacketID += buffer[3]; + } + + host = getSender(); + //llinfos << host << ":" << mCurrentRecvPacketID << llendl; + + // For testing the weird case we're having in the office where the first few packets + // on a connection get dropped + //if ((mCurrentRecvPacketID < 8) && !(buffer[0] & LL_RESENT_FLAG)) + //{ + // llinfos << "Evil! Dropping " << mCurrentRecvPacketID << " from " << host << " for fun!" << llendl; + // continue; + //} + + cdp = mCircuitInfo.findCircuit(host); + if (!cdp) + { + // This packet comes from a circuit we don't know about. + + // Are we rejecting off-circuit packets? + if (mbProtected) + { + // cdp is already NULL, so we don't need to unset it. + } + else + { + // nope, open the new circuit + cdp = mCircuitInfo.addCircuitData(host, mCurrentRecvPacketID); + + // I added this - I think it's correct - DJS + // reset packet in ID + cdp->setPacketInID(mCurrentRecvPacketID); + + // And claim the packet is on the circuit we just added. + } + } + else + { + // this is an old circuit. . . is it still alive? + if (!cdp->isAlive()) + { + // nope. don't accept if we're protected + if (mbProtected) + { + // don't accept packets from unexpected sources + cdp = NULL; + } + else + { + // wake up the circuit + cdp->setAlive(TRUE); + + // reset packet in ID + cdp->setPacketInID(mCurrentRecvPacketID); + } + } + } + + // At this point, cdp is now a pointer to the circuit that + // this message came in on if it's valid, and NULL if the + // circuit was bogus. + + if(cdp && (acks > 0) && ((S32)(acks * sizeof(TPACKETID)) < (true_rcv_size))) + { + TPACKETID packet_id; + U32 mem_id=0; + for(S32 i = 0; i < acks; ++i) + { + true_rcv_size -= sizeof(TPACKETID); + memcpy(&mem_id, &mTrueReceiveBuffer[true_rcv_size], /* Flawfinder: ignore*/ + sizeof(TPACKETID)); + packet_id = ntohl(mem_id); + //llinfos << "got ack: " << packet_id << llendl; + cdp->ackReliablePacket(packet_id); + } + if (!cdp->getUnackedPacketCount()) + { + // Remove this circuit from the list of circuits with unacked packets + mCircuitInfo.mUnackedCircuitMap.erase(cdp->mHost); + } + } + + if (buffer[0] & LL_RELIABLE_FLAG) + { + recv_reliable = TRUE; + } + if (buffer[0] & LL_RESENT_FLAG) + { + recv_resent = TRUE; + if (cdp && cdp->isDuplicateResend(mCurrentRecvPacketID)) + { + // We need to ACK here to suppress + // further resends of packets we've + // already seen. + if (recv_reliable) + { + //mAckList.addData(new LLPacketAck(host, mCurrentRecvPacketID)); + // *************************************** + // TESTING CODE + //if(mCircuitInfo.mCurrentCircuit->mHost != host) + //{ + // llwarns << "DISCARDED PACKET HOST MISMATCH! HOST: " + // << host << " CIRCUIT: " + // << mCircuitInfo.mCurrentCircuit->mHost + // << llendl; + //} + // *************************************** + //mCircuitInfo.mCurrentCircuit->mAcks.put(mCurrentRecvPacketID); + cdp->collectRAck(mCurrentRecvPacketID); + } + + //llinfos << "Discarding duplicate resend from " << host << llendl; + if(mVerboseLog) + { + std::ostringstream str; + str << "MSG: <- " << host; + char buffer[MAX_STRING]; /* Flawfinder: ignore*/ + snprintf(buffer, MAX_STRING, "\t%6d\t%6d\t%6d ", mReceiveSize, (mIncomingCompressedSize ? mIncomingCompressedSize : mReceiveSize), mCurrentRecvPacketID);/* Flawfinder: ignore*/ + str << buffer << "(unknown)" + << (recv_reliable ? " reliable" : "") + << " resent " + << ((acks > 0) ? "acks" : "") + << " DISCARD DUPLICATE"; + llinfos << str.str() << llendl; + } + mPacketsIn++; + valid_packet = FALSE; + continue; + } + } + + // UseCircuitCode can be a valid, off-circuit packet. + // But we don't want to acknowledge UseCircuitCode until the circuit is + // available, which is why the acknowledgement test is done above. JC + + valid_packet = decodeTemplate( buffer, mReceiveSize, &mCurrentRMessageTemplate ); + if( valid_packet ) + { + mCurrentRMessageTemplate->mReceiveCount++; + lldebugst(LLERR_MESSAGE) << "MessageRecvd:" << mCurrentRMessageTemplate->mName << " from " << host << llendl; + } + + // UseCircuitCode is allowed in even from an invalid circuit, so that + // we can toss circuits around. + if (valid_packet && !cdp && (mCurrentRMessageTemplate->mName != _PREHASH_UseCircuitCode) ) + { + logMsgFromInvalidCircuit( host, recv_reliable ); + clearReceiveState(); + valid_packet = FALSE; + } + + if (valid_packet && cdp && !cdp->getTrusted() && (mCurrentRMessageTemplate->getTrust() == MT_TRUST) ) + { + logTrustedMsgFromUntrustedCircuit( host ); + clearReceiveState(); + + sendDenyTrustedCircuit(host); + valid_packet = FALSE; + } + + if (valid_packet + && mCurrentRMessageTemplate->isBanned(cdp && cdp->getTrusted())) + { + llwarns << "LLMessageSystem::checkMessages " + << "received banned message " + << mCurrentRMessageTemplate->mName + << " from " + << ((cdp && cdp->getTrusted()) ? "trusted " : "untrusted ") + << host << llendl; + clearReceiveState(); + valid_packet = FALSE; + } + + if( valid_packet ) + { + logValidMsg(cdp, host, recv_reliable, recv_resent, (BOOL)(acks>0) ); + + valid_packet = decodeData( buffer, host ); + } + + // It's possible that the circuit went away, because ANY message can disable the circuit + // (for example, UseCircuit, CloseCircuit, DisableSimulator). Find it again. + cdp = mCircuitInfo.findCircuit(host); + + if (valid_packet) + { + /* Code for dumping the complete contents of a message. Keep for future use in optimizing messages. + if( 1 ) + { + static char* object_update = gMessageStringTable.getString("ObjectUpdate"); + if(object_update == mCurrentRMessageTemplate->mName ) + { + llinfos << "ObjectUpdate:" << llendl; + U32 i; + llinfos << " Zero Encoded: " << zero_unexpanded_size << llendl; + for( i = 0; imMemberBlocks.begin(), + end = mCurrentRMessageTemplate->mMemberBlocks.end(); + iter != end; iter++) + { + LLMessageBlock* block = iter->second; + const char* block_name = block->mName; + for (LLMsgBlkData::msg_var_data_map_t::iterator + iter = block->mMemberVariables.begin(), + end = block->mMemberVariables.end(); + iter != end; iter++) + { + const char* var_name = iter->first; + + if( getNumberOfBlocksFast( block_name ) < 1 ) + { + llinfos << var_name << " has no blocks" << llendl; + } + for( S32 blocknum = 0; blocknum < getNumberOfBlocksFast( block_name ); blocknum++ ) + { + char *bnamep = (char *)block_name + blocknum; // this works because it's just a hash. The bnamep is never derefference + char *vnamep = (char *)var_name; + + LLMsgBlkData *msg_block_data = mCurrentRMessageData->mMemberBlocks[bnamep]; + + char errmsg[1024]; + if (!msg_block_data) + { + sprintf(errmsg, "Block %s #%d not in message %s", block_name, blocknum, mCurrentRMessageData->mName); + llerrs << errmsg << llendl; + } + + LLMsgVarData vardata = msg_block_data->mMemberVarData[vnamep]; + + if (!vardata.getName()) + { + sprintf(errmsg, "Variable %s not in message %s block %s", vnamep, mCurrentRMessageData->mName, bnamep); + llerrs << errmsg << llendl; + } + + const S32 vardata_size = vardata.getSize(); + if( vardata_size ) + { + for( i = 0; i < vardata_size; i++ ) + { + byte_count++; + llinfos << block_name << " " << var_name << " [" << blocknum << "][" << i << "]= " << (U32)(((U8*)vardata.getData())[i]) << llendl; + } + } + else + { + llinfos << block_name << " " << var_name << " [" << blocknum << "] 0 bytes" << llendl; + } + } + } + } + llinfos << "Byte count =" << byte_count << llendl; + } + } + */ + + mPacketsIn++; + mBytesIn += mTrueReceiveSize; + + // ACK here for valid packets that we've seen + // for the first time. + if (cdp && recv_reliable) + { + // Add to the recently received list for duplicate suppression + cdp->mRecentlyReceivedReliablePackets[mCurrentRecvPacketID] = getMessageTimeUsecs(); + + // Put it onto the list of packets to be acked + cdp->collectRAck(mCurrentRecvPacketID); + mReliablePacketsIn++; + } + } + else + { + if (mbProtected && (!cdp)) + { + llwarns << "Packet " + << (mCurrentRMessageTemplate ? mCurrentRMessageTemplate->mName : "") + << " from invalid circuit " << host << llendl; + mOffCircuitPackets++; + } + else + { + mInvalidOnCircuitPackets++; + } + } + + // Code for dumping the complete contents of a message + // delete [] zero_unexpanded_buffer; + } + } while (!valid_packet && mReceiveSize > 0); + + F64 mt_sec = getMessageTimeSeconds(); + // Check to see if we need to print debug info + if ((mt_sec - mCircuitPrintTime) > mCircuitPrintFreq) + { + dumpCircuitInfo(); + mCircuitPrintTime = mt_sec; + } + + if( !valid_packet ) + { + clearReceiveState(); + } + + return valid_packet; +} + +S32 LLMessageSystem::getReceiveBytes() const +{ + if (getReceiveCompressedSize()) + { + return getReceiveCompressedSize() * 8; + } + else + { + return getReceiveSize() * 8; + } +} + + +void LLMessageSystem::processAcks() +{ + F64 mt_sec = getMessageTimeSeconds(); + { + gTransferManager.updateTransfers(); + + if (gXferManager) + { + gXferManager->retransmitUnackedPackets(); + } + + if (gAssetStorage) + { + gAssetStorage->checkForTimeouts(); + } + } + + BOOL dump = FALSE; + { + // Check the status of circuits + mCircuitInfo.updateWatchDogTimers(this); + + //resend any necessary packets + mCircuitInfo.resendUnackedPackets(mUnackedListDepth, mUnackedListSize); + + //cycle through ack list for each host we need to send acks to + mCircuitInfo.sendAcks(); + + if (!mDenyTrustedCircuitSet.empty()) + { + llinfos << "Sending queued DenyTrustedCircuit messages." << llendl; + for (host_set_t::iterator hostit = mDenyTrustedCircuitSet.begin(); hostit != mDenyTrustedCircuitSet.end(); ++hostit) + { + reallySendDenyTrustedCircuit(*hostit); + } + mDenyTrustedCircuitSet.clear(); + } + + if (mMaxMessageCounts >= 0) + { + if (mNumMessageCounts >= mMaxMessageCounts) + { + dump = TRUE; + } + } + + if (mMaxMessageTime >= 0.f) + { + // This is one of the only places where we're required to get REAL message system time. + mReceiveTime = (F32)(getMessageTimeSeconds(TRUE) - mMessageCountTime); + if (mReceiveTime > mMaxMessageTime) + { + dump = TRUE; + } + } + } + + if (dump) + { + dumpReceiveCounts(); + } + resetReceiveCounts(); + + if ((mt_sec - mResendDumpTime) > CIRCUIT_DUMP_TIMEOUT) + { + mResendDumpTime = mt_sec; + mCircuitInfo.dumpResends(); + } +} + + +void LLMessageSystem::newMessageFast(const char *name) +{ + mbSBuilt = FALSE; + mbSClear = FALSE; + + mCurrentSendTotal = 0; + mSendReliable = FALSE; + + char *namep = (char *)name; + + if (mMessageTemplates.count(namep) > 0) + { + mCurrentSMessageTemplate = mMessageTemplates[namep]; + if (mCurrentSMessageData) + { + delete mCurrentSMessageData; + } + mCurrentSMessageData = new LLMsgData(namep); + mCurrentSMessageName = namep; + mCurrentSDataBlock = NULL; + mCurrentSBlockName = NULL; + + // add at one of each block + LLMessageTemplate* msg_template = mMessageTemplates[namep]; + for (LLMessageTemplate::message_block_map_t::iterator iter = msg_template->mMemberBlocks.begin(); + iter != msg_template->mMemberBlocks.end(); iter++) + { + LLMessageBlock* ci = iter->second; + LLMsgBlkData *tblockp; + tblockp = new LLMsgBlkData(ci->mName, 0); + mCurrentSMessageData->addBlock(tblockp); + } + } + else + { + llerrs << "newMessage - Message " << name << " not registered" << llendl; + } +} + +void LLMessageSystem::copyMessageRtoS() +{ + if (!mCurrentRMessageTemplate) + { + return; + } + newMessageFast(mCurrentRMessageTemplate->mName); + + // copy the blocks + // counting variables used to encode multiple block info + S32 block_count = 0; + char *block_name = NULL; + + // loop through msg blocks to loop through variables, totalling up size data and filling the new (send) message + LLMsgData::msg_blk_data_map_t::iterator iter = mCurrentRMessageData->mMemberBlocks.begin(); + LLMsgData::msg_blk_data_map_t::iterator end = mCurrentRMessageData->mMemberBlocks.end(); + for(; iter != end; ++iter) + { + LLMsgBlkData* mbci = iter->second; + if(!mbci) continue; + + // do we need to encode a block code? + if (block_count == 0) + { + block_count = mbci->mBlockNumber; + block_name = (char *)mbci->mName; + } + + // counting down mutliple blocks + block_count--; + + nextBlockFast(block_name); + + // now loop through the variables + LLMsgBlkData::msg_var_data_map_t::iterator dit = mbci->mMemberVarData.begin(); + LLMsgBlkData::msg_var_data_map_t::iterator dend = mbci->mMemberVarData.end(); + + for(; dit != dend; ++dit) + { + LLMsgVarData& mvci = *dit; + addDataFast(mvci.getName(), mvci.getData(), mvci.getType(), mvci.getSize()); + } + } +} + +void LLMessageSystem::clearMessage() +{ + mbSBuilt = FALSE; + mbSClear = TRUE; + + mCurrentSendTotal = 0; + mSendReliable = FALSE; + + mCurrentSMessageTemplate = NULL; + + delete mCurrentSMessageData; + mCurrentSMessageData = NULL; + + mCurrentSMessageName = NULL; + mCurrentSDataBlock = NULL; + mCurrentSBlockName = NULL; +} + + +// set block to add data to within current message +void LLMessageSystem::nextBlockFast(const char *blockname) +{ + char *bnamep = (char *)blockname; + + if (!mCurrentSMessageTemplate) + { + llerrs << "newMessage not called prior to setBlock" << llendl; + return; + } + + // now, does this block exist? + LLMessageTemplate::message_block_map_t::iterator temp_iter = mCurrentSMessageTemplate->mMemberBlocks.find(bnamep); + if (temp_iter == mCurrentSMessageTemplate->mMemberBlocks.end()) + { + llerrs << "LLMessageSystem::nextBlockFast " << bnamep + << " not a block in " << mCurrentSMessageTemplate->mName << llendl; + return; + } + + LLMessageBlock* template_data = temp_iter->second; + + // ok, have we already set this block? + LLMsgBlkData* block_data = mCurrentSMessageData->mMemberBlocks[bnamep]; + if (block_data->mBlockNumber == 0) + { + // nope! set this as the current block + block_data->mBlockNumber = 1; + mCurrentSDataBlock = block_data; + mCurrentSBlockName = bnamep; + + // add placeholders for each of the variables + for (LLMessageBlock::message_variable_map_t::iterator iter = template_data->mMemberVariables.begin(); + iter != template_data->mMemberVariables.end(); iter++) + { + LLMessageVariable& ci = *(iter->second); + mCurrentSDataBlock->addVariable(ci.getName(), ci.getType()); + } + return; + } + else + { + // already have this block. . . + // are we supposed to have a new one? + + // if the block is type MBT_SINGLE this is bad! + if (template_data->mType == MBT_SINGLE) + { + llerrs << "LLMessageSystem::nextBlockFast called multiple times" + << " for " << bnamep << " but is type MBT_SINGLE" << llendl; + return; + } + + + // if the block is type MBT_MULTIPLE then we need a known number, make sure that we're not exceeding it + if ( (template_data->mType == MBT_MULTIPLE) + &&(mCurrentSDataBlock->mBlockNumber == template_data->mNumber)) + { + llerrs << "LLMessageSystem::nextBlockFast called " + << mCurrentSDataBlock->mBlockNumber << " times for " << bnamep + << " exceeding " << template_data->mNumber + << " specified in type MBT_MULTIPLE." << llendl; + return; + } + + // ok, we can make a new one + // modify the name to avoid name collision by adding number to end + S32 count = block_data->mBlockNumber; + + // incrememt base name's count + block_data->mBlockNumber++; + + if (block_data->mBlockNumber > MAX_BLOCKS) + { + llerrs << "Trying to pack too many blocks into MBT_VARIABLE type (limited to " << MAX_BLOCKS << ")" << llendl; + } + + // create new name + // Nota Bene: if things are working correctly, mCurrentMessageData->mMemberBlocks[blockname]->mBlockNumber == mCurrentDataBlock->mBlockNumber + 1 + + char *nbnamep = bnamep + count; + + mCurrentSDataBlock = new LLMsgBlkData(bnamep, count); + mCurrentSDataBlock->mName = nbnamep; + mCurrentSMessageData->mMemberBlocks[nbnamep] = mCurrentSDataBlock; + + // add placeholders for each of the variables + for (LLMessageBlock::message_variable_map_t::iterator + iter = template_data->mMemberVariables.begin(), + end = template_data->mMemberVariables.end(); + iter != end; iter++) + { + LLMessageVariable& ci = *(iter->second); + mCurrentSDataBlock->addVariable(ci.getName(), ci.getType()); + } + return; + } +} + +// add data to variable in current block +void LLMessageSystem::addDataFast(const char *varname, const void *data, EMsgVariableType type, S32 size) +{ + char *vnamep = (char *)varname; + + // do we have a current message? + if (!mCurrentSMessageTemplate) + { + llerrs << "newMessage not called prior to addData" << llendl; + return; + } + + // do we have a current block? + if (!mCurrentSDataBlock) + { + llerrs << "setBlock not called prior to addData" << llendl; + return; + } + + // kewl, add the data if it exists + LLMessageVariable* var_data = mCurrentSMessageTemplate->mMemberBlocks[mCurrentSBlockName]->mMemberVariables[vnamep]; + if (!var_data || !var_data->getName()) + { + llerrs << vnamep << " not a variable in block " << mCurrentSBlockName << " of " << mCurrentSMessageTemplate->mName << llendl; + return; + } + + // ok, it seems ok. . . are we the correct size? + if (var_data->getType() == MVT_VARIABLE) + { + // Variable 1 can only store 255 bytes, make sure our data is smaller + if ((var_data->getSize() == 1) && + (size > 255)) + { + llwarns << "Field " << varname << " is a Variable 1 but program " + << "attempted to stuff more than 255 bytes in " + << "(" << size << "). Clamping size and truncating data." << llendl; + size = 255; + char *truncate = (char *)data; + truncate[255] = 0; + } + + // no correct size for MVT_VARIABLE, instead we need to tell how many bytes the size will be encoded as + mCurrentSDataBlock->addData(vnamep, data, size, type, var_data->getSize()); + mCurrentSendTotal += size; + } + else + { + if (size != var_data->getSize()) + { + llerrs << varname << " is type MVT_FIXED but request size " << size << " doesn't match template size " + << var_data->getSize() << llendl; + return; + } + // alright, smash it in + mCurrentSDataBlock->addData(vnamep, data, size, type); + mCurrentSendTotal += size; + } +} + +// add data to variable in current block - fails if variable isn't MVT_FIXED +void LLMessageSystem::addDataFast(const char *varname, const void *data, EMsgVariableType type) +{ + char *vnamep = (char *)varname; + + // do we have a current message? + if (!mCurrentSMessageTemplate) + { + llerrs << "newMessage not called prior to addData" << llendl; + return; + } + + // do we have a current block? + if (!mCurrentSDataBlock) + { + llerrs << "setBlock not called prior to addData" << llendl; + return; + } + + // kewl, add the data if it exists + LLMessageVariable* var_data = mCurrentSMessageTemplate->mMemberBlocks[mCurrentSBlockName]->mMemberVariables[vnamep]; + if (!var_data->getName()) + { + llerrs << vnamep << " not a variable in block " << mCurrentSBlockName << " of " << mCurrentSMessageTemplate->mName << llendl; + return; + } + + // ok, it seems ok. . . are we MVT_VARIABLE? + if (var_data->getType() == MVT_VARIABLE) + { + // nope + llerrs << vnamep << " is type MVT_VARIABLE. Call using addData(name, data, size)" << llendl; + return; + } + else + { + mCurrentSDataBlock->addData(vnamep, data, var_data->getSize(), type); + mCurrentSendTotal += var_data->getSize(); + } +} + +BOOL LLMessageSystem::isSendFull(const char* blockname) +{ + if(!blockname) + { + return (mCurrentSendTotal > MTUBYTES); + } + return isSendFullFast(gMessageStringTable.getString(blockname)); +} + +BOOL LLMessageSystem::isSendFullFast(const char* blockname) +{ + if(mCurrentSendTotal > MTUBYTES) + { + return TRUE; + } + if(!blockname) + { + return FALSE; + } + char* bnamep = (char*)blockname; + S32 max; + + LLMessageBlock* template_data = mCurrentSMessageTemplate->mMemberBlocks[bnamep]; + + switch(template_data->mType) + { + case MBT_SINGLE: + max = 1; + break; + case MBT_MULTIPLE: + max = template_data->mNumber; + break; + case MBT_VARIABLE: + default: + max = MAX_BLOCKS; + break; + } + if(mCurrentSMessageData->mMemberBlocks[bnamep]->mBlockNumber >= max) + { + return TRUE; + } + return FALSE; +} + + +// blow away the last block of a message, return FALSE if that leaves no blocks or there wasn't a block to remove +BOOL LLMessageSystem::removeLastBlock() +{ + if (mCurrentSBlockName) + { + if ( (mCurrentSMessageData) + &&(mCurrentSMessageTemplate)) + { + if (mCurrentSMessageData->mMemberBlocks[mCurrentSBlockName]->mBlockNumber >= 1) + { + // At least one block for the current block name. + + // Store the current block name for future reference. + char *block_name = mCurrentSBlockName; + + // Decrement the sent total by the size of the + // data in the message block that we're currently building. + + LLMessageBlock* template_data = mCurrentSMessageTemplate->mMemberBlocks[mCurrentSBlockName]; + + for (LLMessageBlock::message_variable_map_t::iterator iter = template_data->mMemberVariables.begin(); + iter != template_data->mMemberVariables.end(); iter++) + { + LLMessageVariable& ci = *(iter->second); + mCurrentSendTotal -= ci.getSize(); + } + + + // Now we want to find the block that we're blowing away. + + // Get the number of blocks. + LLMsgBlkData* block_data = mCurrentSMessageData->mMemberBlocks[block_name]; + S32 num_blocks = block_data->mBlockNumber; + + // Use the same (suspect?) algorithm that's used to generate + // the names in the nextBlock method to find it. + char *block_getting_whacked = block_name + num_blocks - 1; + LLMsgBlkData* whacked_data = mCurrentSMessageData->mMemberBlocks[block_getting_whacked]; + delete whacked_data; + mCurrentSMessageData->mMemberBlocks.erase(block_getting_whacked); + + if (num_blocks <= 1) + { + // we just blew away the last one, so return FALSE + return FALSE; + } + else + { + // Decrement the counter. + block_data->mBlockNumber--; + return TRUE; + } + } + } + } + return FALSE; +} + +// make sure that all the desired data is in place and then copy the data into mSendBuffer +void LLMessageSystem::buildMessage() +{ + // basic algorithm is to loop through the various pieces, building + // size and offset info if we encounter a -1 for mSize at any + // point that variable wasn't given data + + // do we have a current message? + if (!mCurrentSMessageTemplate) + { + llerrs << "newMessage not called prior to buildMessage" << llendl; + return; + } + + // zero out some useful values + + // leave room for circuit counter + mSendSize = LL_PACKET_ID_SIZE; + + // encode message number and adjust total_offset + if (mCurrentSMessageTemplate->mFrequency == MFT_HIGH) + { +// old, endian-dependant way +// memcpy(&mSendBuffer[mSendSize], &mCurrentMessageTemplate->mMessageNumber, sizeof(U8)); + +// new, independant way + mSendBuffer[mSendSize] = (U8)mCurrentSMessageTemplate->mMessageNumber; + mSendSize += sizeof(U8); + } + else if (mCurrentSMessageTemplate->mFrequency == MFT_MEDIUM) + { + U8 temp = 255; + memcpy(&mSendBuffer[mSendSize], &temp, sizeof(U8)); /*Flawfinder: ignore*/ + mSendSize += sizeof(U8); + + // mask off unsightly bits + temp = mCurrentSMessageTemplate->mMessageNumber & 255; + memcpy(&mSendBuffer[mSendSize], &temp, sizeof(U8)); /*Flawfinder: ignore*/ + mSendSize += sizeof(U8); + } + else if (mCurrentSMessageTemplate->mFrequency == MFT_LOW) + { + U8 temp = 255; + U16 message_num; + memcpy(&mSendBuffer[mSendSize], &temp, sizeof(U8)); /*Flawfinder: ignore*/ + mSendSize += sizeof(U8); + memcpy(&mSendBuffer[mSendSize], &temp, sizeof(U8)); /*Flawfinder: ignore*/ + mSendSize += sizeof(U8); + + // mask off unsightly bits + message_num = mCurrentSMessageTemplate->mMessageNumber & 0xFFFF; + + // convert to network byte order + message_num = htons(message_num); + memcpy(&mSendBuffer[mSendSize], &message_num, sizeof(U16)); /*Flawfinder: ignore*/ + mSendSize += sizeof(U16); + } + else + { + llerrs << "unexpected message frequency in buildMessage" << llendl; + return; + } + + // counting variables used to encode multiple block info + S32 block_count = 0; + U8 temp_block_number; + + // loop through msg blocks to loop through variables, totalling up size data and copying into mSendBuffer + for (LLMsgData::msg_blk_data_map_t::iterator + iter = mCurrentSMessageData->mMemberBlocks.begin(), + end = mCurrentSMessageData->mMemberBlocks.end(); + iter != end; iter++) + { + LLMsgBlkData* mbci = iter->second; + // do we need to encode a block code? + if (block_count == 0) + { + block_count = mbci->mBlockNumber; + + LLMessageBlock* template_data = mCurrentSMessageTemplate->mMemberBlocks[mbci->mName]; + + // ok, if this is the first block of a repeating pack, set block_count and, if it's type MBT_VARIABLE encode a byte for how many there are + if (template_data->mType == MBT_VARIABLE) + { + // remember that mBlockNumber is a S32 + temp_block_number = (U8)mbci->mBlockNumber; + if ((S32)(mSendSize + sizeof(U8)) < MAX_BUFFER_SIZE) + { + memcpy(&mSendBuffer[mSendSize], &temp_block_number, sizeof(U8)); + mSendSize += sizeof(U8); + } + else + { + // Just reporting error is likely not enough. Need + // to check how to abort or error out gracefully + // from this function. XXXTBD + llerrs << "buildMessage failed. Message excedding" + " sendBuffersize." << llendl; + } + } + else if (template_data->mType == MBT_MULTIPLE) + { + if (block_count != template_data->mNumber) + { + // nope! need to fill it in all the way! + llerrs << "Block " << mbci->mName + << " is type MBT_MULTIPLE but only has data for " + << block_count << " out of its " + << template_data->mNumber << " blocks" << llendl; + } + } + } + + // counting down multiple blocks + block_count--; + + // now loop through the variables + for (LLMsgBlkData::msg_var_data_map_t::iterator iter = mbci->mMemberVarData.begin(); + iter != mbci->mMemberVarData.end(); iter++) + { + LLMsgVarData& mvci = *iter; + if (mvci.getSize() == -1) + { + // oops, this variable wasn't ever set! + llerrs << "The variable " << mvci.getName() << " in block " + << mbci->mName << " of message " + << mCurrentSMessageData->mName + << " wasn't set prior to buildMessage call" << llendl; + } + else + { + S32 data_size = mvci.getDataSize(); + if(data_size > 0) + { + // The type is MVT_VARIABLE, which means that we + // need to encode a size argument. Otherwise, + // there is no need. + S32 size = mvci.getSize(); + U8 sizeb; + U16 sizeh; + switch(data_size) + { + case 1: + sizeb = size; + htonmemcpy(&mSendBuffer[mSendSize], &sizeb, MVT_U8, 1); + break; + case 2: + sizeh = size; + htonmemcpy(&mSendBuffer[mSendSize], &sizeh, MVT_U16, 2); + break; + case 4: + htonmemcpy(&mSendBuffer[mSendSize], &size, MVT_S32, 4); + break; + default: + llerrs << "Attempting to build variable field with unknown size of " << size << llendl; + break; + } + mSendSize += mvci.getDataSize(); + } + + // if there is any data to pack, pack it + if((mvci.getData() != NULL) && mvci.getSize()) + { + if(mSendSize + mvci.getSize() < (S32)sizeof(mSendBuffer)) + { + memcpy( + &mSendBuffer[mSendSize], + mvci.getData(), + mvci.getSize()); + mSendSize += mvci.getSize(); + } + else + { + // Just reporting error is likely not + // enough. Need to check how to abort or error + // out gracefully from this function. XXXTBD + llerrs << "LLMessageSystem::buildMessage failed. " + << "Attempted to pack " + << mSendSize + mvci.getSize() + << " bytes into a buffer with size " + << mSendBuffer << "." << llendl + } + } + } + } + } + mbSBuilt = TRUE; +} + +S32 LLMessageSystem::sendReliable(const LLHost &host) +{ + return sendReliable(host, LL_DEFAULT_RELIABLE_RETRIES, TRUE, LL_PING_BASED_TIMEOUT_DUMMY, NULL, NULL); +} + + +S32 LLMessageSystem::sendSemiReliable(const LLHost &host, void (*callback)(void **,S32), void ** callback_data) +{ + F32 timeout; + + LLCircuitData *cdp = mCircuitInfo.findCircuit(host); + if (cdp) + { + timeout = llmax(LL_MINIMUM_SEMIRELIABLE_TIMEOUT_SECONDS, + LL_SEMIRELIABLE_TIMEOUT_FACTOR * cdp->getPingDelayAveraged()); + } + else + { + timeout = LL_SEMIRELIABLE_TIMEOUT_FACTOR * LL_AVERAGED_PING_MAX; + } + + return sendReliable(host, 0, FALSE, timeout, callback, callback_data); +} + +// send the message via a UDP packet +S32 LLMessageSystem::sendReliable( const LLHost &host, + S32 retries, + BOOL ping_based_timeout, + F32 timeout, + void (*callback)(void **,S32), + void ** callback_data) +{ + if (ping_based_timeout) + { + LLCircuitData *cdp = mCircuitInfo.findCircuit(host); + if (cdp) + { + timeout = llmax(LL_MINIMUM_RELIABLE_TIMEOUT_SECONDS, LL_RELIABLE_TIMEOUT_FACTOR * cdp->getPingDelayAveraged()); + } + else + { + timeout = llmax(LL_MINIMUM_RELIABLE_TIMEOUT_SECONDS, LL_RELIABLE_TIMEOUT_FACTOR * LL_AVERAGED_PING_MAX); + } + } + + mSendReliable = TRUE; + mReliablePacketParams.set(host, retries, ping_based_timeout, timeout, + callback, callback_data, mCurrentSMessageName); + return sendMessage(host); +} + +void LLMessageSystem::forwardMessage(const LLHost &host) +{ + copyMessageRtoS(); + sendMessage(host); +} + +void LLMessageSystem::forwardReliable(const LLHost &host) +{ + copyMessageRtoS(); + sendReliable(host); +} + +void LLMessageSystem::forwardReliable(const U32 circuit_code) +{ + copyMessageRtoS(); + sendReliable(findHost(circuit_code)); +} + +S32 LLMessageSystem::flushSemiReliable(const LLHost &host, void (*callback)(void **,S32), void ** callback_data) +{ + F32 timeout; + + LLCircuitData *cdp = mCircuitInfo.findCircuit(host); + if (cdp) + { + timeout = llmax(LL_MINIMUM_SEMIRELIABLE_TIMEOUT_SECONDS, + LL_SEMIRELIABLE_TIMEOUT_FACTOR * cdp->getPingDelayAveraged()); + } + else + { + timeout = LL_SEMIRELIABLE_TIMEOUT_FACTOR * LL_AVERAGED_PING_MAX; + } + + S32 send_bytes = 0; + if (mCurrentSendTotal) + { + mSendReliable = TRUE; + // No need for ping-based retry as not going to retry + mReliablePacketParams.set(host, 0, FALSE, timeout, callback, callback_data, mCurrentSMessageName); + send_bytes = sendMessage(host); + clearMessage(); + } + else + { + delete callback_data; + } + return send_bytes; +} + +S32 LLMessageSystem::flushReliable(const LLHost &host) +{ + S32 send_bytes = 0; + if (mCurrentSendTotal) + { + send_bytes = sendReliable(host); + } + clearMessage(); + return send_bytes; +} + + +// This can be called from signal handlers, +// so should should not use llinfos. +S32 LLMessageSystem::sendMessage(const LLHost &host) +{ + if (!mbSBuilt) + { + buildMessage(); + } + + mCurrentSendTotal = 0; + + if (!(host.isOk())) // if port and ip are zero, don't bother trying to send the message + { + return 0; + } + + LLCircuitData *cdp = mCircuitInfo.findCircuit(host); + if (!cdp) + { + // this is a new circuit! + // are we protected? + if (mbProtected) + { + // yup! don't send packets to an unknown circuit + if(mVerboseLog) + { + llinfos << "MSG: -> " << host << "\tUNKNOWN CIRCUIT:\t" + << mCurrentSMessageName << llendl; + } + llwarns << "sendMessage - Trying to send " + << mCurrentSMessageName << " on unknown circuit " + << host << llendl; + return 0; + } + else + { + // nope, open the new circuit + cdp = mCircuitInfo.addCircuitData(host, 0); + } + } + else + { + // this is an old circuit. . . is it still alive? + if (!cdp->isAlive()) + { + // nope. don't send to dead circuits + if(mVerboseLog) + { + llinfos << "MSG: -> " << host << "\tDEAD CIRCUIT\t\t" + << mCurrentSMessageName << llendl; + } + llwarns << "sendMessage - Trying to send message " + << mCurrentSMessageName << " to dead circuit " + << host << llendl; + return 0; + } + } + + memset(mSendBuffer,0,LL_PACKET_ID_SIZE); // zero out the packet ID field + + // add the send id to the front of the message + cdp->nextPacketOutID(); + + // Packet ID size is always 4 + *((S32*)&mSendBuffer[0]) = htonl(cdp->getPacketOutID()); + + // Compress the message, which will usually reduce its size. + U8 * buf_ptr = (U8 *)mSendBuffer; + S32 buffer_length = mSendSize; + if(ME_ZEROCODED == mCurrentSMessageTemplate->getEncoding()) + { + zeroCode(&buf_ptr, &buffer_length); + } + + if (buffer_length > 1500) + { + if((mCurrentSMessageName != _PREHASH_ChildAgentUpdate) + && (mCurrentSMessageName != _PREHASH_SendXferPacket)) + { + llwarns << "sendMessage - Trying to send " + << ((buffer_length > 4000) ? "EXTRA " : "") + << "BIG message " << mCurrentSMessageName << " - " + << buffer_length << llendl; + } + } + if (mSendReliable) + { + buf_ptr[0] |= LL_RELIABLE_FLAG; + + if (!cdp->getUnackedPacketCount()) + { + // We are adding the first packed onto the unacked packet list(s) + // Add this circuit to the list of circuits with unacked packets + mCircuitInfo.mUnackedCircuitMap[cdp->mHost] = cdp; + } + + cdp->addReliablePacket(mSocket,buf_ptr,buffer_length, &mReliablePacketParams); + mReliablePacketsOut++; + } + + // tack packet acks onto the end of this message + S32 space_left = (MTUBYTES - buffer_length) / sizeof(TPACKETID); // space left for packet ids + S32 ack_count = (S32)cdp->mAcks.size(); + BOOL is_ack_appended = FALSE; + std::vector acks; + if((space_left > 0) && (ack_count > 0) && + (mCurrentSMessageName != _PREHASH_PacketAck)) + { + buf_ptr[0] |= LL_ACK_FLAG; + S32 append_ack_count = llmin(space_left, ack_count); + const S32 MAX_ACKS = 250; + append_ack_count = llmin(append_ack_count, MAX_ACKS); + std::vector::iterator iter = cdp->mAcks.begin(); + std::vector::iterator last = cdp->mAcks.begin(); + last += append_ack_count; + TPACKETID packet_id; + for( ; iter != last ; ++iter) + { + // grab the next packet id. + packet_id = (*iter); + if(mVerboseLog) + { + acks.push_back(packet_id); + } + + // put it on the end of the buffer + packet_id = htonl(packet_id); + + if((S32)(buffer_length + sizeof(TPACKETID)) < MAX_BUFFER_SIZE) + { + memcpy(&buf_ptr[buffer_length], &packet_id, sizeof(TPACKETID)); + // Do the accounting + buffer_length += sizeof(TPACKETID); + } + else + { + // Just reporting error is likely not enough. Need to + // check how to abort or error out gracefully from + // this function. XXXTBD + // *NOTE: Actually hitting this error would indicate + // the calculation above for space_left, ack_count, + // append_acout_count is incorrect or that + // MAX_BUFFER_SIZE has fallen below MTU which is bad + // and probably programmer error. + llerrs << "Buffer packing failed due to size.." << llendl; + } + } + + // clean up the source + cdp->mAcks.erase(cdp->mAcks.begin(), last); + + // tack the count in the final byte + U8 count = (U8)append_ack_count; + buf_ptr[buffer_length++] = count; + is_ack_appended = TRUE; + } + + BOOL success; + success = mPacketRing.sendPacket(mSocket, (char *)buf_ptr, buffer_length, host); + + if (!success) + { + mSendPacketFailureCount++; + } + else + { + // mCircuitInfo already points to the correct circuit data + cdp->addBytesOut( buffer_length ); + } + + if(mVerboseLog) + { + std::ostringstream str; + str << "MSG: -> " << host; + char buffer[MAX_STRING]; /* Flawfinder: ignore */ + snprintf(buffer, MAX_STRING, "\t%6d\t%6d\t%6d ", mSendSize, buffer_length, cdp->getPacketOutID()); /* Flawfinder: ignore */ + str << buffer + << mCurrentSMessageTemplate->mName + << (mSendReliable ? " reliable " : ""); + if(is_ack_appended) + { + str << "\tACKS:\t"; + std::ostream_iterator append(str, " "); + std::copy(acks.begin(), acks.end(), append); + } + llinfos << str.str() << llendl; + } + + lldebugst(LLERR_MESSAGE) << "MessageSent at: " << (S32)totalTime() + << ", " << mCurrentSMessageTemplate->mName + << " to " << host + << llendl; + + // ok, clean up temp data + delete mCurrentSMessageData; + mCurrentSMessageData = NULL; + + mPacketsOut++; + mBytesOut += buffer_length; + + return buffer_length; +} + + +// Returns template for the message contained in buffer +BOOL LLMessageSystem::decodeTemplate( + const U8* buffer, S32 buffer_size, // inputs + LLMessageTemplate** msg_template ) // outputs +{ + const U8* header = buffer + LL_PACKET_ID_SIZE; + + // is there a message ready to go? + if (buffer_size <= 0) + { + llwarns << "No message waiting for decode!" << llendl; + return(FALSE); + } + + U32 num = 0; + + if (header[0] != 255) + { + // high frequency message + num = header[0]; + } + else if ((buffer_size >= ((S32) LL_MINIMUM_VALID_PACKET_SIZE + 1)) && (header[1] != 255)) + { + // medium frequency message + num = (255 << 8) | header[1]; + } + else if ((buffer_size >= ((S32) LL_MINIMUM_VALID_PACKET_SIZE + 3)) && (header[1] == 255)) + { + // low frequency message + U16 message_id_U16 = 0; + // I think this check busts the message system. + // it appears that if there is a NULL in the message #, it won't copy it.... + // what was the goal? + //if(header[2]) + memcpy(&message_id_U16, &header[2], 2); + + // dependant on endian-ness: + // U32 temp = (255 << 24) | (255 << 16) | header[2]; + + // independant of endian-ness: + message_id_U16 = ntohs(message_id_U16); + num = 0xFFFF0000 | message_id_U16; + } + else // bogus packet received (too short) + { + llwarns << "Packet with unusable length received (too short): " + << buffer_size << llendl; + return(FALSE); + } + + LLMessageTemplate* temp = get_ptr_in_map(mMessageNumbers,num); + if (temp) + { + *msg_template = temp; + } + else + { + llwarns << "Message #" << std::hex << num << std::dec + << " received but not registered!" << llendl; + callExceptionFunc(MX_UNREGISTERED_MESSAGE); + return(FALSE); + } + + return(TRUE); +} + + +void LLMessageSystem::logMsgFromInvalidCircuit( const LLHost& host, BOOL recv_reliable ) +{ + if(mVerboseLog) + { + std::ostringstream str; + str << "MSG: <- " << host; + char buffer[MAX_STRING]; /* Flawfinder: ignore */ + snprintf(buffer, MAX_STRING, "\t%6d\t%6d\t%6d ", mReceiveSize, (mIncomingCompressedSize ? mIncomingCompressedSize: mReceiveSize), mCurrentRecvPacketID); /* Flawfinder: ignore */ + str << buffer + << mCurrentRMessageTemplate->mName + << (recv_reliable ? " reliable" : "") + << " REJECTED"; + llinfos << str.str() << llendl; + } + // nope! + // cout << "Rejecting unexpected message " << mCurrentMessageTemplate->mName << " from " << hex << ip << " , " << dec << port << endl; + + // Keep track of rejected messages as well + if (mNumMessageCounts >= MAX_MESSAGE_COUNT_NUM) + { + llwarns << "Got more than " << MAX_MESSAGE_COUNT_NUM << " packets without clearing counts" << llendl; + } + else + { + mMessageCountList[mNumMessageCounts].mMessageNum = mCurrentRMessageTemplate->mMessageNumber; + mMessageCountList[mNumMessageCounts].mMessageBytes = mReceiveSize; + mMessageCountList[mNumMessageCounts].mInvalid = TRUE; + mNumMessageCounts++; + } +} + +void LLMessageSystem::logTrustedMsgFromUntrustedCircuit( const LLHost& host ) +{ + llwarns << "Recieved trusted message on untrusted circuit. " + << "Will reply with deny. " + << "Message: " << mCurrentRMessageTemplate->mName + << " Host: " << host << llendl; + if (mNumMessageCounts >= MAX_MESSAGE_COUNT_NUM) + { + llwarns << "got more than " << MAX_MESSAGE_COUNT_NUM + << " packets without clearing counts" + << llendl; + } + else + { + mMessageCountList[mNumMessageCounts].mMessageNum + = mCurrentRMessageTemplate->mMessageNumber; + mMessageCountList[mNumMessageCounts].mMessageBytes + = mReceiveSize; + mMessageCountList[mNumMessageCounts].mInvalid = TRUE; + mNumMessageCounts++; + } +} + +void LLMessageSystem::logValidMsg(LLCircuitData *cdp, const LLHost& host, BOOL recv_reliable, BOOL recv_resent, BOOL recv_acks ) +{ + if (mNumMessageCounts >= MAX_MESSAGE_COUNT_NUM) + { + llwarns << "Got more than " << MAX_MESSAGE_COUNT_NUM << " packets without clearing counts" << llendl; + } + else + { + mMessageCountList[mNumMessageCounts].mMessageNum = mCurrentRMessageTemplate->mMessageNumber; + mMessageCountList[mNumMessageCounts].mMessageBytes = mReceiveSize; + mMessageCountList[mNumMessageCounts].mInvalid = FALSE; + mNumMessageCounts++; + } + + if (cdp) + { + // update circuit packet ID tracking (missing/out of order packets) + cdp->checkPacketInID( mCurrentRecvPacketID, recv_resent ); + cdp->addBytesIn( mTrueReceiveSize ); + } + + if(mVerboseLog) + { + std::ostringstream str; + str << "MSG: <- " << host; + char buffer[MAX_STRING]; /* Flawfinder: ignore */ + snprintf(buffer, MAX_STRING, "\t%6d\t%6d\t%6d ", mReceiveSize, (mIncomingCompressedSize ? mIncomingCompressedSize : mReceiveSize), mCurrentRecvPacketID); /* Flawfinder: ignore */ + str << buffer + << mCurrentRMessageTemplate->mName + << (recv_reliable ? " reliable" : "") + << (recv_resent ? " resent" : "") + << (recv_acks ? " acks" : ""); + llinfos << str.str() << llendl; + } +} + + +void LLMessageSystem::logRanOffEndOfPacket( const LLHost& host ) +{ + // we've run off the end of the packet! + llwarns << "Ran off end of packet " << mCurrentRMessageTemplate->mName + << " with id " << mCurrentRecvPacketID << " from " << host + << llendl; + if(mVerboseLog) + { + llinfos << "MSG: -> " << host << "\tREAD PAST END:\t" + << mCurrentRecvPacketID << " " + << mCurrentSMessageTemplate->mName << llendl; + } + callExceptionFunc(MX_RAN_OFF_END_OF_PACKET); +} + + +// decode a given message +BOOL LLMessageSystem::decodeData(const U8* buffer, const LLHost& sender ) +{ + llassert( mReceiveSize >= 0 ); + llassert( mCurrentRMessageTemplate); + llassert( !mCurrentRMessageData ); + delete mCurrentRMessageData; // just to make sure + + S32 decode_pos = LL_PACKET_ID_SIZE + (S32)(mCurrentRMessageTemplate->mFrequency); + + // create base working data set + mCurrentRMessageData = new LLMsgData(mCurrentRMessageTemplate->mName); + + // loop through the template building the data structure as we go + for (LLMessageTemplate::message_block_map_t::iterator iter = mCurrentRMessageTemplate->mMemberBlocks.begin(); + iter != mCurrentRMessageTemplate->mMemberBlocks.end(); iter++) + { + LLMessageBlock* mbci = iter->second; + U8 repeat_number; + S32 i; + + // how many of this block? + + if (mbci->mType == MBT_SINGLE) + { + // just one + repeat_number = 1; + } + else if (mbci->mType == MBT_MULTIPLE) + { + // a known number + repeat_number = mbci->mNumber; + } + else if (mbci->mType == MBT_VARIABLE) + { + // need to read the number from the message + // repeat number is a single byte + if (decode_pos >= mReceiveSize) + { + logRanOffEndOfPacket( sender ); + return FALSE; + } + repeat_number = buffer[decode_pos]; + decode_pos++; + } + else + { + llerrs << "Unknown block type" << llendl; + return FALSE; + } + + LLMsgBlkData* cur_data_block = NULL; + + // now loop through the block + for (i = 0; i < repeat_number; i++) + { + if (i) + { + // build new name to prevent collisions + // TODO: This should really change to a vector + cur_data_block = new LLMsgBlkData(mbci->mName, repeat_number); + cur_data_block->mName = mbci->mName + i; + } + else + { + cur_data_block = new LLMsgBlkData(mbci->mName, repeat_number); + } + + // add the block to the message + mCurrentRMessageData->addBlock(cur_data_block); + + // now read the variables + for (LLMessageBlock::message_variable_map_t::iterator iter = mbci->mMemberVariables.begin(); + iter != mbci->mMemberVariables.end(); iter++) + { + LLMessageVariable& mvci = *(iter->second); + // ok, build out the variables + // add variable block + cur_data_block->addVariable(mvci.getName(), mvci.getType()); + + // what type of variable? + if (mvci.getType() == MVT_VARIABLE) + { + // variable, get the number of bytes to read from the template + S32 data_size = mvci.getSize(); + U8 tsizeb = 0; + U16 tsizeh = 0; + U32 tsize = 0; + + if ((decode_pos + data_size) > mReceiveSize) + { + logRanOffEndOfPacket( sender ); + return FALSE; + } + switch(data_size) + { + case 1: + htonmemcpy(&tsizeb, &buffer[decode_pos], MVT_U8, 1); + tsize = tsizeb; + break; + case 2: + htonmemcpy(&tsizeh, &buffer[decode_pos], MVT_U16, 2); + tsize = tsizeh; + break; + case 4: + htonmemcpy(&tsizeb, &buffer[decode_pos], MVT_U32, 4); + break; + default: + llerrs << "Attempting to read variable field with unknown size of " << data_size << llendl; + break; + + } + decode_pos += data_size; + + if ((decode_pos + (S32)tsize) > mReceiveSize) + { + logRanOffEndOfPacket( sender ); + return FALSE; + } + cur_data_block->addData(mvci.getName(), &buffer[decode_pos], tsize, mvci.getType()); + decode_pos += tsize; + } + else + { + // fixed! + // so, copy data pointer and set data size to fixed size + + if ((decode_pos + mvci.getSize()) > mReceiveSize) + { + logRanOffEndOfPacket( sender ); + return FALSE; + } + + cur_data_block->addData(mvci.getName(), &buffer[decode_pos], mvci.getSize(), mvci.getType()); + decode_pos += mvci.getSize(); + } + } + } + } + + if (mCurrentRMessageData->mMemberBlocks.empty() + && !mCurrentRMessageTemplate->mMemberBlocks.empty()) + { + lldebugs << "Empty message '" << mCurrentRMessageTemplate->mName << "' (no blocks)" << llendl; + return FALSE; + } + + { + static LLTimer decode_timer; + + if( mTimeDecodes ) + { + decode_timer.reset(); + } + + // if( mCurrentRMessageTemplate->mName == _PREHASH_AgentToNewRegion ) + // { + // VTResume(); // VTune + // } + + { + LLFastTimer t(LLFastTimer::FTM_PROCESS_MESSAGES); + if( !mCurrentRMessageTemplate->callHandlerFunc(this) ) + { + llwarns << "Message from " << sender << " with no handler function received: " << mCurrentRMessageTemplate->mName << llendl; + } + } + + // if( mCurrentRMessageTemplate->mName == _PREHASH_AgentToNewRegion ) + // { + // VTPause(); // VTune + // } + + if( mTimeDecodes ) + { + F32 decode_time = decode_timer.getElapsedTimeF32(); + mCurrentRMessageTemplate->mDecodeTimeThisFrame += decode_time; + + mCurrentRMessageTemplate->mTotalDecoded++; + mCurrentRMessageTemplate->mTotalDecodeTime += decode_time; + + if( mCurrentRMessageTemplate->mMaxDecodeTimePerMsg < decode_time ) + { + mCurrentRMessageTemplate->mMaxDecodeTimePerMsg = decode_time; + } + + + if( decode_time > mTimeDecodesSpamThreshold ) + { + lldebugs << "--------- Message " << mCurrentRMessageTemplate->mName << " decode took " << decode_time << " seconds. (" << + mCurrentRMessageTemplate->mMaxDecodeTimePerMsg << " max, " << + (mCurrentRMessageTemplate->mTotalDecodeTime / mCurrentRMessageTemplate->mTotalDecoded) << " avg)" << llendl; + } + } + } + return TRUE; +} + +void LLMessageSystem::getDataFast(const char *blockname, const char *varname, void *datap, S32 size, S32 blocknum, S32 max_size) +{ + // is there a message ready to go? + if (mReceiveSize == -1) + { + llerrs << "No message waiting for decode 2!" << llendl; + return; + } + + if (!mCurrentRMessageData) + { + llerrs << "Invalid mCurrentMessageData in getData!" << llendl; + return; + } + + char *bnamep = (char *)blockname + blocknum; // this works because it's just a hash. The bnamep is never derefference + char *vnamep = (char *)varname; + + LLMsgData::msg_blk_data_map_t::iterator iter = mCurrentRMessageData->mMemberBlocks.find(bnamep); + + if (iter == mCurrentRMessageData->mMemberBlocks.end()) + { + llerrs << "Block " << blockname << " #" << blocknum + << " not in message " << mCurrentRMessageData->mName << llendl; + return; + } + + LLMsgBlkData *msg_block_data = iter->second; + LLMsgVarData& vardata = msg_block_data->mMemberVarData[vnamep]; + + if (!vardata.getName()) + { + llerrs << "Variable "<< vnamep << " not in message " + << mCurrentRMessageData->mName<< " block " << bnamep << llendl; + return; + } + + if (size && size != vardata.getSize()) + { + llerrs << "Msg " << mCurrentRMessageData->mName + << " variable " << vnamep + << " is size " << vardata.getSize() + << " but copying into buffer of size " << size + << llendl; + return; + } + + + const S32 vardata_size = vardata.getSize(); + if( max_size >= vardata_size ) + { + switch( vardata_size ) + { + case 1: + *((U8*)datap) = *((U8*)vardata.getData()); + break; + case 2: + *((U16*)datap) = *((U16*)vardata.getData()); + break; + case 4: + *((U32*)datap) = *((U32*)vardata.getData()); + break; + case 8: + ((U32*)datap)[0] = ((U32*)vardata.getData())[0]; + ((U32*)datap)[1] = ((U32*)vardata.getData())[1]; + break; + default: + memcpy(datap, vardata.getData(), vardata_size); + break; + } + } + else + { + llwarns << "Msg " << mCurrentRMessageData->mName + << " variable " << vnamep + << " is size " << vardata.getSize() + << " but truncated to max size of " << max_size + << llendl; + + memcpy(datap, vardata.getData(), max_size); + } +} + +S32 LLMessageSystem::getNumberOfBlocksFast(const char *blockname) +{ + // is there a message ready to go? + if (mReceiveSize == -1) + { + llerrs << "No message waiting for decode 3!" << llendl; + return -1; + } + + if (!mCurrentRMessageData) + { + llerrs << "Invalid mCurrentRMessageData in getData!" << llendl; + return -1; + } + + char *bnamep = (char *)blockname; + + LLMsgData::msg_blk_data_map_t::iterator iter = mCurrentRMessageData->mMemberBlocks.find(bnamep); + + if (iter == mCurrentRMessageData->mMemberBlocks.end()) + { +// sprintf(errmsg, "Block %s not in message %s", bnamep, mCurrentRMessageData->mName); +// llerrs << errmsg << llendl; +// return -1; + return 0; + } + + return (iter->second)->mBlockNumber; +} + +S32 LLMessageSystem::getSizeFast(const char *blockname, const char *varname) +{ + // is there a message ready to go? + if (mReceiveSize == -1) + { + llerrs << "No message waiting for decode 4!" << llendl; + return -1; + } + + if (!mCurrentRMessageData) + { + llerrs << "Invalid mCurrentRMessageData in getData!" << llendl; + return -1; + } + + char *bnamep = (char *)blockname; + + LLMsgData::msg_blk_data_map_t::iterator iter = mCurrentRMessageData->mMemberBlocks.find(bnamep); + + if (iter == mCurrentRMessageData->mMemberBlocks.end()) + { + llerrs << "Block " << bnamep << " not in message " + << mCurrentRMessageData->mName << llendl; + return -1; + } + + char *vnamep = (char *)varname; + + LLMsgBlkData* msg_data = iter->second; + LLMsgVarData& vardata = msg_data->mMemberVarData[vnamep]; + + if (!vardata.getName()) + { + llerrs << "Variable " << varname << " not in message " + << mCurrentRMessageData->mName << " block " << bnamep << llendl; + return -1; + } + + if (mCurrentRMessageTemplate->mMemberBlocks[bnamep]->mType != MBT_SINGLE) + { + llerrs << "Block " << bnamep << " isn't type MBT_SINGLE," + " use getSize with blocknum argument!" << llendl; + return -1; + } + + return vardata.getSize(); +} + + +S32 LLMessageSystem::getSizeFast(const char *blockname, S32 blocknum, const char *varname) +{ + // is there a message ready to go? + if (mReceiveSize == -1) + { + llerrs << "No message waiting for decode 5!" << llendl; + return -1; + } + + if (!mCurrentRMessageData) + { + llerrs << "Invalid mCurrentRMessageData in getData!" << llendl; + return -1; + } + + char *bnamep = (char *)blockname + blocknum; + char *vnamep = (char *)varname; + + LLMsgData::msg_blk_data_map_t::iterator iter = mCurrentRMessageData->mMemberBlocks.find(bnamep); + + if (iter == mCurrentRMessageData->mMemberBlocks.end()) + { + llerrs << "Block " << bnamep << " not in message " + << mCurrentRMessageData->mName << llendl; + return -1; + } + + LLMsgBlkData* msg_data = iter->second; + LLMsgVarData& vardata = msg_data->mMemberVarData[vnamep]; + + if (!vardata.getName()) + { + llerrs << "Variable " << vnamep << " not in message " + << mCurrentRMessageData->mName << " block " << bnamep << llendl; + return -1; + } + + return vardata.getSize(); +} + + +void LLMessageSystem::sanityCheck() +{ + if (!mCurrentRMessageData) + { + llerrs << "mCurrentRMessageData is NULL" << llendl; + } + + if (!mCurrentRMessageTemplate) + { + llerrs << "mCurrentRMessageTemplate is NULL" << llendl; + } + +// if (!mCurrentRTemplateBlock) +// { +// llerrs << "mCurrentRTemplateBlock is NULL" << llendl; +// } + +// if (!mCurrentRDataBlock) +// { +// llerrs << "mCurrentRDataBlock is NULL" << llendl; +// } + + if (!mCurrentSMessageData) + { + llerrs << "mCurrentSMessageData is NULL" << llendl; + } + + if (!mCurrentSMessageTemplate) + { + llerrs << "mCurrentSMessageTemplate is NULL" << llendl; + } + +// if (!mCurrentSTemplateBlock) +// { +// llerrs << "mCurrentSTemplateBlock is NULL" << llendl; +// } + + if (!mCurrentSDataBlock) + { + llerrs << "mCurrentSDataBlock is NULL" << llendl; + } +} + +void LLMessageSystem::showCircuitInfo() +{ + llinfos << mCircuitInfo << llendl; +} + + +void LLMessageSystem::dumpCircuitInfo() +{ + lldebugst(LLERR_CIRCUIT_INFO) << mCircuitInfo << llendl; +} + +/* virtual */ +U32 LLMessageSystem::getOurCircuitCode() +{ + return mOurCircuitCode; +} + +LLString LLMessageSystem::getCircuitInfoString() +{ + LLString info_string; + + info_string += mCircuitInfo.getInfoString(); + return info_string; +} + +// returns whether the given host is on a trusted circuit +BOOL LLMessageSystem::getCircuitTrust(const LLHost &host) +{ + LLCircuitData *cdp = mCircuitInfo.findCircuit(host); + if (cdp) + { + return cdp->getTrusted(); + } + + return FALSE; +} + +// Activate a circuit, and set its trust level (TRUE if trusted, +// FALSE if not). +void LLMessageSystem::enableCircuit(const LLHost &host, BOOL trusted) +{ + LLCircuitData *cdp = mCircuitInfo.findCircuit(host); + if (!cdp) + { + cdp = mCircuitInfo.addCircuitData(host, 0); + } + else + { + cdp->setAlive(TRUE); + } + cdp->setTrusted(trusted); +} + +void LLMessageSystem::disableCircuit(const LLHost &host) +{ + llinfos << "LLMessageSystem::disableCircuit for " << host << llendl; + U32 code = gMessageSystem->findCircuitCode( host ); + + // Don't need to do this, as we're removing the circuit info anyway - djs 01/28/03 + + // don't clean up 0 circuit code entries + // because many hosts (neighbor sims, etc) can have the 0 circuit + if (code) + { + //if (mCircuitCodes.checkKey(code)) + code_session_map_t::iterator it = mCircuitCodes.find(code); + if(it != mCircuitCodes.end()) + { + llinfos << "Circuit " << code << " removed from list" << llendl; + //mCircuitCodes.removeData(code); + mCircuitCodes.erase(it); + } + + U64 ip_port = 0; + std::map::iterator iter = gMessageSystem->mCircuitCodeToIPPort.find(code); + if (iter != gMessageSystem->mCircuitCodeToIPPort.end()) + { + ip_port = iter->second; + + gMessageSystem->mCircuitCodeToIPPort.erase(iter); + + U32 old_port = (U32)(ip_port & (U64)0xFFFFFFFF); + U32 old_ip = (U32)(ip_port >> 32); + + llinfos << "Host " << LLHost(old_ip, old_port) << " circuit " << code << " removed from lookup table" << llendl; + gMessageSystem->mIPPortToCircuitCode.erase(ip_port); + } + } + else + { + // Sigh, since we can open circuits which don't have circuit + // codes, it's possible for this to happen... + + //llwarns << "Couldn't find circuit code for " << host << llendl; + } + + mCircuitInfo.removeCircuitData(host); +} + + +void LLMessageSystem::setCircuitAllowTimeout(const LLHost &host, BOOL allow) +{ + LLCircuitData *cdp = mCircuitInfo.findCircuit(host); + if (cdp) + { + cdp->setAllowTimeout(allow); + } +} + +void LLMessageSystem::setCircuitTimeoutCallback(const LLHost &host, void (*callback_func)(const LLHost & host, void *user_data), void *user_data) +{ + LLCircuitData *cdp = mCircuitInfo.findCircuit(host); + if (cdp) + { + cdp->setTimeoutCallback(callback_func, user_data); + } +} + + +BOOL LLMessageSystem::checkCircuitBlocked(const U32 circuit) +{ + LLHost host = findHost(circuit); + + if (!host.isOk()) + { + //llinfos << "checkCircuitBlocked: Unknown circuit " << circuit << llendl; + return TRUE; + } + + LLCircuitData *cdp = mCircuitInfo.findCircuit(host); + if (cdp) + { + return cdp->isBlocked(); + } + else + { + llinfos << "checkCircuitBlocked(circuit): Unknown host - " << host << llendl; + return FALSE; + } +} + +BOOL LLMessageSystem::checkCircuitAlive(const U32 circuit) +{ + LLHost host = findHost(circuit); + + if (!host.isOk()) + { + //llinfos << "checkCircuitAlive: Unknown circuit " << circuit << llendl; + return FALSE; + } + + LLCircuitData *cdp = mCircuitInfo.findCircuit(host); + if (cdp) + { + return cdp->isAlive(); + } + else + { + llinfos << "checkCircuitAlive(circuit): Unknown host - " << host << llendl; + return FALSE; + } +} + +BOOL LLMessageSystem::checkCircuitAlive(const LLHost &host) +{ + LLCircuitData *cdp = mCircuitInfo.findCircuit(host); + if (cdp) + { + return cdp->isAlive(); + } + else + { + //llinfos << "checkCircuitAlive(host): Unknown host - " << host << llendl; + return FALSE; + } +} + + +void LLMessageSystem::setCircuitProtection(BOOL b_protect) +{ + mbProtected = b_protect; +} + + +U32 LLMessageSystem::findCircuitCode(const LLHost &host) +{ + U64 ip64 = (U64) host.getAddress(); + U64 port64 = (U64) host.getPort(); + U64 ip_port = (ip64 << 32) | port64; + + return get_if_there(mIPPortToCircuitCode, ip_port, U32(0)); +} + +LLHost LLMessageSystem::findHost(const U32 circuit_code) +{ + if (mCircuitCodeToIPPort.count(circuit_code) > 0) + { + return LLHost(mCircuitCodeToIPPort[circuit_code]); + } + else + { + return LLHost::invalid; + } +} + +void LLMessageSystem::setMaxMessageTime(const F32 seconds) +{ + mMaxMessageTime = seconds; +} + +void LLMessageSystem::setMaxMessageCounts(const S32 num) +{ + mMaxMessageCounts = num; +} + + +std::ostream& operator<<(std::ostream& s, LLMessageSystem &msg) +{ + U32 i; + if (msg.mbError) + { + s << "Message system not correctly initialized"; + } + else + { + s << "Message system open on port " << msg.mPort << " and socket " << msg.mSocket << "\n"; +// s << "Message template file " << msg.mName << " loaded\n"; + + s << "\nHigh frequency messages:\n"; + + for (i = 1; msg.mMessageNumbers[i] && (i < 255); i++) + { + s << *(msg.mMessageNumbers[i]); + } + + s << "\nMedium frequency messages:\n"; + + for (i = (255 << 8) + 1; msg.mMessageNumbers[i] && (i < (255 << 8) + 255); i++) + { + s << *msg.mMessageNumbers[i]; + } + + s << "\nLow frequency messages:\n"; + + for (i = (0xFFFF0000) + 1; msg.mMessageNumbers[i] && (i < 0xFFFFFFFF); i++) + { + s << *msg.mMessageNumbers[i]; + } + } + return s; +} + +LLMessageSystem *gMessageSystem = NULL; + +// update appropriate ping info +void process_complete_ping_check(LLMessageSystem *msgsystem, void** /*user_data*/) +{ + U8 ping_id; + msgsystem->getU8Fast(_PREHASH_PingID, _PREHASH_PingID, ping_id); + + LLCircuitData *cdp; + cdp = msgsystem->mCircuitInfo.findCircuit(msgsystem->getSender()); + + // stop the appropriate timer + if (cdp) + { + cdp->pingTimerStop(ping_id); + } +} + +void process_start_ping_check(LLMessageSystem *msgsystem, void** /*user_data*/) +{ + U8 ping_id; + msgsystem->getU8Fast(_PREHASH_PingID, _PREHASH_PingID, ping_id); + + LLCircuitData *cdp; + cdp = msgsystem->mCircuitInfo.findCircuit(msgsystem->getSender()); + if (cdp) + { + // Grab the packet id of the oldest unacked packet + U32 packet_id; + msgsystem->getU32Fast(_PREHASH_PingID, _PREHASH_OldestUnacked, packet_id); + cdp->clearDuplicateList(packet_id); + } + + // Send off the response + msgsystem->newMessageFast(_PREHASH_CompletePingCheck); + msgsystem->nextBlockFast(_PREHASH_PingID); + msgsystem->addU8(_PREHASH_PingID, ping_id); + msgsystem->sendMessage(msgsystem->getSender()); +} + + + +// Note: this is currently unused. --mark +void open_circuit(LLMessageSystem *msgsystem, void** /*user_data*/) +{ + U32 ip; + U16 port; + + msgsystem->getIPAddrFast(_PREHASH_CircuitInfo, _PREHASH_IP, ip); + msgsystem->getIPPortFast(_PREHASH_CircuitInfo, _PREHASH_Port, port); + + // By default, OpenCircuit's are untrusted + msgsystem->enableCircuit(LLHost(ip, port), FALSE); +} + +void close_circuit(LLMessageSystem *msgsystem, void** /*user_data*/) +{ + msgsystem->disableCircuit(msgsystem->getSender()); +} + +// static +/* +void LLMessageSystem::processAssignCircuitCode(LLMessageSystem* msg, void**) +{ + // if we already have a circuit code, we can bail + if(msg->mOurCircuitCode) return; + LLUUID session_id; + msg->getUUIDFast(_PREHASH_CircuitCode, _PREHASH_SessionID, session_id); + if(session_id != msg->getMySessionID()) + { + llwarns << "AssignCircuitCode, bad session id. Expecting " + << msg->getMySessionID() << " but got " << session_id + << llendl; + return; + } + U32 code; + msg->getU32Fast(_PREHASH_CircuitCode, _PREHASH_Code, code); + if (!code) + { + llerrs << "Assigning circuit code of zero!" << llendl; + } + + msg->mOurCircuitCode = code; + llinfos << "Circuit code " << code << " assigned." << llendl; +} +*/ + +// static +void LLMessageSystem::processAddCircuitCode(LLMessageSystem* msg, void**) +{ + U32 code; + msg->getU32Fast(_PREHASH_CircuitCode, _PREHASH_Code, code); + LLUUID session_id; + msg->getUUIDFast(_PREHASH_CircuitCode, _PREHASH_SessionID, session_id); + (void)msg->addCircuitCode(code, session_id); + + // Send the ack back + //msg->newMessageFast(_PREHASH_AckAddCircuitCode); + //msg->nextBlockFast(_PREHASH_CircuitCode); + //msg->addU32Fast(_PREHASH_Code, code); + //msg->sendMessage(msg->getSender()); +} + +bool LLMessageSystem::addCircuitCode(U32 code, const LLUUID& session_id) +{ + if(!code) + { + llwarns << "addCircuitCode: zero circuit code" << llendl; + return false; + } + code_session_map_t::iterator it = mCircuitCodes.find(code); + if(it == mCircuitCodes.end()) + { + llinfos << "New circuit code " << code << " added" << llendl; + //msg->mCircuitCodes[circuit_code] = circuit_code; + + mCircuitCodes.insert(code_session_map_t::value_type(code, session_id)); + } + else + { + llinfos << "Duplicate circuit code " << code << " added" << llendl; + } + return true; +} + +//void ack_add_circuit_code(LLMessageSystem *msgsystem, void** /*user_data*/) +//{ + // By default, we do nothing. This particular message is only handled by the spaceserver +//} + +// static +void LLMessageSystem::processUseCircuitCode(LLMessageSystem* msg, void**) +{ + U32 circuit_code_in; + msg->getU32Fast(_PREHASH_CircuitCode, _PREHASH_Code, circuit_code_in); + + U32 ip = msg->getSenderIP(); + U32 port = msg->getSenderPort(); + + U64 ip64 = ip; + U64 port64 = port; + U64 ip_port_in = (ip64 << 32) | port64; + + if (circuit_code_in) + { + //if (!msg->mCircuitCodes.checkKey(circuit_code_in)) + code_session_map_t::iterator it; + it = msg->mCircuitCodes.find(circuit_code_in); + if(it == msg->mCircuitCodes.end()) + { + // Whoah, abort! We don't know anything about this circuit code. + llwarns << "UseCircuitCode for " << circuit_code_in + << " received without AddCircuitCode message - aborting" + << llendl; + return; + } + + LLUUID id; + msg->getUUIDFast(_PREHASH_CircuitCode, _PREHASH_ID, id); + LLUUID session_id; + msg->getUUIDFast(_PREHASH_CircuitCode, _PREHASH_SessionID, session_id); + if(session_id != (*it).second) + { + llwarns << "UseCircuitCode unmatched session id. Got " + << session_id << " but expected " << (*it).second + << llendl; + return; + } + + // Clean up previous references to this ip/port or circuit + U64 ip_port_old = get_if_there(msg->mCircuitCodeToIPPort, circuit_code_in, U64(0)); + U32 circuit_code_old = get_if_there(msg->mIPPortToCircuitCode, ip_port_in, U32(0)); + + if (ip_port_old) + { + if ((ip_port_old == ip_port_in) && (circuit_code_old == circuit_code_in)) + { + // Current information is the same as incoming info, ignore + llinfos << "Got duplicate UseCircuitCode for circuit " << circuit_code_in << " to " << msg->getSender() << llendl; + return; + } + + // Hmm, got a different IP and port for the same circuit code. + U32 circut_code_old_ip_port = get_if_there(msg->mIPPortToCircuitCode, ip_port_old, U32(0)); + msg->mCircuitCodeToIPPort.erase(circut_code_old_ip_port); + msg->mIPPortToCircuitCode.erase(ip_port_old); + U32 old_port = (U32)(ip_port_old & (U64)0xFFFFFFFF); + U32 old_ip = (U32)(ip_port_old >> 32); + llinfos << "Removing derelict lookup entry for circuit " << circuit_code_old << " to " << LLHost(old_ip, old_port) << llendl; + } + + if (circuit_code_old) + { + LLHost cur_host(ip, port); + + llwarns << "Disabling existing circuit for " << cur_host << llendl; + msg->disableCircuit(cur_host); + if (circuit_code_old == circuit_code_in) + { + llwarns << "Asymmetrical circuit to ip/port lookup!" << llendl; + llwarns << "Multiple circuit codes for " << cur_host << " probably!" << llendl; + llwarns << "Permanently disabling circuit" << llendl; + return; + } + else + { + llwarns << "Circuit code changed for " << msg->getSender() + << " from " << circuit_code_old << " to " + << circuit_code_in << llendl; + } + } + + // Since this comes from the viewer, it's untrusted, but it + // passed the circuit code and session id check, so we will go + // ahead and persist the ID associated. + LLCircuitData *cdp = msg->mCircuitInfo.findCircuit(msg->getSender()); + BOOL had_circuit_already = cdp ? TRUE : FALSE; + + msg->enableCircuit(msg->getSender(), FALSE); + cdp = msg->mCircuitInfo.findCircuit(msg->getSender()); + if(cdp) + { + cdp->setRemoteID(id); + cdp->setRemoteSessionID(session_id); + } + + if (!had_circuit_already) + { + // + // HACK HACK HACK HACK HACK! + // + // This would NORMALLY happen inside logValidMsg, but at the point that this happens + // inside logValidMsg, there's no circuit for this message yet. So the awful thing that + // we do here is do it inside this message handler immediately AFTER the message is + // handled. + // + // We COULD not do this, but then what happens is that some of the circuit bookkeeping + // gets broken, especially the packets in count. That causes some later packets to flush + // the RecentlyReceivedReliable list, resulting in an error in which UseCircuitCode + // doesn't get properly duplicate suppressed. Not a BIG deal, but it's somewhat confusing + // (and bad from a state point of view). DJS 9/23/04 + // + cdp->checkPacketInID(gMessageSystem->mCurrentRecvPacketID, FALSE ); // Since this is the first message on the circuit, by definition it's not resent. + } + + msg->mIPPortToCircuitCode[ip_port_in] = circuit_code_in; + msg->mCircuitCodeToIPPort[circuit_code_in] = ip_port_in; + + llinfos << "Circuit code " << circuit_code_in << " from " + << msg->getSender() << " for agent " << id << " in session " + << session_id << llendl; + } + else + { + llwarns << "Got zero circuit code in use_circuit_code" << llendl; + } +} + + + +static void check_for_unrecognized_messages( + const char* type, + const LLSD& map, + LLMessageSystem::message_template_name_map_t& templates) +{ + for (LLSD::map_const_iterator iter = map.beginMap(), + end = map.endMap(); + iter != end; ++iter) + { + const char* name = gMessageStringTable.getString(iter->first.c_str()); + + if (templates.find(name) == templates.end()) + { + llinfos << " " << type + << " ban list contains unrecognized message " + << name << llendl; + } + } +} + +void LLMessageSystem::setMessageBans( + const LLSD& trusted, const LLSD& untrusted) +{ + llinfos << "LLMessageSystem::setMessageBans:" << llendl; + bool any_set = false; + + for (message_template_name_map_t::iterator iter = mMessageTemplates.begin(), + end = mMessageTemplates.end(); + iter != end; ++iter) + { + LLMessageTemplate* mt = iter->second; + + std::string name(mt->mName); + bool ban_from_trusted + = trusted.has(name) && trusted.get(name).asBoolean(); + bool ban_from_untrusted + = untrusted.has(name) && untrusted.get(name).asBoolean(); + + mt->mBanFromTrusted = ban_from_trusted; + mt->mBanFromUntrusted = ban_from_untrusted; + + if (ban_from_trusted || ban_from_untrusted) + { + llinfos << " " << name << " banned from " + << (ban_from_trusted ? "TRUSTED " : " ") + << (ban_from_untrusted ? "UNTRUSTED " : " ") + << llendl; + any_set = true; + } + } + + if (!any_set) + { + llinfos << " no messages banned" << llendl; + } + + check_for_unrecognized_messages("trusted", trusted, mMessageTemplates); + check_for_unrecognized_messages("untrusted", untrusted, mMessageTemplates); +} + +void process_packet_ack(LLMessageSystem *msgsystem, void** /*user_data*/) +{ + TPACKETID packet_id; + + LLHost host = msgsystem->getSender(); + LLCircuitData *cdp = msgsystem->mCircuitInfo.findCircuit(host); + if (cdp) + { + + S32 ack_count = msgsystem->getNumberOfBlocksFast(_PREHASH_Packets); + + for (S32 i = 0; i < ack_count; i++) + { + msgsystem->getU32Fast(_PREHASH_Packets, _PREHASH_ID, packet_id, i); +// llinfos << "ack recvd' from " << host << " for packet " << (TPACKETID)packet_id << llendl; + cdp->ackReliablePacket(packet_id); + } + if (!cdp->getUnackedPacketCount()) + { + // Remove this circuit from the list of circuits with unacked packets + gMessageSystem->mCircuitInfo.mUnackedCircuitMap.erase(host); + } + } +} + +void send_template_reply(LLMessageSystem* msg, const LLUUID& token) +{ + msg->newMessageFast(_PREHASH_TemplateChecksumReply); + msg->nextBlockFast(_PREHASH_DataBlock); + msg->addU32Fast(_PREHASH_Checksum, msg->mMessageFileChecksum); + msg->addU8Fast(_PREHASH_MajorVersion, U8(msg->mSystemVersionMajor) ); + msg->addU8Fast(_PREHASH_MinorVersion, U8(msg->mSystemVersionMinor) ); + msg->addU8Fast(_PREHASH_PatchVersion, U8(msg->mSystemVersionPatch) ); + msg->addU8Fast(_PREHASH_ServerVersion, U8(msg->mSystemVersionServer) ); + msg->addU32Fast(_PREHASH_Flags, msg->mVersionFlags); + msg->nextBlockFast(_PREHASH_TokenBlock); + msg->addUUIDFast(_PREHASH_Token, token); + msg->sendMessage(msg->getSender()); +} + +void process_template_checksum_request(LLMessageSystem* msg, void**) +{ + llinfos << "Message template checksum request received from " + << msg->getSender() << llendl; + send_template_reply(msg, LLUUID::null); +} + +void process_secured_template_checksum_request(LLMessageSystem* msg, void**) +{ + llinfos << "Secured message template checksum request received from " + << msg->getSender() << llendl; + LLUUID token; + msg->getUUIDFast(_PREHASH_TokenBlock, _PREHASH_Token, token); + send_template_reply(msg, token); +} + +void process_log_control(LLMessageSystem* msg, void**) +{ + U8 level; + U32 mask; + BOOL time; + BOOL location; + BOOL remote_infos; + + msg->getU8Fast(_PREHASH_Options, _PREHASH_Level, level); + msg->getU32Fast(_PREHASH_Options, _PREHASH_Mask, mask); + msg->getBOOLFast(_PREHASH_Options, _PREHASH_Time, time); + msg->getBOOLFast(_PREHASH_Options, _PREHASH_Location, location); + msg->getBOOLFast(_PREHASH_Options, _PREHASH_RemoteInfos, remote_infos); + + gErrorStream.setLevel(LLErrorStream::ELevel(level)); + gErrorStream.setDebugMask(mask); + gErrorStream.setTime(time); + gErrorStream.setPrintLocation(location); + gErrorStream.setElevatedRemote(remote_infos); + + llinfos << "Logging set to level " << gErrorStream.getLevel() + << " mask " << std::hex << gErrorStream.getDebugMask() << std::dec + << " time " << gErrorStream.getTime() + << " loc " << gErrorStream.getPrintLocation() + << llendl; +} + +void process_log_messages(LLMessageSystem* msg, void**) +{ + U8 log_message; + + msg->getU8Fast(_PREHASH_Options, _PREHASH_Enable, log_message); + + if (log_message) + { + llinfos << "Starting logging via message" << llendl; + msg->startLogging(); + } + else + { + llinfos << "Stopping logging via message" << llendl; + msg->stopLogging(); + } +} + +// Make circuit trusted if the MD5 Digest matches, otherwise +// notify remote end that they are not trusted. +void process_create_trusted_circuit(LLMessageSystem *msg, void **) +{ + // don't try to create trust on machines with no shared secret + std::string shared_secret = get_shared_secret(); + if(shared_secret.empty()) return; + + LLUUID remote_id; + msg->getUUIDFast(_PREHASH_DataBlock, _PREHASH_EndPointID, remote_id); + + LLCircuitData *cdp = msg->mCircuitInfo.findCircuit(msg->getSender()); + if (!cdp) + { + llwarns << "Attempt to create trusted circuit without circuit data: " + << msg->getSender() << llendl; + return; + } + + LLUUID local_id; + local_id = cdp->getLocalEndPointID(); + if (remote_id == local_id) + { + // Don't respond to requests that use the same end point ID + return; + } + + char their_digest[MD5HEX_STR_SIZE]; + msg->getBinaryDataFast(_PREHASH_DataBlock, _PREHASH_Digest, their_digest, 32); + their_digest[MD5HEX_STR_SIZE - 1] = '\0'; + if(msg->isMatchingDigestForWindowAndUUIDs(their_digest, TRUST_TIME_WINDOW, local_id, remote_id)) + { + cdp->setTrusted(TRUE); + llinfos << "Trusted digest from " << msg->getSender() << llendl; + return; + } + else if (cdp->getTrusted()) + { + // The digest is bad, but this circuit is already trusted. + // This means that this could just be the result of a stale deny sent from a while back, and + // the message system is being slow. Don't bother sending the deny, as it may continually + // ping-pong back and forth on a very hosed circuit. + llwarns << "Ignoring bad digest from known trusted circuit: " << their_digest + << " host: " << msg->getSender() << llendl; + return; + } + else + { + llwarns << "Bad digest from known circuit: " << their_digest + << " host: " << msg->getSender() << llendl; + msg->sendDenyTrustedCircuit(msg->getSender()); + return; + } +} + +void process_deny_trusted_circuit(LLMessageSystem *msg, void **) +{ + // don't try to create trust on machines with no shared secret + std::string shared_secret = get_shared_secret(); + if(shared_secret.empty()) return; + + LLUUID remote_id; + msg->getUUIDFast(_PREHASH_DataBlock, _PREHASH_EndPointID, remote_id); + + LLCircuitData *cdp = msg->mCircuitInfo.findCircuit(msg->getSender()); + if (!cdp) + { + return; + } + + LLUUID local_id; + local_id = cdp->getLocalEndPointID(); + if (remote_id == local_id) + { + // Don't respond to requests that use the same end point ID + return; + } + + // Assume that we require trust to proceed, so resend. + // This catches the case where a circuit that was trusted + // times out, and allows us to re-establish it, but does + // mean that if our shared_secret or clock is wrong, we'll + // spin. + // FIXME: probably should keep a count of number of resends + // per circuit, and stop resending after a while. + llinfos << "Got DenyTrustedCircuit. Sending CreateTrustedCircuit to " + << msg->getSender() << llendl; + msg->sendCreateTrustedCircuit(msg->getSender(), local_id, remote_id); +} + +#define LL_ENCRYPT_BUF_LENGTH 16384 + +void encrypt_template(const char *src_name, const char *dest_name) +{ + // encrypt and decrypt are symmetric + decrypt_template(src_name, dest_name); +} + +BOOL decrypt_template(const char *src_name, const char *dest_name) +{ + S32 buf_length = LL_ENCRYPT_BUF_LENGTH; + char buf[LL_ENCRYPT_BUF_LENGTH]; + + FILE* infp = NULL; + FILE* outfp = NULL; + BOOL success = FALSE; + char* bufp = NULL; + U32 key = 0; + S32 more_data = 0; + + if(src_name==NULL) + { + llwarns << "Input src_name is NULL!!" << llendl; + goto exit; + } + + infp = LLFile::fopen(src_name,"rb"); + if (!infp) + { + llwarns << "could not open " << src_name << " for reading" << llendl; + goto exit; + } + + if(dest_name==NULL) + { + llwarns << "Output dest_name is NULL!!" << llendl; + goto exit; + } + + outfp = LLFile::fopen(dest_name,"w+b"); + if (!outfp) + { + llwarns << "could not open " << src_name << " for writing" << llendl; + goto exit; + } + + while ((buf_length = (S32)fread(buf,1,LL_ENCRYPT_BUF_LENGTH,infp))) + { + // unscrozzle bits here + bufp = buf; + more_data = buf_length; + while (more_data--) + { + *bufp = *bufp ^ ((key * 43) % 256); + key++; + bufp++; + } + + if(buf_length != (S32)fwrite(buf,1,buf_length,outfp)) + { + goto exit; + } + } + success = TRUE; + + exit: + if(infp) fclose(infp); + if(outfp) fclose(outfp); + return success; +} + +void dump_prehash_files() +{ + U32 i; + FILE *fp = LLFile::fopen("../../indra/llmessage/message_prehash.h", "w"); + if (fp) + { + fprintf( + fp, + "/**\n" + " * @file message_prehash.h\n" + " * @brief header file of externs of prehashed variables plus defines.\n" + " *\n" + " * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc.\n" + " * $License$\n" + " */\n\n" + "#ifndef LL_MESSAGE_PREHASH_H\n#define LL_MESSAGE_PREHASH_H\n\n"); + fprintf( + fp, + "/**\n" + " * Generated from message template version number %.3f\n" + " */\n", + gMessageSystem->mMessageFileVersionNumber); + fprintf(fp, "\n\nextern F32 gPrehashVersionNumber;\n\n"); + for (i = 0; i < MESSAGE_NUMBER_OF_HASH_BUCKETS; i++) + { + if (!gMessageStringTable.mEmpty[i] && gMessageStringTable.mString[i][0] != '.') + { + fprintf(fp, "extern char * _PREHASH_%s;\n", gMessageStringTable.mString[i]); + } + } + fprintf(fp, "\n\nvoid init_prehash_data();\n\n"); + fprintf(fp, "\n\n"); + fprintf(fp, "\n\n#endif\n"); + fclose(fp); + } + fp = LLFile::fopen("../../indra/llmessage/message_prehash.cpp", "w"); + if (fp) + { + fprintf( + fp, + "/**\n" + " * @file message_prehash.cpp\n" + " * @brief file of prehashed variables\n" + " *\n" + " * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc.\n" + " * $License$\n" + " */\n\n" + "/**\n" + " * Generated from message template version number %.3f\n" + " */\n", + gMessageSystem->mMessageFileVersionNumber); + fprintf(fp, "#include \"linden_common.h\"\n"); + fprintf(fp, "#include \"message.h\"\n\n"); + fprintf(fp, "\n\nF32 gPrehashVersionNumber = %.3ff;\n\n", gMessageSystem->mMessageFileVersionNumber); + for (i = 0; i < MESSAGE_NUMBER_OF_HASH_BUCKETS; i++) + { + if (!gMessageStringTable.mEmpty[i] && gMessageStringTable.mString[i][0] != '.') + { + fprintf(fp, "char * _PREHASH_%s;\n", gMessageStringTable.mString[i]); + } + } + fprintf(fp, "\nvoid init_prehash_data()\n"); + fprintf(fp, "{\n"); + for (i = 0; i < MESSAGE_NUMBER_OF_HASH_BUCKETS; i++) + { + if (!gMessageStringTable.mEmpty[i] && gMessageStringTable.mString[i][0] != '.') + { + fprintf(fp, "\t_PREHASH_%s = gMessageStringTable.getString(\"%s\");\n", gMessageStringTable.mString[i], gMessageStringTable.mString[i]); + } + } + fprintf(fp, "}\n"); + fclose(fp); + } +} + +BOOL start_messaging_system( + const std::string& template_name, + U32 port, + S32 version_major, + S32 version_minor, + S32 version_patch, + BOOL b_dump_prehash_file, + const std::string& secret) +{ + gMessageSystem = new LLMessageSystem( + template_name.c_str(), + port, + version_major, + version_minor, + version_patch); + g_shared_secret.assign(secret); + + if (!gMessageSystem) + { + llerrs << "Messaging system initialization failed." << llendl; + return FALSE; + } + + // bail if system encountered an error. + if(!gMessageSystem->isOK()) + { + return FALSE; + } + + if (b_dump_prehash_file) + { + dump_prehash_files(); + exit(0); + } + else + { + init_prehash_data(); + if (gMessageSystem->mMessageFileVersionNumber != gPrehashVersionNumber) + { + llinfos << "Message template version does not match prehash version number" << llendl; + llinfos << "Run simulator with -prehash command line option to rebuild prehash data" << llendl; + } + else + { + llinfos << "Message template version matches prehash version number" << llendl; + } + } + + gMessageSystem->setHandlerFuncFast(_PREHASH_StartPingCheck, process_start_ping_check, NULL); + gMessageSystem->setHandlerFuncFast(_PREHASH_CompletePingCheck, process_complete_ping_check, NULL); + gMessageSystem->setHandlerFuncFast(_PREHASH_OpenCircuit, open_circuit, NULL); + gMessageSystem->setHandlerFuncFast(_PREHASH_CloseCircuit, close_circuit, NULL); + + //gMessageSystem->setHandlerFuncFast(_PREHASH_AssignCircuitCode, LLMessageSystem::processAssignCircuitCode); + gMessageSystem->setHandlerFuncFast(_PREHASH_AddCircuitCode, LLMessageSystem::processAddCircuitCode); + //gMessageSystem->setHandlerFuncFast(_PREHASH_AckAddCircuitCode, ack_add_circuit_code, NULL); + gMessageSystem->setHandlerFuncFast(_PREHASH_UseCircuitCode, LLMessageSystem::processUseCircuitCode); + gMessageSystem->setHandlerFuncFast(_PREHASH_PacketAck, process_packet_ack, NULL); + gMessageSystem->setHandlerFuncFast(_PREHASH_TemplateChecksumRequest, process_template_checksum_request, NULL); + gMessageSystem->setHandlerFuncFast(_PREHASH_SecuredTemplateChecksumRequest, process_secured_template_checksum_request, NULL); + gMessageSystem->setHandlerFuncFast(_PREHASH_LogControl, process_log_control, NULL); + gMessageSystem->setHandlerFuncFast(_PREHASH_LogMessages, process_log_messages, NULL); + gMessageSystem->setHandlerFuncFast(_PREHASH_CreateTrustedCircuit, + process_create_trusted_circuit, + NULL); + gMessageSystem->setHandlerFuncFast(_PREHASH_DenyTrustedCircuit, + process_deny_trusted_circuit, + NULL); + + // We can hand this to the null_message_callback since it is a + // trusted message, so it will automatically be denied if it isn't + // trusted and ignored if it is -- exactly what we want. + gMessageSystem->setHandlerFunc( + "RequestTrustedCircuit", + null_message_callback, + NULL); + + // Initialize the transfer manager + gTransferManager.init(); + + return TRUE; +} + +void LLMessageSystem::startLogging() +{ + mVerboseLog = TRUE; + std::ostringstream str; + str << "START MESSAGE LOG" << std::endl; + str << "Legend:" << std::endl; + str << "\t<-\tincoming message" <\toutgoing message" << std::endl; + str << " <> host size zero id name"; + llinfos << str.str() << llendl; +} + +void LLMessageSystem::stopLogging() +{ + if(mVerboseLog) + { + mVerboseLog = FALSE; + llinfos << "END MESSAGE LOG" << llendl; + } +} + +void LLMessageSystem::summarizeLogs(std::ostream& str) +{ + char buffer[MAX_STRING]; /* Flawfinder: ignore */ + char tmp_str[MAX_STRING]; /* Flawfinder: ignore */ + F32 run_time = mMessageSystemTimer.getElapsedTimeF32(); + str << "START MESSAGE LOG SUMMARY" << std::endl; + snprintf(buffer, MAX_STRING, "Run time: %12.3f seconds", run_time); /* Flawfinder: ignore */ + + // Incoming + str << buffer << std::endl << "Incoming:" << std::endl; + U64_to_str(mTotalBytesIn, tmp_str, sizeof(tmp_str)); + snprintf(buffer, MAX_STRING, "Total bytes received: %20s (%5.2f kbits per second)", tmp_str, ((F32)mTotalBytesIn * 0.008f) / run_time); /* Flawfinder: ignore */ + str << buffer << std::endl; + U64_to_str(mPacketsIn, tmp_str, sizeof(tmp_str)); + snprintf(buffer, MAX_STRING, "Total packets received: %20s (%5.2f packets per second)", tmp_str, ((F32) mPacketsIn / run_time)); /* Flawfinder: ignore */ + str << buffer << std::endl; + snprintf(buffer, MAX_STRING, "Average packet size: %20.0f bytes", (F32)mTotalBytesIn / (F32)mPacketsIn); /* Flawfinder: ignore */ + str << buffer << std::endl; + U64_to_str(mReliablePacketsIn, tmp_str, sizeof(tmp_str)); + snprintf(buffer, MAX_STRING, "Total reliable packets: %20s (%5.2f%%)", tmp_str, 100.f * ((F32) mReliablePacketsIn)/((F32) mPacketsIn + 1)); /* Flawfinder: ignore */ + str << buffer << std::endl; + U64_to_str(mCompressedPacketsIn, tmp_str, sizeof(tmp_str)); + snprintf(buffer, MAX_STRING, "Total compressed packets: %20s (%5.2f%%)", tmp_str, 100.f * ((F32) mCompressedPacketsIn)/((F32) mPacketsIn + 1)); /* Flawfinder: ignore */ + str << buffer << std::endl; + S64 savings = mUncompressedBytesIn - mCompressedBytesIn; + U64_to_str(savings, tmp_str, sizeof(tmp_str)); + snprintf(buffer, MAX_STRING, "Total compression savings: %20s bytes", tmp_str); /* Flawfinder: ignore */ + str << buffer << std::endl; + U64_to_str(savings/(mCompressedPacketsIn +1), tmp_str, sizeof(tmp_str)); + snprintf(buffer, MAX_STRING, "Avg comp packet savings: %20s (%5.2f : 1)", tmp_str, ((F32) mUncompressedBytesIn)/((F32) mCompressedBytesIn+1)); /* Flawfinder: ignore */ + str << buffer << std::endl; + U64_to_str(savings/(mPacketsIn+1), tmp_str, sizeof(tmp_str)); + snprintf(buffer, MAX_STRING, "Avg overall comp savings: %20s (%5.2f : 1)", tmp_str, ((F32) mTotalBytesIn + (F32) savings)/((F32) mTotalBytesIn + 1.f)); /* Flawfinder: ignore */ + + // Outgoing + str << buffer << std::endl << std::endl << "Outgoing:" << std::endl; + U64_to_str(mTotalBytesOut, tmp_str, sizeof(tmp_str)); + snprintf(buffer, MAX_STRING, "Total bytes sent: %20s (%5.2f kbits per second)", tmp_str, ((F32)mTotalBytesOut * 0.008f) / run_time ); /* Flawfinder: ignore */ + str << buffer << std::endl; + U64_to_str(mPacketsOut, tmp_str, sizeof(tmp_str)); + snprintf(buffer, MAX_STRING, "Total packets sent: %20s (%5.2f packets per second)", tmp_str, ((F32)mPacketsOut / run_time)); /* Flawfinder: ignore */ + str << buffer << std::endl; + snprintf(buffer, MAX_STRING, "Average packet size: %20.0f bytes", (F32)mTotalBytesOut / (F32)mPacketsOut); /* Flawfinder: ignore */ + str << buffer << std::endl; + U64_to_str(mReliablePacketsOut, tmp_str, sizeof(tmp_str)); + snprintf(buffer, MAX_STRING, "Total reliable packets: %20s (%5.2f%%)", tmp_str, 100.f * ((F32) mReliablePacketsOut)/((F32) mPacketsOut + 1)); /* Flawfinder: ignore */ + str << buffer << std::endl; + U64_to_str(mCompressedPacketsOut, tmp_str, sizeof(tmp_str)); + snprintf(buffer, MAX_STRING, "Total compressed packets: %20s (%5.2f%%)", tmp_str, 100.f * ((F32) mCompressedPacketsOut)/((F32) mPacketsOut + 1)); /* Flawfinder: ignore */ + str << buffer << std::endl; + savings = mUncompressedBytesOut - mCompressedBytesOut; + U64_to_str(savings, tmp_str, sizeof(tmp_str)); + snprintf(buffer, MAX_STRING, "Total compression savings: %20s bytes", tmp_str); /* Flawfinder: ignore */ + str << buffer << std::endl; + U64_to_str(savings/(mCompressedPacketsOut +1), tmp_str, sizeof(tmp_str)); + snprintf(buffer, MAX_STRING, "Avg comp packet savings: %20s (%5.2f : 1)", tmp_str, ((F32) mUncompressedBytesOut)/((F32) mCompressedBytesOut+1)); /* Flawfinder: ignore */ + str << buffer << std::endl; + U64_to_str(savings/(mPacketsOut+1), tmp_str, sizeof(tmp_str)); + snprintf(buffer, MAX_STRING, "Avg overall comp savings: %20s (%5.2f : 1)", tmp_str, ((F32) mTotalBytesOut + (F32) savings)/((F32) mTotalBytesOut + 1.f)); /* Flawfinder: ignore */ + str << buffer << std::endl << std::endl; + snprintf(buffer, MAX_STRING, "SendPacket failures: %20d", mSendPacketFailureCount); /* Flawfinder: ignore */ + str << buffer << std::endl; + snprintf(buffer, MAX_STRING, "Dropped packets: %20d", mDroppedPackets); /* Flawfinder: ignore */ + str << buffer << std::endl; + snprintf(buffer, MAX_STRING, "Resent packets: %20d", mResentPackets); /* Flawfinder: ignore */ + str << buffer << std::endl; + snprintf(buffer, MAX_STRING, "Failed reliable resends: %20d", mFailedResendPackets); /* Flawfinder: ignore */ + str << buffer << std::endl; + snprintf(buffer, MAX_STRING, "Off-circuit rejected packets: %17d", mOffCircuitPackets); /* Flawfinder: ignore */ + str << buffer << std::endl; + snprintf(buffer, MAX_STRING, "On-circuit invalid packets: %17d", mInvalidOnCircuitPackets); /* Flawfinder: ignore */ + str << buffer << std::endl << std::endl; + + str << "Decoding: " << std::endl; + snprintf(buffer, MAX_STRING, "%35s%10s%10s%10s%10s", "Message", "Count", "Time", "Max", "Avg"); /* Flawfinder: ignore */ + str << buffer << std:: endl; + F32 avg; + for (message_template_name_map_t::iterator iter = mMessageTemplates.begin(), + end = mMessageTemplates.end(); + iter != end; iter++) + { + LLMessageTemplate* mt = iter->second; + if(mt->mTotalDecoded > 0) + { + avg = mt->mTotalDecodeTime / (F32)mt->mTotalDecoded; + snprintf(buffer, MAX_STRING, "%35s%10u%10f%10f%10f", mt->mName, mt->mTotalDecoded, mt->mTotalDecodeTime, mt->mMaxDecodeTimePerMsg, avg); /* Flawfinder: ignore */ + str << buffer << std::endl; + } + } + str << "END MESSAGE LOG SUMMARY" << std::endl; +} + +void end_messaging_system() +{ + gTransferManager.cleanup(); + LLTransferTargetVFile::updateQueue(true); // shutdown LLTransferTargetVFile + if (gMessageSystem) + { + gMessageSystem->stopLogging(); + + std::ostringstream str; + gMessageSystem->summarizeLogs(str); + llinfos << str.str().c_str() << llendl; + + delete gMessageSystem; + gMessageSystem = NULL; + } +} + +void LLMessageSystem::resetReceiveCounts() +{ + mNumMessageCounts = 0; + + for (message_template_name_map_t::iterator iter = mMessageTemplates.begin(), + end = mMessageTemplates.end(); + iter != end; iter++) + { + LLMessageTemplate* mt = iter->second; + mt->mDecodeTimeThisFrame = 0.f; + } +} + + +void LLMessageSystem::dumpReceiveCounts() +{ + LLMessageTemplate *mt; + + for (message_template_name_map_t::iterator iter = mMessageTemplates.begin(), + end = mMessageTemplates.end(); + iter != end; iter++) + { + LLMessageTemplate* mt = iter->second; + mt->mReceiveCount = 0; + mt->mReceiveBytes = 0; + mt->mReceiveInvalid = 0; + } + + S32 i; + for (i = 0; i < mNumMessageCounts; i++) + { + mt = get_ptr_in_map(mMessageNumbers,mMessageCountList[i].mMessageNum); + if (mt) + { + mt->mReceiveCount++; + mt->mReceiveBytes += mMessageCountList[i].mMessageBytes; + if (mMessageCountList[i].mInvalid) + { + mt->mReceiveInvalid++; + } + } + } + + if(mNumMessageCounts > 0) + { + llinfos << "Dump: " << mNumMessageCounts << " messages processed in " << mReceiveTime << " seconds" << llendl; + for (message_template_name_map_t::iterator iter = mMessageTemplates.begin(), + end = mMessageTemplates.end(); + iter != end; iter++) + { + LLMessageTemplate* mt = iter->second; + if (mt->mReceiveCount > 0) + { + llinfos << "Num: " << std::setw(3) << mt->mReceiveCount << " Bytes: " << std::setw(6) << mt->mReceiveBytes + << " Invalid: " << std::setw(3) << mt->mReceiveInvalid << " " << mt->mName << " " << llround(100 * mt->mDecodeTimeThisFrame / mReceiveTime) << "%" << llendl; + } + } + } +} + + + +BOOL LLMessageSystem::isClear() const +{ + return mbSClear; +} + + +S32 LLMessageSystem::flush(const LLHost &host) +{ + if (mCurrentSendTotal) + { + S32 sentbytes = sendMessage(host); + clearMessage(); + return sentbytes; + } + else + { + return 0; + } +} + +U32 LLMessageSystem::getListenPort( void ) const +{ + return mPort; +} + + +S32 LLMessageSystem::zeroCode(U8 **data, S32 *data_size) +{ + S32 count = *data_size; + + S32 net_gain = 0; + U8 num_zeroes = 0; + + U8 *inptr = (U8 *)*data; + U8 *outptr = (U8 *)mEncodedSendBuffer; + +// skip the packet id field + + for (U32 i=0;i256 zeroes) + + while (count--) + { + if (!(*inptr)) // in a zero count + { + if (num_zeroes) + { + if (++num_zeroes > 254) + { + *outptr++ = num_zeroes; + num_zeroes = 0; + } + net_gain--; // subseqent zeroes save one + } + else + { + *outptr++ = 0; + net_gain++; // starting a zero count adds one + num_zeroes = 1; + } + inptr++; + } + else + { + if (num_zeroes) + { + *outptr++ = num_zeroes; + num_zeroes = 0; + } + *outptr++ = *inptr++; + } + } + + if (num_zeroes) + { + *outptr++ = num_zeroes; + } + + if (net_gain < 0) + { + mCompressedPacketsOut++; + mUncompressedBytesOut += *data_size; + + *data = mEncodedSendBuffer; + *data_size += net_gain; + mEncodedSendBuffer[0] |= LL_ZERO_CODE_FLAG; // set the head bit to indicate zero coding + + mCompressedBytesOut += *data_size; + + } + mTotalBytesOut += *data_size; + + return(net_gain); +} + +S32 LLMessageSystem::zeroCodeAdjustCurrentSendTotal() +{ + if (!mbSBuilt) + { + buildMessage(); + } + mbSBuilt = FALSE; + + S32 count = mSendSize; + + S32 net_gain = 0; + U8 num_zeroes = 0; + + U8 *inptr = (U8 *)mSendBuffer; + +// skip the packet id field + + for (U32 i=0;i256 zeroes) + + while (count--) + { + if (!(*inptr)) // in a zero count + { + if (num_zeroes) + { + if (++num_zeroes > 254) + { + num_zeroes = 0; + } + net_gain--; // subseqent zeroes save one + } + else + { + net_gain++; // starting a zero count adds one + num_zeroes = 1; + } + inptr++; + } + else + { + if (num_zeroes) + { + num_zeroes = 0; + } + inptr++; + } + } + if (net_gain < 0) + { + return net_gain; + } + else + { + return 0; + } +} + + + +S32 LLMessageSystem::zeroCodeExpand(U8 **data, S32 *data_size) +{ + + if ((*data_size ) < LL_PACKET_ID_SIZE) + { + llwarns << "zeroCodeExpand() called with data_size of " << *data_size << llendl; + } + + mTotalBytesIn += *data_size; + + if (!(*data[0] & LL_ZERO_CODE_FLAG)) // if we're not zero-coded, just go 'way + { + return(0); + } + + S32 in_size = *data_size; + mCompressedPacketsIn++; + mCompressedBytesIn += *data_size; + + *data[0] &= (~LL_ZERO_CODE_FLAG); + + S32 count = (*data_size); + + U8 *inptr = (U8 *)*data; + U8 *outptr = (U8 *)mEncodedRecvBuffer; + +// skip the packet id field + + for (U32 i=0;i256 zeroes) + + while (count--) + { + if (outptr > (&mEncodedRecvBuffer[MAX_BUFFER_SIZE-1])) + { + llwarns << "attempt to write past reasonable encoded buffer size 1" << llendl; + callExceptionFunc(MX_WROTE_PAST_BUFFER_SIZE); + outptr = mEncodedRecvBuffer; + break; + } + if (!((*outptr++ = *inptr++))) + { + while (((count--)) && (!(*inptr))) + { + *outptr++ = *inptr++; + if (outptr > (&mEncodedRecvBuffer[MAX_BUFFER_SIZE-256])) + { + llwarns << "attempt to write past reasonable encoded buffer size 2" << llendl; + callExceptionFunc(MX_WROTE_PAST_BUFFER_SIZE); + outptr = mEncodedRecvBuffer; + count = -1; + break; + } + memset(outptr,0,255); + outptr += 255; + } + + if (count < 0) + { + break; + } + + else + { + if (outptr > (&mEncodedRecvBuffer[MAX_BUFFER_SIZE-(*inptr)])) + { + llwarns << "attempt to write past reasonable encoded buffer size 3" << llendl; + callExceptionFunc(MX_WROTE_PAST_BUFFER_SIZE); + outptr = mEncodedRecvBuffer; + } + memset(outptr,0,(*inptr) - 1); + outptr += ((*inptr) - 1); + inptr++; + } + } + } + + *data = mEncodedRecvBuffer; + *data_size = (S32)(outptr - mEncodedRecvBuffer); + mUncompressedBytesIn += *data_size; + + return(in_size); +} + + +void LLMessageSystem::addTemplate(LLMessageTemplate *templatep) +{ + if (mMessageTemplates.count(templatep->mName) > 0) + { + llerrs << templatep->mName << " already used as a template name!" + << llendl; + } + mMessageTemplates[templatep->mName] = templatep; + mMessageNumbers[templatep->mMessageNumber] = templatep; +} + + +void LLMessageSystem::setHandlerFuncFast(const char *name, void (*handler_func)(LLMessageSystem *msgsystem, void **user_data), void **user_data) +{ + LLMessageTemplate* msgtemplate = get_ptr_in_map(mMessageTemplates, name); + if (msgtemplate) + { + msgtemplate->setHandlerFunc(handler_func, user_data); + } + else + { + llerrs << name << " is not a known message name!" << llendl; + } +} + + +bool LLMessageSystem::callHandler(const char *name, + bool trustedSource, LLMessageSystem* msg) +{ + name = gMessageStringTable.getString(name); + LLMessageTemplate* msg_template = mMessageTemplates[(char*)name]; + if (!msg_template) + { + llwarns << "LLMessageSystem::callHandler: unknown message " + << name << llendl; + return false; + } + + if (msg_template->isBanned(trustedSource)) + { + llwarns << "LLMessageSystem::callHandler: banned message " + << name + << " from " + << (trustedSource ? "trusted " : "untrusted ") + << "source" << llendl; + return false; + } + + return msg_template->callHandlerFunc(msg); +} + + +void LLMessageSystem::setExceptionFunc(EMessageException e, + msg_exception_callback func, + void* data) +{ + callbacks_t::iterator it = mExceptionCallbacks.find(e); + if(it != mExceptionCallbacks.end()) + { + mExceptionCallbacks.erase(it); + } + if(func) + { + mExceptionCallbacks.insert(callbacks_t::value_type(e, exception_t(func, data))); + } +} + +BOOL LLMessageSystem::callExceptionFunc(EMessageException exception) +{ + callbacks_t::iterator it = mExceptionCallbacks.find(exception); + if(it != mExceptionCallbacks.end()) + { + ((*it).second.first)(this, (*it).second.second,exception); + return TRUE; + } + return FALSE; +} + +BOOL LLMessageSystem::isCircuitCodeKnown(U32 code) const +{ + if(mCircuitCodes.find(code) == mCircuitCodes.end()) + return FALSE; + return TRUE; +} + +BOOL LLMessageSystem::isMessageFast(const char *msg) +{ + if (mCurrentRMessageTemplate) + { + return(msg == mCurrentRMessageTemplate->mName); + } + else + { + return FALSE; + } +} + + +char* LLMessageSystem::getMessageName() +{ + if (mCurrentRMessageTemplate) + { + return mCurrentRMessageTemplate->mName; + } + else + { + return NULL; + } +} + +const LLUUID& LLMessageSystem::getSenderID() const +{ + LLCircuitData *cdp = mCircuitInfo.findCircuit(mLastSender); + if (cdp) + { + return (cdp->mRemoteID); + } + + return LLUUID::null; +} + +const LLUUID& LLMessageSystem::getSenderSessionID() const +{ + LLCircuitData *cdp = mCircuitInfo.findCircuit(mLastSender); + if (cdp) + { + return (cdp->mRemoteSessionID); + } + return LLUUID::null; +} + +void LLMessageSystem::addVector3Fast(const char *varname, const LLVector3& vec) +{ + addDataFast(varname, vec.mV, MVT_LLVector3, sizeof(vec.mV)); +} + +void LLMessageSystem::addVector3(const char *varname, const LLVector3& vec) +{ + addDataFast(gMessageStringTable.getString(varname), vec.mV, MVT_LLVector3, sizeof(vec.mV)); +} + +void LLMessageSystem::addVector4Fast(const char *varname, const LLVector4& vec) +{ + addDataFast(varname, vec.mV, MVT_LLVector4, sizeof(vec.mV)); +} + +void LLMessageSystem::addVector4(const char *varname, const LLVector4& vec) +{ + addDataFast(gMessageStringTable.getString(varname), vec.mV, MVT_LLVector4, sizeof(vec.mV)); +} + + +void LLMessageSystem::addVector3dFast(const char *varname, const LLVector3d& vec) +{ + addDataFast(varname, vec.mdV, MVT_LLVector3d, sizeof(vec.mdV)); +} + +void LLMessageSystem::addVector3d(const char *varname, const LLVector3d& vec) +{ + addDataFast(gMessageStringTable.getString(varname), vec.mdV, MVT_LLVector3d, sizeof(vec.mdV)); +} + + +void LLMessageSystem::addQuatFast(const char *varname, const LLQuaternion& quat) +{ + addDataFast(varname, quat.packToVector3().mV, MVT_LLQuaternion, sizeof(LLVector3)); +} + +void LLMessageSystem::addQuat(const char *varname, const LLQuaternion& quat) +{ + addDataFast(gMessageStringTable.getString(varname), quat.packToVector3().mV, MVT_LLQuaternion, sizeof(LLVector3)); +} + + +void LLMessageSystem::addUUIDFast(const char *varname, const LLUUID& uuid) +{ + addDataFast(varname, uuid.mData, MVT_LLUUID, sizeof(uuid.mData)); +} + +void LLMessageSystem::addUUID(const char *varname, const LLUUID& uuid) +{ + addDataFast(gMessageStringTable.getString(varname), uuid.mData, MVT_LLUUID, sizeof(uuid.mData)); +} + +void LLMessageSystem::getF32Fast(const char *block, const char *var, F32 &d, S32 blocknum) +{ + getDataFast(block, var, &d, sizeof(F32), blocknum); + + if( !llfinite( d ) ) + { + llwarns << "non-finite in getF32Fast " << block << " " << var << llendl; + d = 0; + } +} + +void LLMessageSystem::getF32(const char *block, const char *var, F32 &d, S32 blocknum) +{ + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), &d, sizeof(F32), blocknum); + + if( !llfinite( d ) ) + { + llwarns << "non-finite in getF32 " << block << " " << var << llendl; + d = 0; + } +} + +void LLMessageSystem::getF64Fast(const char *block, const char *var, F64 &d, S32 blocknum) +{ + getDataFast(block, var, &d, sizeof(F64), blocknum); + + if( !llfinite( d ) ) + { + llwarns << "non-finite in getF64Fast " << block << " " << var << llendl; + d = 0; + } +} + +void LLMessageSystem::getF64(const char *block, const char *var, F64 &d, S32 blocknum) +{ + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), &d, sizeof(F64), blocknum); + + if( !llfinite( d ) ) + { + llwarns << "non-finite in getF64 " << block << " " << var << llendl; + d = 0; + } +} + + +void LLMessageSystem::getVector3Fast(const char *block, const char *var, LLVector3 &v, S32 blocknum ) +{ + getDataFast(block, var, v.mV, sizeof(v.mV), blocknum); + + if( !v.isFinite() ) + { + llwarns << "non-finite in getVector3Fast " << block << " " << var << llendl; + v.zeroVec(); + } +} + +void LLMessageSystem::getVector3(const char *block, const char *var, LLVector3 &v, S32 blocknum ) +{ + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), v.mV, sizeof(v.mV), blocknum); + + if( !v.isFinite() ) + { + llwarns << "non-finite in getVector4 " << block << " " << var << llendl; + v.zeroVec(); + } +} + +void LLMessageSystem::getVector4Fast(const char *block, const char *var, LLVector4 &v, S32 blocknum ) +{ + getDataFast(block, var, v.mV, sizeof(v.mV), blocknum); + + if( !v.isFinite() ) + { + llwarns << "non-finite in getVector4Fast " << block << " " << var << llendl; + v.zeroVec(); + } +} + +void LLMessageSystem::getVector4(const char *block, const char *var, LLVector4 &v, S32 blocknum ) +{ + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), v.mV, sizeof(v.mV), blocknum); + + if( !v.isFinite() ) + { + llwarns << "non-finite in getVector3 " << block << " " << var << llendl; + v.zeroVec(); + } +} + +void LLMessageSystem::getVector3dFast(const char *block, const char *var, LLVector3d &v, S32 blocknum ) +{ + getDataFast(block, var, v.mdV, sizeof(v.mdV), blocknum); + + if( !v.isFinite() ) + { + llwarns << "non-finite in getVector3dFast " << block << " " << var << llendl; + v.zeroVec(); + } + +} + +void LLMessageSystem::getVector3d(const char *block, const char *var, LLVector3d &v, S32 blocknum ) +{ + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), v.mdV, sizeof(v.mdV), blocknum); + + if( !v.isFinite() ) + { + llwarns << "non-finite in getVector3d " << block << " " << var << llendl; + v.zeroVec(); + } +} + +void LLMessageSystem::getQuatFast(const char *block, const char *var, LLQuaternion &q, S32 blocknum ) +{ + LLVector3 vec; + getDataFast(block, var, vec.mV, sizeof(vec.mV), blocknum); + if( vec.isFinite() ) + { + q.unpackFromVector3( vec ); + } + else + { + llwarns << "non-finite in getQuatFast " << block << " " << var << llendl; + q.loadIdentity(); + } +} + +void LLMessageSystem::getQuat(const char *block, const char *var, LLQuaternion &q, S32 blocknum ) +{ + LLVector3 vec; + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), vec.mV, sizeof(vec.mV), blocknum); + if( vec.isFinite() ) + { + q.unpackFromVector3( vec ); + } + else + { + llwarns << "non-finite in getQuat " << block << " " << var << llendl; + q.loadIdentity(); + } +} + +void LLMessageSystem::getUUIDFast(const char *block, const char *var, LLUUID &u, S32 blocknum ) +{ + getDataFast(block, var, u.mData, sizeof(u.mData), blocknum); +} + +void LLMessageSystem::getUUID(const char *block, const char *var, LLUUID &u, S32 blocknum ) +{ + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), u.mData, sizeof(u.mData), blocknum); +} + +bool LLMessageSystem::generateDigestForNumberAndUUIDs(char* digest, const U32 number, const LLUUID &id1, const LLUUID &id2) const +{ + const char *colon = ":"; + char tbuf[16]; /* Flawfinder: ignore */ + LLMD5 d; + LLString id1string = id1.getString(); + LLString id2string = id2.getString(); + std::string shared_secret = get_shared_secret(); + unsigned char * secret = (unsigned char*)shared_secret.c_str(); + unsigned char * id1str = (unsigned char*)id1string.c_str(); + unsigned char * id2str = (unsigned char*)id2string.c_str(); + + memset(digest, 0, MD5HEX_STR_SIZE); + + if( secret != NULL) + { + d.update(secret, (U32)strlen((char *) secret)); + } + + d.update((const unsigned char *) colon, (U32)strlen(colon)); /* Flawfinder: ignore */ + + snprintf(tbuf, sizeof(tbuf),"%i", number); /* Flawfinder: ignore */ + d.update((unsigned char *) tbuf, (U32)strlen(tbuf)); /* Flawfinder: ignore */ + + d.update((const unsigned char *) colon, (U32)strlen(colon)); /* Flawfinder: ignore */ + if( (char*) id1str != NULL) + { + d.update(id1str, (U32)strlen((char *) id1str)); + } + d.update((const unsigned char *) colon, (U32)strlen(colon)); /* Flawfinder: ignore */ + + if( (char*) id2str != NULL) + { + d.update(id2str, (U32)strlen((char *) id2str)); + } + + d.finalize(); + d.hex_digest(digest); + digest[MD5HEX_STR_SIZE - 1] = '\0'; + + return true; +} + +bool LLMessageSystem::generateDigestForWindowAndUUIDs(char* digest, const S32 window, const LLUUID &id1, const LLUUID &id2) const +{ + if(0 == window) return false; + std::string shared_secret = get_shared_secret(); + if(shared_secret.empty()) + { + llerrs << "Trying to generate complex digest on a machine without a shared secret!" << llendl; + } + + U32 now = time(NULL); + + now /= window; + + bool result = generateDigestForNumberAndUUIDs(digest, now, id1, id2); + + return result; +} + +bool LLMessageSystem::isMatchingDigestForWindowAndUUIDs(const char* digest, const S32 window, const LLUUID &id1, const LLUUID &id2) const +{ + if(0 == window) return false; + + std::string shared_secret = get_shared_secret(); + if(shared_secret.empty()) + { + llerrs << "Trying to compare complex digests on a machine without a shared secret!" << llendl; + } + + char our_digest[MD5HEX_STR_SIZE]; /* Flawfinder: ignore */ + U32 now = time(NULL); + + now /= window; + + // Check 1 window ago, now, and one window from now to catch edge + // conditions. Process them as current window, one window ago, and + // one window in the future to catch the edges. + const S32 WINDOW_BIN_COUNT = 3; + U32 window_bin[WINDOW_BIN_COUNT]; + window_bin[0] = now; + window_bin[1] = now - 1; + window_bin[2] = now + 1; + for(S32 i = 0; i < WINDOW_BIN_COUNT; ++i) + { + generateDigestForNumberAndUUIDs(our_digest, window_bin[i], id2, id1); + if(0 == strncmp(digest, our_digest, MD5HEX_STR_BYTES)) + { + return true; + } + } + return false; +} + +bool LLMessageSystem::generateDigestForNumber(char* digest, const U32 number) const +{ + memset(digest, 0, MD5HEX_STR_SIZE); + + LLMD5 d; + std::string shared_secret = get_shared_secret(); + d = LLMD5((const unsigned char *)shared_secret.c_str(), number); + d.hex_digest(digest); + digest[MD5HEX_STR_SIZE - 1] = '\0'; + + return true; +} + +bool LLMessageSystem::generateDigestForWindow(char* digest, const S32 window) const +{ + if(0 == window) return false; + + std::string shared_secret = get_shared_secret(); + if(shared_secret.empty()) + { + llerrs << "Trying to generate simple digest on a machine without a shared secret!" << llendl; + } + + U32 now = time(NULL); + + now /= window; + + bool result = generateDigestForNumber(digest, now); + + return result; +} + +bool LLMessageSystem::isMatchingDigestForWindow(const char* digest, S32 const window) const +{ + if(0 == window) return false; + + std::string shared_secret = get_shared_secret(); + if(shared_secret.empty()) + { + llerrs << "Trying to compare simple digests on a machine without a shared secret!" << llendl; + } + + char our_digest[MD5HEX_STR_SIZE]; /* Flawfinder: ignore */ + U32 now = (S32)time(NULL); + + now /= window; + + // Check 1 window ago, now, and one window from now to catch edge + // conditions. Process them as current window, one window ago, and + // one window in the future to catch the edges. + const S32 WINDOW_BIN_COUNT = 3; + U32 window_bin[WINDOW_BIN_COUNT]; + window_bin[0] = now; + window_bin[1] = now - 1; + window_bin[2] = now + 1; + for(S32 i = 0; i < WINDOW_BIN_COUNT; ++i) + { + generateDigestForNumber(our_digest, window_bin[i]); + if(0 == strncmp(digest, our_digest, MD5HEX_STR_BYTES)) + { + return true; + } + } + return false; +} + +void LLMessageSystem::sendCreateTrustedCircuit(const LLHost &host, const LLUUID & id1, const LLUUID & id2) +{ + std::string shared_secret = get_shared_secret(); + if(shared_secret.empty()) return; + char digest[MD5HEX_STR_SIZE]; /* Flawfinder: ignore */ + if (id1.isNull()) + { + llwarns << "Can't send CreateTrustedCircuit to " << host << " because we don't have the local end point ID" << llendl; + return; + } + if (id2.isNull()) + { + llwarns << "Can't send CreateTrustedCircuit to " << host << " because we don't have the remote end point ID" << llendl; + return; + } + generateDigestForWindowAndUUIDs(digest, TRUST_TIME_WINDOW, id1, id2); + newMessageFast(_PREHASH_CreateTrustedCircuit); + nextBlockFast(_PREHASH_DataBlock); + addUUIDFast(_PREHASH_EndPointID, id1); + addBinaryDataFast(_PREHASH_Digest, digest, MD5HEX_STR_BYTES); + llinfos << "xmitting digest: " << digest << " Host: " << host << llendl; + sendMessage(host); +} + +void LLMessageSystem::sendDenyTrustedCircuit(const LLHost &host) +{ + mDenyTrustedCircuitSet.insert(host); +} + +void LLMessageSystem::reallySendDenyTrustedCircuit(const LLHost &host) +{ + LLCircuitData *cdp = mCircuitInfo.findCircuit(host); + if (!cdp) + { + llwarns << "Not sending DenyTrustedCircuit to host without a circuit." << llendl; + return; + } + llinfos << "Sending DenyTrustedCircuit to " << host << llendl; + newMessageFast(_PREHASH_DenyTrustedCircuit); + nextBlockFast(_PREHASH_DataBlock); + addUUIDFast(_PREHASH_EndPointID, cdp->getLocalEndPointID()); + sendMessage(host); +} + +void null_message_callback(LLMessageSystem *msg, void **data) +{ + // Nothing should ever go here, but we use this to register messages + // that we are expecting to see (and spinning on) at startup. + return; +} + +// Try to establish a bidirectional trust metric by pinging a host until it's +// up, and then sending auth messages. +void LLMessageSystem::establishBidirectionalTrust(const LLHost &host, S64 frame_count ) +{ + std::string shared_secret = get_shared_secret(); + if(shared_secret.empty()) + { + llerrs << "Trying to establish bidirectional trust on a machine without a shared secret!" << llendl; + } + LLTimer timeout; + + timeout.setTimerExpirySec(20.0); + setHandlerFuncFast(_PREHASH_StartPingCheck, null_message_callback, NULL); + setHandlerFuncFast(_PREHASH_CompletePingCheck, null_message_callback, + NULL); + + while (! timeout.hasExpired()) + { + newMessageFast(_PREHASH_StartPingCheck); + nextBlockFast(_PREHASH_PingID); + addU8Fast(_PREHASH_PingID, 0); + addU32Fast(_PREHASH_OldestUnacked, 0); + sendMessage(host); + if (checkMessages( frame_count )) + { + if (isMessageFast(_PREHASH_CompletePingCheck) && + (getSender() == host)) + { + break; + } + } + processAcks(); + ms_sleep(1); + } + + // Send a request, a deny, and give the host 2 seconds to complete + // the trust handshake. + newMessage("RequestTrustedCircuit"); + sendMessage(host); + reallySendDenyTrustedCircuit(host); + setHandlerFuncFast(_PREHASH_StartPingCheck, process_start_ping_check, NULL); + setHandlerFuncFast(_PREHASH_CompletePingCheck, process_complete_ping_check, NULL); + + timeout.setTimerExpirySec(2.0); + LLCircuitData* cdp = NULL; + while(!timeout.hasExpired()) + { + cdp = mCircuitInfo.findCircuit(host); + if(!cdp) break; // no circuit anymore, no point continuing. + if(cdp->getTrusted()) break; // circuit is trusted. + checkMessages(frame_count); + processAcks(); + ms_sleep(1); + } +} + + +void LLMessageSystem::dumpPacketToLog() +{ + llwarns << "Packet Dump from:" << mPacketRing.getLastSender() << llendl; + llwarns << "Packet Size:" << mTrueReceiveSize << llendl; + char line_buffer[256]; /* Flawfinder: ignore */ + S32 i; + S32 cur_line_pos = 0; + + S32 cur_line = 0; + for (i = 0; i < mTrueReceiveSize; i++) + { + snprintf(line_buffer + cur_line_pos*3, sizeof(line_buffer),"%02x ", mTrueReceiveBuffer[i]); /* Flawfinder: ignore */ + cur_line_pos++; + if (cur_line_pos >= 16) + { + cur_line_pos = 0; + llwarns << "PD:" << cur_line << "PD:" << line_buffer << llendl; + cur_line++; + } + } + if (cur_line_pos) + { + llwarns << "PD:" << cur_line << "PD:" << line_buffer << llendl; + } +} + +//static +U64 LLMessageSystem::getMessageTimeUsecs(const BOOL update) +{ + if (gMessageSystem) + { + if (update) + { + gMessageSystem->mCurrentMessageTimeSeconds = totalTime()*SEC_PER_USEC; + } + return (U64)(gMessageSystem->mCurrentMessageTimeSeconds * USEC_PER_SEC); + } + else + { + return totalTime(); + } +} + +//static +F64 LLMessageSystem::getMessageTimeSeconds(const BOOL update) +{ + if (gMessageSystem) + { + if (update) + { + gMessageSystem->mCurrentMessageTimeSeconds = totalTime()*SEC_PER_USEC; + } + return gMessageSystem->mCurrentMessageTimeSeconds; + } + else + { + return totalTime()*SEC_PER_USEC; + } +} + +std::string get_shared_secret() +{ + static const std::string SHARED_SECRET_KEY("shared_secret"); + if(g_shared_secret.empty()) + { + LLApp* app = LLApp::instance(); + if(app) return app->getOption(SHARED_SECRET_KEY); + } + return g_shared_secret; +} + diff --git a/indra/llmessage/message.h b/indra/llmessage/message.h new file mode 100644 index 0000000000..c33016669d --- /dev/null +++ b/indra/llmessage/message.h @@ -0,0 +1,1253 @@ +/** + * @file message.h + * @brief LLMessageSystem class header file + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_MESSAGE_H +#define LL_MESSAGE_H + +#include +#include +#include +#include + +#if LL_LINUX +#include +#include +#endif + +#if LL_WINDOWS +#include "winsock2.h" // htons etc. +#endif + +#include "llerror.h" +#include "net.h" +#include "string_table.h" +#include "llptrskipmap.h" +#include "llcircuit.h" +#include "lltimer.h" +#include "llpacketring.h" +#include "llhost.h" +#include "llpacketack.h" +#include "doublelinkedlist.h" +#include "message_prehash.h" +#include "llstl.h" +#include "lldarray.h" + +const U32 MESSAGE_MAX_STRINGS_LENGTH = 64; +const U32 MESSAGE_NUMBER_OF_HASH_BUCKETS = 8192; + +const S32 MESSAGE_MAX_PER_FRAME = 400; + +// FIXME: This needs to be moved to a server-side only header. +// 30 Sep 2002 mark +//extern char *MESSAGE_SHARED_SECRET; + +class LLMessageStringTable +{ +public: + LLMessageStringTable(); + ~LLMessageStringTable(); + + char *getString(const char *str); + + U32 mUsed; + BOOL mEmpty[MESSAGE_NUMBER_OF_HASH_BUCKETS]; + char mString[MESSAGE_NUMBER_OF_HASH_BUCKETS][MESSAGE_MAX_STRINGS_LENGTH]; /* Flawfinder: ignore */ +}; + +extern LLMessageStringTable gMessageStringTable; + +// Individual Messages are described with the following format +// Note that to ease parsing, keywords are used +// +// // Comment (Comment like a C++ single line comment) +// Comments can only be placed between Messages +// { +// MessageName (same naming restrictions as C variable) +// Frequency ("High", "Medium", or "Low" - determines whether message ID is 8, 16, or 32-bits -- +// there can 254 messages in the first 2 groups, 32K in the last group) +// (A message can be made up only of the Name if it is only a signal) +// Trust ("Trusted", "NotTrusted" - determines if a message will be accepted +// on a circuit. "Trusted" messages are not accepted from NotTrusted circuits +// while NotTrusted messages are accepted on any circuit. An example of a +// NotTrusted circuit is any circuit from the viewer.) +// Encoding ("Zerocoded", "Unencoded" - zerocoded messages attempt to compress sequences of +// zeros, but if there is no space win, it discards the compression and goes unencoded) +// { +// Block Name (same naming restrictions as C variable) +// Block Type ("Single", "Multiple", or "Variable" - determines if the block is coded once, +// a known number of times, or has a 8 bit argument encoded to tell the decoder +// how many times the group is repeated) +// Block Repeat Number (Optional - used only with the "Multiple" type - tells how many times the field is repeated +// { +// Variable 1 Name (same naming restrictions as C variable) +// Variable Type ("Fixed" or "Variable" - determines if the variable is of fixed size or needs to +// encode an argument describing the size in bytes) +// Variable Size (In bytes, either of the "Fixed" variable itself or of the size argument) +// +// repeat variables +// +// } +// +// Repeat for number of variables in block +// } +// +// Repeat for number of blocks in message +// } +// Repeat for number of messages in file +// + +// Constants +const S32 MAX_MESSAGE_INTERNAL_NAME_SIZE = 255; +const S32 MAX_BUFFER_SIZE = NET_BUFFER_SIZE; +const S32 MAX_BLOCKS = 255; + +const U8 LL_ZERO_CODE_FLAG = 0x80; +const U8 LL_RELIABLE_FLAG = 0x40; +const U8 LL_RESENT_FLAG = 0x20; +const U8 LL_ACK_FLAG = 0x10; + +const S32 LL_MINIMUM_VALID_PACKET_SIZE = LL_PACKET_ID_SIZE + 1; // 4 bytes id + 1 byte message name (high) + +const S32 LL_DEFAULT_RELIABLE_RETRIES = 3; +const F32 LL_MINIMUM_RELIABLE_TIMEOUT_SECONDS = 1.f; +const F32 LL_MINIMUM_SEMIRELIABLE_TIMEOUT_SECONDS = 1.f; +const F32 LL_PING_BASED_TIMEOUT_DUMMY = 0.0f; + +// FIXME: These factors shouldn't include the msec to sec conversion implicitly +const F32 LL_SEMIRELIABLE_TIMEOUT_FACTOR = 5.f / 1000.f; // factor * averaged ping +const F32 LL_RELIABLE_TIMEOUT_FACTOR = 5.f / 1000.f; // factor * averaged ping +const F32 LL_FILE_XFER_TIMEOUT_FACTOR = 5.f / 1000.f; // factor * averaged ping +const F32 LL_LOST_TIMEOUT_FACTOR = 16.f / 1000.f; // factor * averaged ping for marking packets "Lost" +const F32 LL_MAX_LOST_TIMEOUT = 5.f; // Maximum amount of time before considering something "lost" + +const S32 MAX_MESSAGE_COUNT_NUM = 1024; + +// Forward declarations +class LLCircuit; +class LLVector3; +class LLVector4; +class LLVector3d; +class LLQuaternion; +class LLSD; +class LLUUID; +class LLMessageSystem; + +// message data pieces are used to collect the data called for by the message template + +// iterator typedefs precede each class as needed +typedef enum e_message_variable_type +{ + MVT_NULL, + MVT_FIXED, + MVT_VARIABLE, + MVT_U8, + MVT_U16, + MVT_U32, + MVT_U64, + MVT_S8, + MVT_S16, + MVT_S32, + MVT_S64, + MVT_F32, + MVT_F64, + MVT_LLVector3, + MVT_LLVector3d, + MVT_LLVector4, + MVT_LLQuaternion, + MVT_LLUUID, + MVT_BOOL, + MVT_IP_ADDR, + MVT_IP_PORT, + MVT_U16Vec3, + MVT_U16Quat, + MVT_S16Array, + MVT_EOL +} EMsgVariableType; + +// message system exceptional condition handlers. +enum EMessageException +{ + MX_UNREGISTERED_MESSAGE, // message number not part of template + MX_PACKET_TOO_SHORT, // invalid packet, shorter than minimum packet size + MX_RAN_OFF_END_OF_PACKET, // ran off the end of the packet during decode + MX_WROTE_PAST_BUFFER_SIZE // wrote past buffer size in zero code expand +}; +typedef void (*msg_exception_callback)(LLMessageSystem*,void*,EMessageException); + + + +class LLMsgData; +class LLMsgBlkData; +class LLMessageTemplate; + +class LLMessagePollInfo; + +class LLMessageSystem +{ +public: + U8 mSendBuffer[MAX_BUFFER_SIZE]; + // Encoded send buffer needs to be slightly larger since the zero + // coding can potentially increase the size of the send data. + U8 mEncodedSendBuffer[2 * MAX_BUFFER_SIZE]; + S32 mSendSize; + S32 mCurrentSendTotal; + + LLPacketRing mPacketRing; + LLReliablePacketParams mReliablePacketParams; + + //LLLinkedList mAckList; + + // Set this flag to TRUE when you want *very* verbose logs. + BOOL mVerboseLog; + + U32 mMessageFileChecksum; + F32 mMessageFileVersionNumber; + + typedef std::map message_template_name_map_t; + typedef std::map message_template_number_map_t; + +private: + message_template_name_map_t mMessageTemplates; + message_template_number_map_t mMessageNumbers; + +public: + S32 mSystemVersionMajor; + S32 mSystemVersionMinor; + S32 mSystemVersionPatch; + S32 mSystemVersionServer; + U32 mVersionFlags; + + + BOOL mbProtected; + + U32 mNumberHighFreqMessages; + U32 mNumberMediumFreqMessages; + U32 mNumberLowFreqMessages; + S32 mPort; + S32 mSocket; + + U32 mPacketsIn; // total packets in, including compressed and uncompressed + U32 mPacketsOut; // total packets out, including compressed and uncompressed + + U64 mBytesIn; // total bytes in, including compressed and uncompressed + U64 mBytesOut; // total bytes out, including compressed and uncompressed + + U32 mCompressedPacketsIn; // total compressed packets in + U32 mCompressedPacketsOut; // total compressed packets out + + U32 mReliablePacketsIn; // total reliable packets in + U32 mReliablePacketsOut; // total reliable packets out + + U32 mDroppedPackets; // total dropped packets in + U32 mResentPackets; // total resent packets out + U32 mFailedResendPackets; // total resend failure packets out + U32 mOffCircuitPackets; // total # of off-circuit packets rejected + U32 mInvalidOnCircuitPackets; // total # of on-circuit but invalid packets rejected + + S64 mUncompressedBytesIn; // total uncompressed size of compressed packets in + S64 mUncompressedBytesOut; // total uncompressed size of compressed packets out + S64 mCompressedBytesIn; // total compressed size of compressed packets in + S64 mCompressedBytesOut; // total compressed size of compressed packets out + S64 mTotalBytesIn; // total size of all uncompressed packets in + S64 mTotalBytesOut; // total size of all uncompressed packets out + + BOOL mSendReliable; // does the outgoing message require a pos ack? + + LLCircuit mCircuitInfo; + F64 mCircuitPrintTime; // used to print circuit debug info every couple minutes + F32 mCircuitPrintFreq; // seconds + + std::map mIPPortToCircuitCode; + std::map mCircuitCodeToIPPort; + U32 mOurCircuitCode; + S32 mSendPacketFailureCount; + S32 mUnackedListDepth; + S32 mUnackedListSize; + S32 mDSMaxListDepth; + +public: + // Read file and build message templates + LLMessageSystem(const char *filename, U32 port, S32 version_major, + S32 version_minor, S32 version_patch); + +public: + // Subclass use. + LLMessageSystem(); + +public: + virtual ~LLMessageSystem(); + + BOOL isOK() const { return !mbError; } + S32 getErrorCode() const { return mErrorCode; } + + // Read file and build message templates filename must point to a + // valid string which specifies the path of a valid linden + // template. + void loadTemplateFile(const char* filename); + + + // methods for building, sending, receiving, and handling messages + void setHandlerFuncFast(const char *name, void (*handler_func)(LLMessageSystem *msgsystem, void **user_data), void **user_data = NULL); + void setHandlerFunc(const char *name, void (*handler_func)(LLMessageSystem *msgsystem, void **user_data), void **user_data = NULL) + { + setHandlerFuncFast(gMessageStringTable.getString(name), handler_func, user_data); + } + + bool callHandler(const char *name, bool trustedSource, + LLMessageSystem* msg); + + // Set a callback function for a message system exception. + void setExceptionFunc(EMessageException exception, msg_exception_callback func, void* data = NULL); + // Call the specified exception func, and return TRUE if a + // function was found and called. Otherwise return FALSE. + BOOL callExceptionFunc(EMessageException exception); + + // This method returns true if the code is in the circuit codes map. + BOOL isCircuitCodeKnown(U32 code) const; + + // usually called in response to an AddCircuitCode message, but + // may also be called by the login process. + bool addCircuitCode(U32 code, const LLUUID& session_id); + + BOOL poll(F32 seconds); // Number of seconds that we want to block waiting for data, returns if data was received + BOOL checkMessages( S64 frame_count = 0 ); + void processAcks(); + + BOOL isMessageFast(const char *msg); + BOOL isMessage(const char *msg) + { + return isMessageFast(gMessageStringTable.getString(msg)); + } + + void dumpPacketToLog(); + + char *getMessageName(); + + const LLHost& getSender() const; + U32 getSenderIP() const; // getSender() is preferred + U32 getSenderPort() const; // getSender() is preferred + + // This method returns the uuid associated with the sender. The + // UUID will be null if it is not yet known or is a server + // circuit. + const LLUUID& getSenderID() const; + + // This method returns the session id associated with the last + // sender. + const LLUUID& getSenderSessionID() const; + + // set & get the session id (useful for viewers for now.) + void setMySessionID(const LLUUID& session_id) { mSessionID = session_id; } + const LLUUID& getMySessionID() { return mSessionID; } + + virtual void newMessageFast(const char *name); + void newMessage(const char *name) + { + newMessageFast(gMessageStringTable.getString(name)); + } + + void copyMessageRtoS(); + void clearMessage(); + + virtual void nextBlockFast(const char *blockname); + void nextBlock(const char *blockname) + { + nextBlockFast(gMessageStringTable.getString(blockname)); + } +private: + void addDataFast(const char *varname, const void *data, EMsgVariableType type, S32 size); // Use only for types not in system already + void addData(const char *varname, const void *data, EMsgVariableType type, S32 size) + { + addDataFast(gMessageStringTable.getString(varname), data, type, size); + } + + + void addDataFast(const char *varname, const void *data, EMsgVariableType type); // DEPRECATED - not typed, doesn't check storage space + void addData(const char *varname, const void *data, EMsgVariableType type) + { + addDataFast(gMessageStringTable.getString(varname), data, type); + } +public: + void addBinaryDataFast(const char *varname, const void *data, S32 size) + { + addDataFast(varname, data, MVT_FIXED, size); + } + void addBinaryData(const char *varname, const void *data, S32 size) + { + addDataFast(gMessageStringTable.getString(varname), data, MVT_FIXED, size); + } + + void addBOOLFast( const char* varname, BOOL b); // typed, checks storage space + void addBOOL( const char* varname, BOOL b); // typed, checks storage space + void addS8Fast( const char *varname, S8 s); // typed, checks storage space + void addS8( const char *varname, S8 s); // typed, checks storage space + void addU8Fast( const char *varname, U8 u); // typed, checks storage space + void addU8( const char *varname, U8 u); // typed, checks storage space + void addS16Fast( const char *varname, S16 i); // typed, checks storage space + void addS16( const char *varname, S16 i); // typed, checks storage space + void addU16Fast( const char *varname, U16 i); // typed, checks storage space + void addU16( const char *varname, U16 i); // typed, checks storage space + void addF32Fast( const char *varname, F32 f); // typed, checks storage space + void addF32( const char *varname, F32 f); // typed, checks storage space + void addS32Fast( const char *varname, S32 s); // typed, checks storage space + void addS32( const char *varname, S32 s); // typed, checks storage space + virtual void addU32Fast( const char *varname, U32 u); // typed, checks storage space + void addU32( const char *varname, U32 u); // typed, checks storage space + void addU64Fast( const char *varname, U64 lu); // typed, checks storage space + void addU64( const char *varname, U64 lu); // typed, checks storage space + void addF64Fast( const char *varname, F64 d); // typed, checks storage space + void addF64( const char *varname, F64 d); // typed, checks storage space + void addVector3Fast( const char *varname, const LLVector3& vec); // typed, checks storage space + void addVector3( const char *varname, const LLVector3& vec); // typed, checks storage space + void addVector4Fast( const char *varname, const LLVector4& vec); // typed, checks storage space + void addVector4( const char *varname, const LLVector4& vec); // typed, checks storage space + void addVector3dFast( const char *varname, const LLVector3d& vec); // typed, checks storage space + void addVector3d( const char *varname, const LLVector3d& vec); // typed, checks storage space + void addQuatFast( const char *varname, const LLQuaternion& quat); // typed, checks storage space + void addQuat( const char *varname, const LLQuaternion& quat); // typed, checks storage space + virtual void addUUIDFast( const char *varname, const LLUUID& uuid); // typed, checks storage space + void addUUID( const char *varname, const LLUUID& uuid); // typed, checks storage space + void addIPAddrFast( const char *varname, const U32 ip); // typed, checks storage space + void addIPAddr( const char *varname, const U32 ip); // typed, checks storage space + void addIPPortFast( const char *varname, const U16 port); // typed, checks storage space + void addIPPort( const char *varname, const U16 port); // typed, checks storage space + void addStringFast( const char* varname, const char* s); // typed, checks storage space + void addString( const char* varname, const char* s); // typed, checks storage space + void addStringFast( const char* varname, const std::string& s); // typed, checks storage space + void addString( const char* varname, const std::string& s); // typed, checks storage space + + S32 getCurrentSendTotal() const { return mCurrentSendTotal; } + + // This method checks for current send total and returns true if + // you need to go to the next block type or need to start a new + // message. Specify the current blockname to check block counts, + // otherwise the method only checks against MTU. + BOOL isSendFull(const char* blockname = NULL); + BOOL isSendFullFast(const char* blockname = NULL); + + BOOL removeLastBlock(); + + void buildMessage(); + + S32 zeroCode(U8 **data, S32 *data_size); + S32 zeroCodeExpand(U8 **data, S32 *data_size); + S32 zeroCodeAdjustCurrentSendTotal(); + + // Uses ping-based retry + virtual S32 sendReliable(const LLHost &host); + + // Uses ping-based retry + S32 sendReliable(const U32 circuit) { return sendReliable(findHost(circuit)); } + + // Use this one if you DON'T want automatic ping-based retry. + S32 sendReliable( const LLHost &host, + S32 retries, + BOOL ping_based_retries, + F32 timeout, + void (*callback)(void **,S32), + void ** callback_data); + + S32 sendSemiReliable( const LLHost &host, + void (*callback)(void **,S32), void ** callback_data); + + // flush sends a message only if data's been pushed on it. + S32 flushSemiReliable( const LLHost &host, + void (*callback)(void **,S32), void ** callback_data); + + S32 flushReliable( const LLHost &host ); + + void forwardMessage(const LLHost &host); + void forwardReliable(const LLHost &host); + void forwardReliable(const U32 circuit_code); + + S32 sendMessage(const LLHost &host); + S32 sendMessage(const U32 circuit); + + BOOL decodeData(const U8 *buffer, const LLHost &host); + + // TODO: Consolide these functions + // TODO: Make these private, force use of typed functions. + // If size is not 0, an error is generated if size doesn't exactly match the size of the data. + // At all times, the number if bytes written to *datap is <= max_size. +private: + void getDataFast(const char *blockname, const char *varname, void *datap, S32 size = 0, S32 blocknum = 0, S32 max_size = S32_MAX); + void getData(const char *blockname, const char *varname, void *datap, S32 size = 0, S32 blocknum = 0, S32 max_size = S32_MAX) + { + getDataFast(gMessageStringTable.getString(blockname), gMessageStringTable.getString(varname), datap, size, blocknum, max_size); + } +public: + void getBinaryDataFast(const char *blockname, const char *varname, void *datap, S32 size, S32 blocknum = 0, S32 max_size = S32_MAX) + { + getDataFast(blockname, varname, datap, size, blocknum, max_size); + } + void getBinaryData(const char *blockname, const char *varname, void *datap, S32 size, S32 blocknum = 0, S32 max_size = S32_MAX) + { + getDataFast(gMessageStringTable.getString(blockname), gMessageStringTable.getString(varname), datap, size, blocknum, max_size); + } + + void getBOOLFast( const char *block, const char *var, BOOL &data, S32 blocknum = 0); + void getBOOL( const char *block, const char *var, BOOL &data, S32 blocknum = 0); + void getS8Fast( const char *block, const char *var, S8 &data, S32 blocknum = 0); + void getS8( const char *block, const char *var, S8 &data, S32 blocknum = 0); + void getU8Fast( const char *block, const char *var, U8 &data, S32 blocknum = 0); + void getU8( const char *block, const char *var, U8 &data, S32 blocknum = 0); + void getS16Fast( const char *block, const char *var, S16 &data, S32 blocknum = 0); + void getS16( const char *block, const char *var, S16 &data, S32 blocknum = 0); + void getU16Fast( const char *block, const char *var, U16 &data, S32 blocknum = 0); + void getU16( const char *block, const char *var, U16 &data, S32 blocknum = 0); + void getS32Fast( const char *block, const char *var, S32 &data, S32 blocknum = 0); + void getS32( const char *block, const char *var, S32 &data, S32 blocknum = 0); + void getF32Fast( const char *block, const char *var, F32 &data, S32 blocknum = 0); + void getF32( const char *block, const char *var, F32 &data, S32 blocknum = 0); + virtual void getU32Fast( const char *block, const char *var, U32 &data, S32 blocknum = 0); + void getU32( const char *block, const char *var, U32 &data, S32 blocknum = 0); + virtual void getU64Fast( const char *block, const char *var, U64 &data, S32 blocknum = 0); + void getU64( const char *block, const char *var, U64 &data, S32 blocknum = 0); + void getF64Fast( const char *block, const char *var, F64 &data, S32 blocknum = 0); + void getF64( const char *block, const char *var, F64 &data, S32 blocknum = 0); + void getVector3Fast( const char *block, const char *var, LLVector3 &vec, S32 blocknum = 0); + void getVector3( const char *block, const char *var, LLVector3 &vec, S32 blocknum = 0); + void getVector4Fast( const char *block, const char *var, LLVector4 &vec, S32 blocknum = 0); + void getVector4( const char *block, const char *var, LLVector4 &vec, S32 blocknum = 0); + void getVector3dFast(const char *block, const char *var, LLVector3d &vec, S32 blocknum = 0); + void getVector3d(const char *block, const char *var, LLVector3d &vec, S32 blocknum = 0); + void getQuatFast( const char *block, const char *var, LLQuaternion &q, S32 blocknum = 0); + void getQuat( const char *block, const char *var, LLQuaternion &q, S32 blocknum = 0); + virtual void getUUIDFast( const char *block, const char *var, LLUUID &uuid, S32 blocknum = 0); + void getUUID( const char *block, const char *var, LLUUID &uuid, S32 blocknum = 0); + virtual void getIPAddrFast( const char *block, const char *var, U32 &ip, S32 blocknum = 0); + void getIPAddr( const char *block, const char *var, U32 &ip, S32 blocknum = 0); + virtual void getIPPortFast( const char *block, const char *var, U16 &port, S32 blocknum = 0); + void getIPPort( const char *block, const char *var, U16 &port, S32 blocknum = 0); + virtual void getStringFast( const char *block, const char *var, S32 buffer_size, char *buffer, S32 blocknum = 0); + void getString( const char *block, const char *var, S32 buffer_size, char *buffer, S32 blocknum = 0); + + + // Utility functions to generate a replay-resistant digest check + // against the shared secret. The window specifies how much of a + // time window is allowed - 1 second is good for tight + // connections, but multi-process windows might want to be upwards + // of 5 seconds. For generateDigest, you want to pass in a + // character array of at least MD5HEX_STR_SIZE so that the hex + // digest and null termination will fit. + bool generateDigestForNumberAndUUIDs(char* digest, const U32 number, const LLUUID &id1, const LLUUID &id2) const; + bool generateDigestForWindowAndUUIDs(char* digest, const S32 window, const LLUUID &id1, const LLUUID &id2) const; + bool isMatchingDigestForWindowAndUUIDs(const char* digest, const S32 window, const LLUUID &id1, const LLUUID &id2) const; + + bool generateDigestForNumber(char* digest, const U32 number) const; + bool generateDigestForWindow(char* digest, const S32 window) const; + bool isMatchingDigestForWindow(const char* digest, const S32 window) const; + + void showCircuitInfo(); + LLString getCircuitInfoString(); + + virtual U32 getOurCircuitCode(); + + void enableCircuit(const LLHost &host, BOOL trusted); + void disableCircuit(const LLHost &host); + + // Use this to establish trust on startup and in response to + // DenyTrustedCircuit. + void sendCreateTrustedCircuit(const LLHost& host, const LLUUID & id1, const LLUUID & id2); + + // Use this to inform a peer that they aren't currently trusted... + // This now enqueues the request so that we can ensure that we only send + // one deny per circuit per message loop so that this doesn't become a DoS. + // The actual sending is done by reallySendDenyTrustedCircuit() + void sendDenyTrustedCircuit(const LLHost &host); + +private: + // A list of the circuits that need to be sent DenyTrustedCircuit messages. + typedef std::set host_set_t; + host_set_t mDenyTrustedCircuitSet; + + // Really sends the DenyTrustedCircuit message to a given host + // related to sendDenyTrustedCircuit() + void reallySendDenyTrustedCircuit(const LLHost &host); + + +public: + // Use this to establish trust to and from a host. This blocks + // until trust has been established, and probably should only be + // used on startup. + void establishBidirectionalTrust(const LLHost &host, S64 frame_count = 0); + + // returns whether the given host is on a trusted circuit + BOOL getCircuitTrust(const LLHost &host); + + void setCircuitAllowTimeout(const LLHost &host, BOOL allow); + void setCircuitTimeoutCallback(const LLHost &host, void (*callback_func)(const LLHost &host, void *user_data), void *user_data); + + BOOL checkCircuitBlocked(const U32 circuit); + BOOL checkCircuitAlive(const U32 circuit); + BOOL checkCircuitAlive(const LLHost &host); + void setCircuitProtection(BOOL b_protect); + U32 findCircuitCode(const LLHost &host); + LLHost findHost(const U32 circuit_code); + void sanityCheck(); + + S32 getNumberOfBlocksFast(const char *blockname); + S32 getNumberOfBlocks(const char *blockname) + { + return getNumberOfBlocksFast(gMessageStringTable.getString(blockname)); + } + S32 getSizeFast(const char *blockname, const char *varname); + S32 getSize(const char *blockname, const char *varname) + { + return getSizeFast(gMessageStringTable.getString(blockname), gMessageStringTable.getString(varname)); + } + S32 getSizeFast(const char *blockname, S32 blocknum, const char *varname); // size in bytes of variable length data + S32 getSize(const char *blockname, S32 blocknum, const char *varname) + { + return getSizeFast(gMessageStringTable.getString(blockname), blocknum, gMessageStringTable.getString(varname)); + } + + void resetReceiveCounts(); // resets receive counts for all message types to 0 + void dumpReceiveCounts(); // dumps receive count for each message type to llinfos + void dumpCircuitInfo(); // Circuit information to llinfos + + BOOL isClear() const; // returns mbSClear; + S32 flush(const LLHost &host); + + U32 getListenPort( void ) const; + + void startLogging(); // start verbose logging + void stopLogging(); // flush and close file + void summarizeLogs(std::ostream& str); // log statistics + + S32 getReceiveSize() const { return mReceiveSize; } + S32 getReceiveCompressedSize() const { return mIncomingCompressedSize; } + S32 getReceiveBytes() const; + + S32 getUnackedListSize() const { return mUnackedListSize; } + + const char* getCurrentSMessageName() const { return mCurrentSMessageName; } + const char* getCurrentSBlockName() const { return mCurrentSBlockName; } + + // friends + friend std::ostream& operator<<(std::ostream& s, LLMessageSystem &msg); + + void setMaxMessageTime(const F32 seconds); // Max time to process messages before warning and dumping (neg to disable) + void setMaxMessageCounts(const S32 num); // Max number of messages before dumping (neg to disable) + + // statics +public: + static U64 getMessageTimeUsecs(const BOOL update = FALSE); // Get the current message system time in microseconds + static F64 getMessageTimeSeconds(const BOOL update = FALSE); // Get the current message system time in seconds + + static void setTimeDecodes( BOOL b ) + { LLMessageSystem::mTimeDecodes = b; } + + static void setTimeDecodesSpamThreshold( F32 seconds ) + { LLMessageSystem::mTimeDecodesSpamThreshold = seconds; } + + // message handlers internal to the message systesm + //static void processAssignCircuitCode(LLMessageSystem* msg, void**); + static void processAddCircuitCode(LLMessageSystem* msg, void**); + static void processUseCircuitCode(LLMessageSystem* msg, void**); + + void setMessageBans(const LLSD& trusted, const LLSD& untrusted); + +private: + // data used in those internal handlers + + // The mCircuitCodes is a map from circuit codes to session + // ids. This allows us to verify sessions on connect. + typedef std::map code_session_map_t; + code_session_map_t mCircuitCodes; + + // Viewers need to track a process session in order to make sure + // that no one gives them a bad circuit code. + LLUUID mSessionID; + +private: + void addTemplate(LLMessageTemplate *templatep); + void clearReceiveState(); + BOOL decodeTemplate( const U8* buffer, S32 buffer_size, LLMessageTemplate** msg_template ); + + void logMsgFromInvalidCircuit( const LLHost& sender, BOOL recv_reliable ); + void logTrustedMsgFromUntrustedCircuit( const LLHost& sender ); + void logValidMsg(LLCircuitData *cdp, const LLHost& sender, BOOL recv_reliable, BOOL recv_resent, BOOL recv_acks ); + void logRanOffEndOfPacket( const LLHost& sender ); + +private: + class LLMessageCountInfo + { + public: + U32 mMessageNum; + U32 mMessageBytes; + BOOL mInvalid; + }; + + LLMessagePollInfo *mPollInfop; + + U8 mEncodedRecvBuffer[MAX_BUFFER_SIZE]; + U8 mTrueReceiveBuffer[MAX_BUFFER_SIZE]; + S32 mTrueReceiveSize; + + // Must be valid during decode + S32 mReceiveSize; + TPACKETID mCurrentRecvPacketID; // packet ID of current receive packet (for reporting) + LLMessageTemplate *mCurrentRMessageTemplate; + LLMsgData *mCurrentRMessageData; + S32 mIncomingCompressedSize; // original size of compressed msg (0 if uncomp.) + LLHost mLastSender; + + // send message storage + LLMsgData *mCurrentSMessageData; + LLMessageTemplate *mCurrentSMessageTemplate; + LLMsgBlkData *mCurrentSDataBlock; + char *mCurrentSMessageName; + char *mCurrentSBlockName; + + BOOL mbError; + S32 mErrorCode; + + BOOL mbSBuilt; // is send message built? + BOOL mbSClear; // is the send message clear? + + F64 mResendDumpTime; // The last time we dumped resends + + LLMessageCountInfo mMessageCountList[MAX_MESSAGE_COUNT_NUM]; + S32 mNumMessageCounts; + F32 mReceiveTime; + F32 mMaxMessageTime; // Max number of seconds for processing messages + S32 mMaxMessageCounts; // Max number of messages to process before dumping. + F64 mMessageCountTime; + + F64 mCurrentMessageTimeSeconds; // The current "message system time" (updated the first call to checkMessages after a resetReceiveCount + + // message system exceptions + typedef std::pair exception_t; + typedef std::map callbacks_t; + callbacks_t mExceptionCallbacks; + + // stuff for logging + LLTimer mMessageSystemTimer; + + static F32 mTimeDecodesSpamThreshold; // If mTimeDecodes is on, all this many seconds for each msg decode before spamming + static BOOL mTimeDecodes; // Measure time for all message decodes if TRUE; + + void LLMessageSystem::init(); // ctor shared initialisation. +}; + + +// external hook into messaging system +extern LLMessageSystem *gMessageSystem; +//extern const char* MESSAGE_LOG_FILENAME; + +void encrypt_template(const char *src_name, const char *dest_name); +BOOL decrypt_template(const char *src_name, const char *dest_name); + +// Must specific overall system version, which is used to determine +// if a patch is available in the message template checksum verification. +// Return TRUE if able to initialize system. +BOOL start_messaging_system( + const std::string& template_name, + U32 port, + S32 version_major, + S32 version_minor, + S32 version_patch, + BOOL b_dump_prehash_file, + const std::string& secret); + +void end_messaging_system(); + +void null_message_callback(LLMessageSystem *msg, void **data); +void process_log_control(LLMessageSystem* msg, void**); + +// +// Inlines +// + +static inline void *htonmemcpy(void *vs, const void *vct, EMsgVariableType type, size_t n) +{ + char *s = (char *)vs; + const char *ct = (const char *)vct; +#ifdef LL_BIG_ENDIAN + S32 i, length; +#endif + switch(type) + { + case MVT_FIXED: + case MVT_VARIABLE: + case MVT_U8: + case MVT_S8: + case MVT_BOOL: + case MVT_LLUUID: + case MVT_IP_ADDR: // these two are swizzled in the getters and setters + case MVT_IP_PORT: // these two are swizzled in the getters and setters + return(memcpy(s,ct,n)); /* Flawfinder: ignore */ + + case MVT_U16: + case MVT_S16: + if (n != 2) + { + llerrs << "Size argument passed to htonmemcpy doesn't match swizzle type size" << llendl; + } +#ifdef LL_BIG_ENDIAN + *(s + 1) = *(ct); + *(s) = *(ct + 1); + return(vs); +#else + return(memcpy(s,ct,n)); /* Flawfinder: ignore */ +#endif + + case MVT_U32: + case MVT_S32: + case MVT_F32: + if (n != 4) + { + llerrs << "Size argument passed to htonmemcpy doesn't match swizzle type size" << llendl; + } +#ifdef LL_BIG_ENDIAN + *(s + 3) = *(ct); + *(s + 2) = *(ct + 1); + *(s + 1) = *(ct + 2); + *(s) = *(ct + 3); + return(vs); +#else + return(memcpy(s,ct,n)); /* Flawfinder: ignore */ +#endif + + case MVT_U64: + case MVT_S64: + case MVT_F64: + if (n != 8) + { + llerrs << "Size argument passed to htonmemcpy doesn't match swizzle type size" << llendl; + } +#ifdef LL_BIG_ENDIAN + *(s + 7) = *(ct); + *(s + 6) = *(ct + 1); + *(s + 5) = *(ct + 2); + *(s + 4) = *(ct + 3); + *(s + 3) = *(ct + 4); + *(s + 2) = *(ct + 5); + *(s + 1) = *(ct + 6); + *(s) = *(ct + 7); + return(vs); +#else + return(memcpy(s,ct,n)); /* Flawfinder: ignore */ +#endif + + case MVT_LLVector3: + case MVT_LLQuaternion: // We only send x, y, z and infer w (we set x, y, z to ensure that w >= 0) + if (n != 12) + { + llerrs << "Size argument passed to htonmemcpy doesn't match swizzle type size" << llendl; + } +#ifdef LL_BIG_ENDIAN + htonmemcpy(s + 8, ct + 8, MVT_F32, 4); + htonmemcpy(s + 4, ct + 4, MVT_F32, 4); + return(htonmemcpy(s, ct, MVT_F32, 4)); +#else + return(memcpy(s,ct,n)); /* Flawfinder: ignore */ +#endif + + case MVT_LLVector3d: + if (n != 24) + { + llerrs << "Size argument passed to htonmemcpy doesn't match swizzle type size" << llendl; + } +#ifdef LL_BIG_ENDIAN + htonmemcpy(s + 16, ct + 16, MVT_F64, 8); + htonmemcpy(s + 8, ct + 8, MVT_F64, 8); + return(htonmemcpy(s, ct, MVT_F64, 8)); +#else + return(memcpy(s,ct,n)); /* Flawfinder: ignore */ +#endif + + case MVT_LLVector4: + if (n != 16) + { + llerrs << "Size argument passed to htonmemcpy doesn't match swizzle type size" << llendl; + } +#ifdef LL_BIG_ENDIAN + htonmemcpy(s + 12, ct + 12, MVT_F32, 4); + htonmemcpy(s + 8, ct + 8, MVT_F32, 4); + htonmemcpy(s + 4, ct + 4, MVT_F32, 4); + return(htonmemcpy(s, ct, MVT_F32, 4)); +#else + return(memcpy(s,ct,n)); /* Flawfinder: ignore */ +#endif + + case MVT_U16Vec3: + if (n != 6) + { + llerrs << "Size argument passed to htonmemcpy doesn't match swizzle type size" << llendl; + } +#ifdef LL_BIG_ENDIAN + htonmemcpy(s + 4, ct + 4, MVT_U16, 2); + htonmemcpy(s + 2, ct + 2, MVT_U16, 2); + return(htonmemcpy(s, ct, MVT_U16, 2)); +#else + return(memcpy(s,ct,n)); /* Flawfinder: ignore */ +#endif + + case MVT_U16Quat: + if (n != 8) + { + llerrs << "Size argument passed to htonmemcpy doesn't match swizzle type size" << llendl; + } +#ifdef LL_BIG_ENDIAN + htonmemcpy(s + 6, ct + 6, MVT_U16, 2); + htonmemcpy(s + 4, ct + 4, MVT_U16, 2); + htonmemcpy(s + 2, ct + 2, MVT_U16, 2); + return(htonmemcpy(s, ct, MVT_U16, 2)); +#else + return(memcpy(s,ct,n)); /* Flawfinder: ignore */ +#endif + + case MVT_S16Array: + if (n % 2) + { + llerrs << "Size argument passed to htonmemcpy doesn't match swizzle type size" << llendl; + } +#ifdef LL_BIG_ENDIAN + length = n % 2; + for (i = 1; i < length; i++) + { + htonmemcpy(s + i*2, ct + i*2, MVT_S16, 2); + } + return(htonmemcpy(s, ct, MVT_S16, 2)); +#else + return(memcpy(s,ct,n)); +#endif + + default: + return(memcpy(s,ct,n)); /* Flawfinder: ignore */ + } +} + +inline void *ntohmemcpy(void *s, const void *ct, EMsgVariableType type, size_t n) +{ + return(htonmemcpy(s,ct,type, n)); +} + + +inline const LLHost& LLMessageSystem::getSender() const +{ + return mLastSender; +} + +inline U32 LLMessageSystem::getSenderIP() const +{ + return mLastSender.getAddress(); +} + +inline U32 LLMessageSystem::getSenderPort() const +{ + return mLastSender.getPort(); +} + +inline void LLMessageSystem::addS8Fast(const char *varname, S8 s) +{ + addDataFast(varname, &s, MVT_S8, sizeof(s)); +} + +inline void LLMessageSystem::addS8(const char *varname, S8 s) +{ + addDataFast(gMessageStringTable.getString(varname), &s, MVT_S8, sizeof(s)); +} + +inline void LLMessageSystem::addU8Fast(const char *varname, U8 u) +{ + addDataFast(varname, &u, MVT_U8, sizeof(u)); +} + +inline void LLMessageSystem::addU8(const char *varname, U8 u) +{ + addDataFast(gMessageStringTable.getString(varname), &u, MVT_U8, sizeof(u)); +} + +inline void LLMessageSystem::addS16Fast(const char *varname, S16 i) +{ + addDataFast(varname, &i, MVT_S16, sizeof(i)); +} + +inline void LLMessageSystem::addS16(const char *varname, S16 i) +{ + addDataFast(gMessageStringTable.getString(varname), &i, MVT_S16, sizeof(i)); +} + +inline void LLMessageSystem::addU16Fast(const char *varname, U16 i) +{ + addDataFast(varname, &i, MVT_U16, sizeof(i)); +} + +inline void LLMessageSystem::addU16(const char *varname, U16 i) +{ + addDataFast(gMessageStringTable.getString(varname), &i, MVT_U16, sizeof(i)); +} + +inline void LLMessageSystem::addF32Fast(const char *varname, F32 f) +{ + addDataFast(varname, &f, MVT_F32, sizeof(f)); +} + +inline void LLMessageSystem::addF32(const char *varname, F32 f) +{ + addDataFast(gMessageStringTable.getString(varname), &f, MVT_F32, sizeof(f)); +} + +inline void LLMessageSystem::addS32Fast(const char *varname, S32 s) +{ + addDataFast(varname, &s, MVT_S32, sizeof(s)); +} + +inline void LLMessageSystem::addS32(const char *varname, S32 s) +{ + addDataFast(gMessageStringTable.getString(varname), &s, MVT_S32, sizeof(s)); +} + +inline void LLMessageSystem::addU32Fast(const char *varname, U32 u) +{ + addDataFast(varname, &u, MVT_U32, sizeof(u)); +} + +inline void LLMessageSystem::addU32(const char *varname, U32 u) +{ + addDataFast(gMessageStringTable.getString(varname), &u, MVT_U32, sizeof(u)); +} + +inline void LLMessageSystem::addU64Fast(const char *varname, U64 lu) +{ + addDataFast(varname, &lu, MVT_U64, sizeof(lu)); +} + +inline void LLMessageSystem::addU64(const char *varname, U64 lu) +{ + addDataFast(gMessageStringTable.getString(varname), &lu, MVT_U64, sizeof(lu)); +} + +inline void LLMessageSystem::addF64Fast(const char *varname, F64 d) +{ + addDataFast(varname, &d, MVT_F64, sizeof(d)); +} + +inline void LLMessageSystem::addF64(const char *varname, F64 d) +{ + addDataFast(gMessageStringTable.getString(varname), &d, MVT_F64, sizeof(d)); +} + +inline void LLMessageSystem::addIPAddrFast(const char *varname, U32 u) +{ + addDataFast(varname, &u, MVT_IP_ADDR, sizeof(u)); +} + +inline void LLMessageSystem::addIPAddr(const char *varname, U32 u) +{ + addDataFast(gMessageStringTable.getString(varname), &u, MVT_IP_ADDR, sizeof(u)); +} + +inline void LLMessageSystem::addIPPortFast(const char *varname, U16 u) +{ + u = htons(u); + addDataFast(varname, &u, MVT_IP_PORT, sizeof(u)); +} + +inline void LLMessageSystem::addIPPort(const char *varname, U16 u) +{ + u = htons(u); + addDataFast(gMessageStringTable.getString(varname), &u, MVT_IP_PORT, sizeof(u)); +} + +inline void LLMessageSystem::addBOOLFast(const char* varname, BOOL b) +{ + // Can't just cast a BOOL (actually a U32) to a U8. + // In some cases the low order bits will be zero. + U8 temp = (b != 0); + addDataFast(varname, &temp, MVT_BOOL, sizeof(temp)); +} + +inline void LLMessageSystem::addBOOL(const char* varname, BOOL b) +{ + // Can't just cast a BOOL (actually a U32) to a U8. + // In some cases the low order bits will be zero. + U8 temp = (b != 0); + addDataFast(gMessageStringTable.getString(varname), &temp, MVT_BOOL, sizeof(temp)); +} + +inline void LLMessageSystem::addStringFast(const char* varname, const char* s) +{ + if (s) + addDataFast( varname, (void *)s, MVT_VARIABLE, (S32)strlen(s) + 1); /* Flawfinder: ignore */ + else + addDataFast( varname, NULL, MVT_VARIABLE, 0); +} + +inline void LLMessageSystem::addString(const char* varname, const char* s) +{ + if (s) + addDataFast( gMessageStringTable.getString(varname), (void *)s, MVT_VARIABLE, (S32)strlen(s) + 1); /* Flawfinder: ignore */ + else + addDataFast( gMessageStringTable.getString(varname), NULL, MVT_VARIABLE, 0); +} + +inline void LLMessageSystem::addStringFast(const char* varname, const std::string& s) +{ + if (s.size()) + addDataFast( varname, (void *)s.c_str(), MVT_VARIABLE, (S32)(s.size()) + 1); + else + addDataFast( varname, NULL, MVT_VARIABLE, 0); +} + +inline void LLMessageSystem::addString(const char* varname, const std::string& s) +{ + if (s.size()) + addDataFast( gMessageStringTable.getString(varname), (void *)s.c_str(), MVT_VARIABLE, (S32)(s.size()) + 1); + else + addDataFast( gMessageStringTable.getString(varname), NULL, MVT_VARIABLE, 0); +} + + +//----------------------------------------------------------------------------- +// Retrieval aliases +//----------------------------------------------------------------------------- +inline void LLMessageSystem::getS8Fast(const char *block, const char *var, S8 &u, S32 blocknum) +{ + getDataFast(block, var, &u, sizeof(S8), blocknum); +} + +inline void LLMessageSystem::getS8(const char *block, const char *var, S8 &u, S32 blocknum) +{ + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), &u, sizeof(S8), blocknum); +} + +inline void LLMessageSystem::getU8Fast(const char *block, const char *var, U8 &u, S32 blocknum) +{ + getDataFast(block, var, &u, sizeof(U8), blocknum); +} + +inline void LLMessageSystem::getU8(const char *block, const char *var, U8 &u, S32 blocknum) +{ + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), &u, sizeof(U8), blocknum); +} + +inline void LLMessageSystem::getBOOLFast(const char *block, const char *var, BOOL &b, S32 blocknum ) +{ + U8 value; + getDataFast(block, var, &value, sizeof(U8), blocknum); + b = (BOOL) value; +} + +inline void LLMessageSystem::getBOOL(const char *block, const char *var, BOOL &b, S32 blocknum ) +{ + U8 value; + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), &value, sizeof(U8), blocknum); + b = (BOOL) value; +} + +inline void LLMessageSystem::getS16Fast(const char *block, const char *var, S16 &d, S32 blocknum) +{ + getDataFast(block, var, &d, sizeof(S16), blocknum); +} + +inline void LLMessageSystem::getS16(const char *block, const char *var, S16 &d, S32 blocknum) +{ + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), &d, sizeof(S16), blocknum); +} + +inline void LLMessageSystem::getU16Fast(const char *block, const char *var, U16 &d, S32 blocknum) +{ + getDataFast(block, var, &d, sizeof(U16), blocknum); +} + +inline void LLMessageSystem::getU16(const char *block, const char *var, U16 &d, S32 blocknum) +{ + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), &d, sizeof(U16), blocknum); +} + +inline void LLMessageSystem::getS32Fast(const char *block, const char *var, S32 &d, S32 blocknum) +{ + getDataFast(block, var, &d, sizeof(S32), blocknum); +} + +inline void LLMessageSystem::getS32(const char *block, const char *var, S32 &d, S32 blocknum) +{ + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), &d, sizeof(S32), blocknum); +} + +inline void LLMessageSystem::getU32Fast(const char *block, const char *var, U32 &d, S32 blocknum) +{ + getDataFast(block, var, &d, sizeof(U32), blocknum); +} + +inline void LLMessageSystem::getU32(const char *block, const char *var, U32 &d, S32 blocknum) +{ + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), &d, sizeof(U32), blocknum); +} + +inline void LLMessageSystem::getU64Fast(const char *block, const char *var, U64 &d, S32 blocknum) +{ + getDataFast(block, var, &d, sizeof(U64), blocknum); +} + +inline void LLMessageSystem::getU64(const char *block, const char *var, U64 &d, S32 blocknum) +{ + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), &d, sizeof(U64), blocknum); +} + + +inline void LLMessageSystem::getIPAddrFast(const char *block, const char *var, U32 &u, S32 blocknum) +{ + getDataFast(block, var, &u, sizeof(U32), blocknum); +} + +inline void LLMessageSystem::getIPAddr(const char *block, const char *var, U32 &u, S32 blocknum) +{ + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), &u, sizeof(U32), blocknum); +} + +inline void LLMessageSystem::getIPPortFast(const char *block, const char *var, U16 &u, S32 blocknum) +{ + getDataFast(block, var, &u, sizeof(U16), blocknum); + u = ntohs(u); +} + +inline void LLMessageSystem::getIPPort(const char *block, const char *var, U16 &u, S32 blocknum) +{ + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), &u, sizeof(U16), blocknum); + u = ntohs(u); +} + + +inline void LLMessageSystem::getStringFast(const char *block, const char *var, S32 buffer_size, char *s, S32 blocknum ) +{ + s[0] = '\0'; + getDataFast(block, var, s, 0, blocknum, buffer_size); + s[buffer_size - 1] = '\0'; +} + +inline void LLMessageSystem::getString(const char *block, const char *var, S32 buffer_size, char *s, S32 blocknum ) +{ + s[0] = '\0'; + getDataFast(gMessageStringTable.getString(block), gMessageStringTable.getString(var), s, 0, blocknum, buffer_size); + s[buffer_size - 1] = '\0'; +} + +//----------------------------------------------------------------------------- +// Transmission aliases +//----------------------------------------------------------------------------- +//inline S32 LLMessageSystem::sendMessage(U32 ip, U32 port, BOOL zero_code) +//{ +// return sendMessage(LLHost(ip, port), zero_code); +//} + +//inline S32 LLMessageSystem::sendMessage(const char *ip_str, U32 port, BOOL zero_code) +//{ +// return sendMessage(LLHost(ip_str, port), zero_code); +//} + +inline S32 LLMessageSystem::sendMessage(const U32 circuit)//, BOOL zero_code) +{ + return sendMessage(findHost(circuit));//, zero_code); +} + +#endif diff --git a/indra/llmessage/message_prehash.cpp b/indra/llmessage/message_prehash.cpp new file mode 100644 index 0000000000..18be31af58 --- /dev/null +++ b/indra/llmessage/message_prehash.cpp @@ -0,0 +1,2964 @@ +/** + * @file message_prehash.cpp + * @brief file of prehashed variables + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +/** + * Generated from message template version number 1.053 + */ +#include "linden_common.h" +#include "message.h" + + + +F32 gPrehashVersionNumber = 1.053f; + +char * _PREHASH_X; +char * _PREHASH_Y; +char * _PREHASH_Z; +char * _PREHASH_AddFlags; +char * _PREHASH_ReservedNewbie; +char * _PREHASH_FailureInfo; +char * _PREHASH_MapData; +char * _PREHASH_AddItem; +char * _PREHASH_MeanCollision; +char * _PREHASH_RezScript; +char * _PREHASH_AvatarSitResponse; +char * _PREHASH_InventoryAssetResponse; +char * _PREHASH_KillObject; +char * _PREHASH_ProposalID; +char * _PREHASH_SerialNum; +char * _PREHASH_Duration; +char * _PREHASH_ScriptQuestion; +char * _PREHASH_AddCircuitCode; +char * _PREHASH_UseCircuitCode; +char * _PREHASH_ViewerCircuitCode; +char * _PREHASH_ScriptAnswerYes; +char * _PREHASH_PartnerID; +char * _PREHASH_DirLandQuery; +char * _PREHASH_TeleportStart; +char * _PREHASH_LogMessages; +char * _PREHASH_AboutText; +char * _PREHASH_VisualParam; +char * _PREHASH_GroupPrims; +char * _PREHASH_SelectedPrims; +char * _PREHASH_ID; +char * _PREHASH_UUIDNameRequest; +char * _PREHASH_UUIDGroupNameRequest; +char * _PREHASH_MoneyTransactionsRequest; +char * _PREHASH_GroupAccountTransactionsRequest; +char * _PREHASH_MapNameRequest; +char * _PREHASH_MailTaskSimRequest; +char * _PREHASH_UpdateSimulator; +char * _PREHASH_BillableFactor; +char * _PREHASH_ObjectBonusFactor; +char * _PREHASH_EnableSimulator; +char * _PREHASH_DisableSimulator; +char * _PREHASH_ConfirmEnableSimulator; +char * _PREHASH_LayerType; +char * _PREHASH_OwnerRole; +char * _PREHASH_ParcelOverlay; +char * _PREHASH_AdjustBalance; +char * _PREHASH_GroupOwned; +char * _PREHASH_IP; +char * _PREHASH_ChatFromViewer; +char * _PREHASH_AvgAgentsInView; +char * _PREHASH_AgentsInView; +char * _PREHASH_GroupTitle; +char * _PREHASH_MapLayerReply; +char * _PREHASH_CompoundMsgID; +char * _PREHASH_CameraConstraint; +char * _PREHASH_DownloadTotals; +char * _PREHASH_GenCounter; +char * _PREHASH_FrozenData; +char * _PREHASH_ChildAgentDying; +char * _PREHASH_To; +char * _PREHASH_CopyInventoryFromNotecard; +char * _PREHASH_RezObjectFromNotecard; +char * _PREHASH_ParcelDirFeeCurrent; +char * _PREHASH_SeedCapability; +char * _PREHASH_ObjectDuplicate; +char * _PREHASH_InventoryData; +char * _PREHASH_ReplyData; +char * _PREHASH_ResetList; +char * _PREHASH_MediaID; +char * _PREHASH_RelatedRights; +char * _PREHASH_RedirectGridX; +char * _PREHASH_RedirectGridY; +char * _PREHASH_TransferID; +char * _PREHASH_Transacted; +char * _PREHASH_TexturesChanged; +char * _PREHASH_UserLookAt; +char * _PREHASH_TestBlock1; +char * _PREHASH_SensedData; +char * _PREHASH_UpdateBlock; +char * _PREHASH_ClassifiedGodDelete; +char * _PREHASH_ObjectGrabUpdate; +char * _PREHASH_TaxDate; +char * _PREHASH_LocationPos; +char * _PREHASH_StartDateTime; +char * _PREHASH_ObjectUpdateCached; +char * _PREHASH_Packets; +char * _PREHASH_FailureType; +char * _PREHASH_UpdateGroupInfo; +char * _PREHASH_ObjectPermissions; +char * _PREHASH_RevokePermissions; +char * _PREHASH_UpdateFlags; +char * _PREHASH_ObjectExportSelected; +char * _PREHASH_RezSelected; +char * _PREHASH_AutoPilot; +char * _PREHASH_UpdateMuteListEntry; +char * _PREHASH_RemoveMuteListEntry; +char * _PREHASH_SetSimStatusInDatabase; +char * _PREHASH_SetSimPresenceInDatabase; +char * _PREHASH_CameraProperty; +char * _PREHASH_BrushSize; +char * _PREHASH_StartExpungeProcess; +char * _PREHASH_SimulatorSetMap; +char * _PREHASH_RegionPresenceRequestByRegionID; +char * _PREHASH_ParcelObjectOwnersReply; +char * _PREHASH_GroupMembersReply; +char * _PREHASH_GroupRoleMembersReply; +char * _PREHASH_RequestRegionInfo; +char * _PREHASH_AABBMax; +char * _PREHASH_RequestPayPrice; +char * _PREHASH_SimulatorPresentAtLocation; +char * _PREHASH_AgentRequestSit; +char * _PREHASH_AABBMin; +char * _PREHASH_ClassifiedFlags; +char * _PREHASH_ControlFlags; +char * _PREHASH_TeleportRequest; +char * _PREHASH_SpaceLocationTeleportRequest; +char * _PREHASH_LeaderBoardRequest; +char * _PREHASH_ScriptTeleportRequest; +char * _PREHASH_DateUTC; +char * _PREHASH_TaskIDs; +char * _PREHASH_EstateCovenantRequest; +char * _PREHASH_RequestResult; +char * _PREHASH_ReputationAgentAssign; +char * _PREHASH_CanAcceptAgents; +char * _PREHASH_ObjectSaleInfo; +char * _PREHASH_KillChildAgents; +char * _PREHASH_Balance; +char * _PREHASH_DerezContainer; +char * _PREHASH_ObjectData; +char * _PREHASH_CameraAtAxis; +char * _PREHASH_InfoBlock; +char * _PREHASH_OwnershipCost; +char * _PREHASH_AvatarNotesUpdate; +char * _PREHASH_PID; +char * _PREHASH_TimeString; +char * _PREHASH_DirPopularReply; +char * _PREHASH_TerrainHeightRange00; +char * _PREHASH_SimData; +char * _PREHASH_TerrainHeightRange01; +char * _PREHASH_TerrainHeightRange10; +char * _PREHASH_TerrainHeightRange11; +char * _PREHASH_UpdateInventoryItem; +char * _PREHASH_UpdateCreateInventoryItem; +char * _PREHASH_MoveInventoryItem; +char * _PREHASH_CopyInventoryItem; +char * _PREHASH_RemoveInventoryItem; +char * _PREHASH_CreateInventoryItem; +char * _PREHASH_PathTwistBegin; +char * _PREHASH_CRC; +char * _PREHASH_AttachmentPoint; +char * _PREHASH_TelehubBlock; +char * _PREHASH_FOVBlock; +char * _PREHASH_StartLocationData; +char * _PREHASH_PositionData; +char * _PREHASH_TimeSinceLast; +char * _PREHASH_MapImage; +char * _PREHASH_Objects; +char * _PREHASH_URL; +char * _PREHASH_CreationDate; +char * _PREHASH_JointPivot; +char * _PREHASH_RateeID; +char * _PREHASH_FPS; +char * _PREHASH_HasTelehub; +char * _PREHASH_PathEnd; +char * _PREHASH_ScriptDataReply; +char * _PREHASH_MapBlockReply; +char * _PREHASH_PropertiesData; +char * _PREHASH_ViewerEffect; +char * _PREHASH_FreezeUser; +char * _PREHASH_OwnerPrims; +char * _PREHASH_ObjectGrab; +char * _PREHASH_ToAgentID; +char * _PREHASH_SimulatorMapUpdate; +char * _PREHASH_TransferPacket; +char * _PREHASH_ObjectName; +char * _PREHASH_GroupPowers; +char * _PREHASH_OriginalName; +char * _PREHASH_CompletePingCheck; +char * _PREHASH_OnlineStatus; +char * _PREHASH_ObjectDrop; +char * _PREHASH_UseBigPackets; +char * _PREHASH_GroupNoticesListReply; +char * _PREHASH_ParcelAccessListReply; +char * _PREHASH_RpcChannelReply; +char * _PREHASH_RegionPresenceResponse; +char * _PREHASH_AgentPresenceResponse; +char * _PREHASH_CharterMember; +char * _PREHASH_EdgeData; +char * _PREHASH_NameData; +char * _PREHASH_RegionPushOverride; +char * _PREHASH_SimName; +char * _PREHASH_UserReport; +char * _PREHASH_DownloadPriority; +char * _PREHASH_ToAgentId; +char * _PREHASH_Mag; +char * _PREHASH_DirPopularQuery; +char * _PREHASH_ParcelPropertiesRequestByID; +char * _PREHASH_ObjectLink; +char * _PREHASH_RpcScriptReplyInbound; +char * _PREHASH_BoardData; +char * _PREHASH_RezData; +char * _PREHASH_RemoveInventoryObjects; +char * _PREHASH_GroupProposalBallot; +char * _PREHASH_RPCServerIP; +char * _PREHASH_Far; +char * _PREHASH_GodSessionID; +char * _PREHASH_ViewerDigest; +char * _PREHASH_FLAboutText; +char * _PREHASH_RegionHandshakeReply; +char * _PREHASH_GroupActiveProposalItemReply; +char * _PREHASH_MapItemReply; +char * _PREHASH_Seconds; +char * _PREHASH_UpdateUserInfo; +char * _PREHASH_AggregatePermTexturesOwner; +char * _PREHASH_Set; +char * _PREHASH_NewName; +char * _PREHASH_Key; +char * _PREHASH_AgentID; +char * _PREHASH_OnlineStatusRequest; +char * _PREHASH_EventNotificationRemoveRequest; +char * _PREHASH_NewFolderID; +char * _PREHASH_Arc; +char * _PREHASH_RegionX; +char * _PREHASH_RegionY; +char * _PREHASH_RequestData; +char * _PREHASH_Msg; +char * _PREHASH_Top; +char * _PREHASH_MiscStats; +char * _PREHASH_ImageID; +char * _PREHASH_DataPacket; +char * _PREHASH_ObjectDehinge; +char * _PREHASH_You; +char * _PREHASH_ScriptControlChange; +char * _PREHASH_LoadURL; +char * _PREHASH_SetCPURatio; +char * _PREHASH_NameValueData; +char * _PREHASH_AtomicPassObject; +char * _PREHASH_ErrorMessage; +char * _PREHASH_ViewerFrozenMessage; +char * _PREHASH_HealthMessage; +char * _PREHASH_LogTextMessage; +char * _PREHASH_TimeDilation; +char * _PREHASH_RemoveContribution; +char * _PREHASH_Contribution; +char * _PREHASH_SetGroupContribution; +char * _PREHASH_Offline; +char * _PREHASH_AgentIsNowWearing; +char * _PREHASH_SecPerDay; +char * _PREHASH_Members; +char * _PREHASH_FailedResends; +char * _PREHASH_CameraCenter; +char * _PREHASH_CameraLeftAxis; +char * _PREHASH_ExBlock; +char * _PREHASH_Channel; +char * _PREHASH_NetTest; +char * _PREHASH_DiscardLevel; +char * _PREHASH_LayerID; +char * _PREHASH_RatorID; +char * _PREHASH_GrabOffset; +char * _PREHASH_SimPort; +char * _PREHASH_PricePerMeter; +char * _PREHASH_RegionFlags; +char * _PREHASH_VoteResult; +char * _PREHASH_ParcelDirFeeEstimate; +char * _PREHASH_ModifyBlock; +char * _PREHASH_InventoryBlock; +char * _PREHASH_ReplyBlock; +char * _PREHASH_ValidUntil; +char * _PREHASH_VelocityInterpolateOn; +char * _PREHASH_ClassifiedDelete; +char * _PREHASH_RegionDenyAnonymous; +char * _PREHASH_FLImageID; +char * _PREHASH_AllowPublish; +char * _PREHASH_SitName; +char * _PREHASH_RegionsVisited; +char * _PREHASH_DirClassifiedReply; +char * _PREHASH_AvatarClassifiedReply; +char * _PREHASH_ReputationIndividualReply; +char * _PREHASH_MediaURL; +char * _PREHASH_CompleteAgentMovement; +char * _PREHASH_SpaceIP; +char * _PREHASH_ClassifiedID; +char * _PREHASH_LocalID; +char * _PREHASH_RemoveItem; +char * _PREHASH_LogFailedMoneyTransaction; +char * _PREHASH_ViewerStartAuction; +char * _PREHASH_StartAuction; +char * _PREHASH_NameValueName; +char * _PREHASH_AngVelX; +char * _PREHASH_DuplicateFlags; +char * _PREHASH_AngVelY; +char * _PREHASH_AngVelZ; +char * _PREHASH_TextColor; +char * _PREHASH_SlaveID; +char * _PREHASH_Charter; +char * _PREHASH_AlertData; +char * _PREHASH_TargetBlock; +char * _PREHASH_CheckParcelAuctions; +char * _PREHASH_ParcelAuctions; +char * _PREHASH_OwnerIsGroup; +char * _PREHASH_NameValuePair; +char * _PREHASH_RemoveNameValuePair; +char * _PREHASH_GetNameValuePair; +char * _PREHASH_BulkUpdateInventory; +char * _PREHASH_UpdateTaskInventory; +char * _PREHASH_RemoveTaskInventory; +char * _PREHASH_MoveTaskInventory; +char * _PREHASH_RequestTaskInventory; +char * _PREHASH_ReplyTaskInventory; +char * _PREHASH_DeclineInventory; +char * _PREHASH_AggregatePermInventory; +char * _PREHASH_SimulatorInfo; +char * _PREHASH_MoneyTransactionsReply; +char * _PREHASH_GroupAccountTransactionsReply; +char * _PREHASH_MailTaskSimReply; +char * _PREHASH_WearableData; +char * _PREHASH_StatisticsData; +char * _PREHASH_Enabled; +char * _PREHASH_Savings; +char * _PREHASH_SimulatorLoad; +char * _PREHASH_InternalRegionIP; +char * _PREHASH_ExternalRegionIP; +char * _PREHASH_TotalPairs; +char * _PREHASH_CreateGroupRequest; +char * _PREHASH_JoinGroupRequest; +char * _PREHASH_LeaveGroupRequest; +char * _PREHASH_InviteGroupRequest; +char * _PREHASH_LiveHelpGroupRequest; +char * _PREHASH_ServerVersion; +char * _PREHASH_PriceParcelClaimFactor; +char * _PREHASH_BillableArea; +char * _PREHASH_ObjectID; +char * _PREHASH_ObjectFlagUpdate; +char * _PREHASH_GroupRoleUpdate; +char * _PREHASH_RequestInventoryAsset; +char * _PREHASH_RedoLand; +char * _PREHASH_TravelAccess; +char * _PREHASH_ChangedGrid; +char * _PREHASH_AgentDropGroup; +char * _PREHASH_Details; +char * _PREHASH_LocationX; +char * _PREHASH_SaleType; +char * _PREHASH_LocationY; +char * _PREHASH_LocationZ; +char * _PREHASH_EconomyData; +char * _PREHASH_HeadRotation; +char * _PREHASH_DeleteOnCompletion; +char * _PREHASH_PublicPort; +char * _PREHASH_DirClassifiedQuery; +char * _PREHASH_CallbackID; +char * _PREHASH_RequestParcelTransfer; +char * _PREHASH_RoleCount; +char * _PREHASH_ObjectCapacity; +char * _PREHASH_RequestID; +char * _PREHASH_RequestXfer; +char * _PREHASH_ObjectTaxCurrent; +char * _PREHASH_LightTaxCurrent; +char * _PREHASH_LandTaxCurrent; +char * _PREHASH_GroupTaxCurrent; +char * _PREHASH_FetchInventoryDescendents; +char * _PREHASH_InventoryDescendents; +char * _PREHASH_Descendents; +char * _PREHASH_PurgeInventoryDescendents; +char * _PREHASH_ShowDir; +char * _PREHASH_IsOwner; +char * _PREHASH_Timestamp; +char * _PREHASH_GlobalPos; +char * _PREHASH_GrabOffsetInitial; +char * _PREHASH_IsTrial; +char * _PREHASH_FinalizeLogout; +char * _PREHASH_ObjectDuplicateOnRay; +char * _PREHASH_GroupMembershipCount; +char * _PREHASH_MethodData; +char * _PREHASH_ActivateGestures; +char * _PREHASH_DeactivateGestures; +char * _PREHASH_ProposalData; +char * _PREHASH_PosGlobal; +char * _PREHASH_SearchID; +char * _PREHASH_RezMultipleAttachmentsFromInv; +char * _PREHASH_SearchName; +char * _PREHASH_VersionString; +char * _PREHASH_CreateGroupReply; +char * _PREHASH_LeaveGroupReply; +char * _PREHASH_ActualArea; +char * _PREHASH_Message; +char * _PREHASH_ClickAction; +char * _PREHASH_AssetUploadComplete; +char * _PREHASH_RequestType; +char * _PREHASH_UUID; +char * _PREHASH_BaseMask; +char * _PREHASH_NetBlock; +char * _PREHASH_GlobalX; +char * _PREHASH_GlobalY; +char * _PREHASH_CopyRotates; +char * _PREHASH_KickUserAck; +char * _PREHASH_TopPick; +char * _PREHASH_SessionID; +char * _PREHASH_GlobalZ; +char * _PREHASH_DeclineFriendship; +char * _PREHASH_FormFriendship; +char * _PREHASH_TerminateFriendship; +char * _PREHASH_TaskData; +char * _PREHASH_SimWideMaxPrims; +char * _PREHASH_TotalPrims; +char * _PREHASH_SourceFilename; +char * _PREHASH_ProfileBegin; +char * _PREHASH_MoneyDetailsRequest; +char * _PREHASH_Request; +char * _PREHASH_GroupAccountDetailsRequest; +char * _PREHASH_GroupActiveProposalsRequest; +char * _PREHASH_StringValue; +char * _PREHASH_ClosestSimulator; +char * _PREHASH_Version; +char * _PREHASH_OtherCount; +char * _PREHASH_MemberCount; +char * _PREHASH_ChatData; +char * _PREHASH_IsGroupOwned; +char * _PREHASH_EnergyEfficiency; +char * _PREHASH_MaxPlace; +char * _PREHASH_PickInfoUpdate; +char * _PREHASH_PickDelete; +char * _PREHASH_ScriptReset; +char * _PREHASH_Requester; +char * _PREHASH_ForSale; +char * _PREHASH_NearestLandingRegionReply; +char * _PREHASH_RecordAgentPresence; +char * _PREHASH_EraseAgentPresence; +char * _PREHASH_ParcelID; +char * _PREHASH_Godlike; +char * _PREHASH_TotalDebits; +char * _PREHASH_Direction; +char * _PREHASH_Appearance; +char * _PREHASH_HealthData; +char * _PREHASH_LeftAxis; +char * _PREHASH_LocationBlock; +char * _PREHASH_ObjectImage; +char * _PREHASH_TerrainStartHeight00; +char * _PREHASH_TerrainStartHeight01; +char * _PREHASH_TerrainStartHeight10; +char * _PREHASH_ObjectHinge; +char * _PREHASH_TerrainStartHeight11; +char * _PREHASH_MetersPerGrid; +char * _PREHASH_WaterHeight; +char * _PREHASH_FetchInventoryReply; +char * _PREHASH_MoneySummaryReply; +char * _PREHASH_GroupAccountSummaryReply; +char * _PREHASH_AttachedSound; +char * _PREHASH_ParamInUse; +char * _PREHASH_GodKickUser; +char * _PREHASH_PickName; +char * _PREHASH_TaskName; +char * _PREHASH_ParcelGodReserveForNewbie; +char * _PREHASH_SubType; +char * _PREHASH_ObjectCount; +char * _PREHASH_RegionPresenceRequestByHandle; +char * _PREHASH_RezSingleAttachmentFromInv; +char * _PREHASH_ChildAgentUpdate; +char * _PREHASH_ToID; +char * _PREHASH_ViewerPort; +char * _PREHASH_IsOwnerGroup; +char * _PREHASH_AgentHeightWidth; +char * _PREHASH_VerticalAngle; +char * _PREHASH_WearableType; +char * _PREHASH_AggregatePermNextOwner; +char * _PREHASH_ShowInList; +char * _PREHASH_PositionSuggestion; +char * _PREHASH_UpdateParcel; +char * _PREHASH_ClearAgentSessions; +char * _PREHASH_SetAlwaysRun; +char * _PREHASH_NVPair; +char * _PREHASH_ObjectSpinStart; +char * _PREHASH_UseEstateSun; +char * _PREHASH_LogoutBlock; +char * _PREHASH_RelayLogControl; +char * _PREHASH_RegionID; +char * _PREHASH_Creator; +char * _PREHASH_ProposalText; +char * _PREHASH_DirEventsReply; +char * _PREHASH_EventInfoReply; +char * _PREHASH_UserInfoReply; +char * _PREHASH_PathRadiusOffset; +char * _PREHASH_SessionInfo; +char * _PREHASH_TextureData; +char * _PREHASH_ChatPass; +char * _PREHASH_TargetID; +char * _PREHASH_DefaultPayPrice; +char * _PREHASH_UserLocation; +char * _PREHASH_MaxPrims; +char * _PREHASH_RegionIP; +char * _PREHASH_LandmarkID; +char * _PREHASH_InitiateDownload; +char * _PREHASH_Name; +char * _PREHASH_OtherCleanTime; +char * _PREHASH_ParcelSetOtherCleanTime; +char * _PREHASH_TeleportPriceExponent; +char * _PREHASH_Gain; +char * _PREHASH_VelX; +char * _PREHASH_PacketAck; +char * _PREHASH_PathSkew; +char * _PREHASH_Negative; +char * _PREHASH_VelY; +char * _PREHASH_SimulatorShutdownRequest; +char * _PREHASH_NearestLandingRegionRequest; +char * _PREHASH_VelZ; +char * _PREHASH_OtherID; +char * _PREHASH_MemberID; +char * _PREHASH_MapLayerRequest; +char * _PREHASH_PatchVersion; +char * _PREHASH_ObjectScale; +char * _PREHASH_TargetIP; +char * _PREHASH_Redo; +char * _PREHASH_MoneyBalance; +char * _PREHASH_TrackAgent; +char * _PREHASH_MaxX; +char * _PREHASH_Data; +char * _PREHASH_MaxY; +char * _PREHASH_TextureAnim; +char * _PREHASH_ReturnIDs; +char * _PREHASH_Date; +char * _PREHASH_GestureUpdate; +char * _PREHASH_AgentWearablesUpdate; +char * _PREHASH_AgentDataUpdate; +char * _PREHASH_Hash; +char * _PREHASH_GroupDataUpdate; +char * _PREHASH_AgentGroupDataUpdate; +char * _PREHASH_Left; +char * _PREHASH_Mask; +char * _PREHASH_ForceMouselook; +char * _PREHASH_Success; +char * _PREHASH_ObjectGroup; +char * _PREHASH_SunHour; +char * _PREHASH_MinX; +char * _PREHASH_ScriptSensorReply; +char * _PREHASH_MinY; +char * _PREHASH_Command; +char * _PREHASH_Desc; +char * _PREHASH_AttachmentNeedsSave; +char * _PREHASH_HistoryItemData; +char * _PREHASH_AgentCachedTexture; +char * _PREHASH_Subject; +char * _PREHASH_East; +char * _PREHASH_GodExpungeUser; +char * _PREHASH_QueryReplies; +char * _PREHASH_ObjectCategory; +char * _PREHASH_Time; +char * _PREHASH_CreateLandmarkForEvent; +char * _PREHASH_ParentID; +char * _PREHASH_Ping; +char * _PREHASH_Perp; +char * _PREHASH_Code; +char * _PREHASH_InvType; +char * _PREHASH_AgentFOV; +char * _PREHASH_BulkMoneyTransfer; +char * _PREHASH_Audible; +char * _PREHASH_AuctionData; +char * _PREHASH_IDBlock; +char * _PREHASH_ReputationData; +char * _PREHASH_West; +char * _PREHASH_Undo; +char * _PREHASH_TotalNumItems; +char * _PREHASH_Info; +char * _PREHASH_Area; +char * _PREHASH_Behavior; +char * _PREHASH_SimCrashed; +char * _PREHASH_Text; +char * _PREHASH_AgentToNewRegion; +char * _PREHASH_PriceGroupCreate; +char * _PREHASH_ObjectShape; +char * _PREHASH_GroupRoleDataReply; +char * _PREHASH_PosX; +char * _PREHASH_PosY; +char * _PREHASH_MuteCRC; +char * _PREHASH_PosZ; +char * _PREHASH_Size; +char * _PREHASH_FromAddress; +char * _PREHASH_Body; +char * _PREHASH_FileData; +char * _PREHASH_List; +char * _PREHASH_KickUser; +char * _PREHASH_OtherPrims; +char * _PREHASH_RunTime; +char * _PREHASH_GrantUserRights; +char * _PREHASH_RpcScriptRequestInboundForward; +char * _PREHASH_More; +char * _PREHASH_Majority; +char * _PREHASH_MetersTraveled; +char * _PREHASH_Stat; +char * _PREHASH_SoundID; +char * _PREHASH_Item; +char * _PREHASH_User; +char * _PREHASH_RemoteInfos; +char * _PREHASH_Prey; +char * _PREHASH_UsecSinceStart; +char * _PREHASH_RayStart; +char * _PREHASH_ParcelData; +char * _PREHASH_CameraUpAxis; +char * _PREHASH_ScriptDialog; +char * _PREHASH_MasterParcelData; +char * _PREHASH_Invalid; +char * _PREHASH_MinPlace; +char * _PREHASH_ProfileCurve; +char * _PREHASH_ParcelAccessListUpdate; +char * _PREHASH_MuteListUpdate; +char * _PREHASH_SendPacket; +char * _PREHASH_SendXferPacket; +char * _PREHASH_RegionDenyIdentified; +char * _PREHASH_NotecardItemID; +char * _PREHASH_LastName; +char * _PREHASH_From; +char * _PREHASH_RoleChange; +char * _PREHASH_Port; +char * _PREHASH_MemberTitle; +char * _PREHASH_LogParcelChanges; +char * _PREHASH_AgentCachedTextureResponse; +char * _PREHASH_DeRezObject; +char * _PREHASH_IsTemporary; +char * _PREHASH_InsigniaID; +char * _PREHASH_CheckFlags; +char * _PREHASH_TransferPriority; +char * _PREHASH_EventID; +char * _PREHASH_Selected; +char * _PREHASH_FromAgentId; +char * _PREHASH_Type; +char * _PREHASH_ChatType; +char * _PREHASH_ReportData; +char * _PREHASH_LeaderBoardData; +char * _PREHASH_RequestBlock; +char * _PREHASH_GrantData; +char * _PREHASH_DetachAttachmentIntoInv; +char * _PREHASH_ParcelDisableObjects; +char * _PREHASH_Sections; +char * _PREHASH_GodLevel; +char * _PREHASH_PayPriceReply; +char * _PREHASH_QueryID; +char * _PREHASH_CameraEyeOffset; +char * _PREHASH_AgentPosition; +char * _PREHASH_GrabPosition; +char * _PREHASH_OnlineNotification; +char * _PREHASH_OfflineNotification; +char * _PREHASH_SendPostcard; +char * _PREHASH_RequestFlags; +char * _PREHASH_MoneyHistoryRequest; +char * _PREHASH_MoneySummaryRequest; +char * _PREHASH_GroupAccountSummaryRequest; +char * _PREHASH_GroupVoteHistoryRequest; +char * _PREHASH_ParamValue; +char * _PREHASH_Checksum; +char * _PREHASH_MaxAgents; +char * _PREHASH_CreateNewOutfitAttachments; +char * _PREHASH_RegionHandle; +char * _PREHASH_TeleportProgress; +char * _PREHASH_AgentQuitCopy; +char * _PREHASH_ToViewer; +char * _PREHASH_AvatarInterestsUpdate; +char * _PREHASH_GroupNoticeID; +char * _PREHASH_ParcelName; +char * _PREHASH_PriceObjectRent; +char * _PREHASH_ConnectAgentToUserserver; +char * _PREHASH_ConnectToUserserver; +char * _PREHASH_OfferCallingCard; +char * _PREHASH_AgentAccess; +char * _PREHASH_AcceptCallingCard; +char * _PREHASH_DeclineCallingCard; +char * _PREHASH_DataHomeLocationReply; +char * _PREHASH_EventLocationReply; +char * _PREHASH_TerseDateID; +char * _PREHASH_ObjectOwner; +char * _PREHASH_AssetID; +char * _PREHASH_AlertMessage; +char * _PREHASH_AgentAlertMessage; +char * _PREHASH_EstateOwnerMessage; +char * _PREHASH_ParcelMediaCommandMessage; +char * _PREHASH_Auction; +char * _PREHASH_Category; +char * _PREHASH_FilePath; +char * _PREHASH_ItemFlags; +char * _PREHASH_Invoice; +char * _PREHASH_IntervalDays; +char * _PREHASH_PathScaleX; +char * _PREHASH_FromTaskID; +char * _PREHASH_TimeInfo; +char * _PREHASH_PathScaleY; +char * _PREHASH_PublicCount; +char * _PREHASH_ParcelJoin; +char * _PREHASH_GroupRolesCount; +char * _PREHASH_SimulatorBlock; +char * _PREHASH_GroupID; +char * _PREHASH_AgentVel; +char * _PREHASH_RequestImage; +char * _PREHASH_NetStats; +char * _PREHASH_AgentPos; +char * _PREHASH_AgentSit; +char * _PREHASH_Material; +char * _PREHASH_ObjectDeGrab; +char * _PREHASH_VelocityInterpolateOff; +char * _PREHASH_AuthorizedBuyerID; +char * _PREHASH_AvatarPropertiesReply; +char * _PREHASH_GroupProfileReply; +char * _PREHASH_SimOwner; +char * _PREHASH_SalePrice; +char * _PREHASH_Animation; +char * _PREHASH_OwnerID; +char * _PREHASH_NearestLandingRegionUpdated; +char * _PREHASH_PassToAgent; +char * _PREHASH_PreyAgent; +char * _PREHASH_SimStats; +char * _PREHASH_Options; +char * _PREHASH_LogoutReply; +char * _PREHASH_FeatureDisabled; +char * _PREHASH_ObjectLocalID; +char * _PREHASH_Dropped; +char * _PREHASH_WebProfilesDisabled; +char * _PREHASH_Destination; +char * _PREHASH_MasterID; +char * _PREHASH_TransferData; +char * _PREHASH_WantToMask; +char * _PREHASH_AvatarData; +char * _PREHASH_ParcelSelectObjects; +char * _PREHASH_ExtraParams; +char * _PREHASH_LogLogin; +char * _PREHASH_CreatorID; +char * _PREHASH_Summary; +char * _PREHASH_BuyObjectInventory; +char * _PREHASH_FetchInventory; +char * _PREHASH_InventoryID; +char * _PREHASH_PacketNumber; +char * _PREHASH_SetFollowCamProperties; +char * _PREHASH_ClearFollowCamProperties; +char * _PREHASH_SequenceID; +char * _PREHASH_DataServerLogout; +char * _PREHASH_NameValue; +char * _PREHASH_PathShearX; +char * _PREHASH_PathShearY; +char * _PREHASH_Velocity; +char * _PREHASH_SecPerYear; +char * _PREHASH_FirstName; +char * _PREHASH_AttachedSoundGainChange; +char * _PREHASH_LocationID; +char * _PREHASH_Running; +char * _PREHASH_AgentThrottle; +char * _PREHASH_NeighborList; +char * _PREHASH_PathTaperX; +char * _PREHASH_PathTaperY; +char * _PREHASH_AgentRelated; +char * _PREHASH_GranterBlock; +char * _PREHASH_UseCachedMuteList; +char * _PREHASH_FailStats; +char * _PREHASH_Tempfile; +char * _PREHASH_BuyerID; +char * _PREHASH_DirPeopleReply; +char * _PREHASH_TransferInfo; +char * _PREHASH_AvatarPickerRequestBackend; +char * _PREHASH_AvatarPropertiesRequestBackend; +char * _PREHASH_UpdateData; +char * _PREHASH_SimFPS; +char * _PREHASH_ReporterID; +char * _PREHASH_ButtonLabel; +char * _PREHASH_GranterID; +char * _PREHASH_WantToText; +char * _PREHASH_ReportType; +char * _PREHASH_DataBlock; +char * _PREHASH_SimulatorReady; +char * _PREHASH_AnimationSourceList; +char * _PREHASH_SubscribeLoad; +char * _PREHASH_UnsubscribeLoad; +char * _PREHASH_Packet; +char * _PREHASH_UndoLand; +char * _PREHASH_SimAccess; +char * _PREHASH_MembershipFee; +char * _PREHASH_InviteGroupResponse; +char * _PREHASH_CreateInventoryFolder; +char * _PREHASH_UpdateInventoryFolder; +char * _PREHASH_MoveInventoryFolder; +char * _PREHASH_RemoveInventoryFolder; +char * _PREHASH_MoneyData; +char * _PREHASH_ObjectDeselect; +char * _PREHASH_NewAssetID; +char * _PREHASH_ObjectAdd; +char * _PREHASH_RayEndIsIntersection; +char * _PREHASH_CompleteAuction; +char * _PREHASH_CircuitCode; +char * _PREHASH_AgentMovementComplete; +char * _PREHASH_ViewerIP; +char * _PREHASH_Header; +char * _PREHASH_GestureFlags; +char * _PREHASH_XferID; +char * _PREHASH_StatValue; +char * _PREHASH_PickID; +char * _PREHASH_TaskID; +char * _PREHASH_GridsPerEdge; +char * _PREHASH_RayEnd; +char * _PREHASH_Throttles; +char * _PREHASH_RebakeAvatarTextures; +char * _PREHASH_UpAxis; +char * _PREHASH_AgentTextures; +char * _PREHASH_NotecardData; +char * _PREHASH_Radius; +char * _PREHASH_OffCircuit; +char * _PREHASH_Access; +char * _PREHASH_TitleRoleID; +char * _PREHASH_SquareMetersCredit; +char * _PREHASH_Filename; +char * _PREHASH_SecuredTemplateChecksumRequest; +char * _PREHASH_TemplateChecksumRequest; +char * _PREHASH_AgentPresenceRequest; +char * _PREHASH_ClassifiedInfoRequest; +char * _PREHASH_ParcelInfoRequest; +char * _PREHASH_ParcelObjectOwnersRequest; +char * _PREHASH_TeleportLandmarkRequest; +char * _PREHASH_EventInfoRequest; +char * _PREHASH_ChatFromSimulator; +char * _PREHASH_PickInfoRequest; +char * _PREHASH_MoneyBalanceRequest; +char * _PREHASH_GroupMembersRequest; +char * _PREHASH_GroupRoleMembersRequest; +char * _PREHASH_OldFolderID; +char * _PREHASH_UserInfoRequest; +char * _PREHASH_TextureID; +char * _PREHASH_ProfileURL; +char * _PREHASH_Handle; +char * _PREHASH_StartParcelRenameAck; +char * _PREHASH_ButtonIndex; +char * _PREHASH_GetScriptRunning; +char * _PREHASH_SetScriptRunning; +char * _PREHASH_Health; +char * _PREHASH_FileID; +char * _PREHASH_CircuitInfo; +char * _PREHASH_ObjectBuy; +char * _PREHASH_ProfileEnd; +char * _PREHASH_Effect; +char * _PREHASH_TestMessage; +char * _PREHASH_ScriptMailRegistration; +char * _PREHASH_AgentSetAppearance; +char * _PREHASH_AvatarAppearance; +char * _PREHASH_RegionData; +char * _PREHASH_RequestingRegionData; +char * _PREHASH_LandingRegionData; +char * _PREHASH_SitTransform; +char * _PREHASH_TerrainBase0; +char * _PREHASH_SkillsMask; +char * _PREHASH_AtAxis; +char * _PREHASH_TerrainBase1; +char * _PREHASH_Reason; +char * _PREHASH_TerrainBase2; +char * _PREHASH_TerrainBase3; +char * _PREHASH_Params; +char * _PREHASH_PingID; +char * _PREHASH_Change; +char * _PREHASH_Height; +char * _PREHASH_Region; +char * _PREHASH_MoneyHistoryReply; +char * _PREHASH_TelehubInfo; +char * _PREHASH_StateSave; +char * _PREHASH_RoleData; +char * _PREHASH_AgentAnimation; +char * _PREHASH_AvatarAnimation; +char * _PREHASH_LogDwellTime; +char * _PREHASH_ParcelGodMarkAsContent; +char * _PREHASH_UsePhysics; +char * _PREHASH_RegionDenyTransacted; +char * _PREHASH_JointType; +char * _PREHASH_TaxEstimate; +char * _PREHASH_ObjectTaxEstimate; +char * _PREHASH_LightTaxEstimate; +char * _PREHASH_TeleportLandingStatusChanged; +char * _PREHASH_LandTaxEstimate; +char * _PREHASH_GroupTaxEstimate; +char * _PREHASH_AvgViewerFPS; +char * _PREHASH_Buttons; +char * _PREHASH_Sender; +char * _PREHASH_Dialog; +char * _PREHASH_TargetData; +char * _PREHASH_DestID; +char * _PREHASH_PricePublicObjectDelete; +char * _PREHASH_ObjectDelete; +char * _PREHASH_Delete; +char * _PREHASH_EventGodDelete; +char * _PREHASH_LastTaxDate; +char * _PREHASH_MapImageID; +char * _PREHASH_EndDateTime; +char * _PREHASH_TerrainDetail0; +char * _PREHASH_TerrainDetail1; +char * _PREHASH_TerrainDetail2; +char * _PREHASH_TerrainDetail3; +char * _PREHASH_Offset; +char * _PREHASH_ObjectDelink; +char * _PREHASH_TargetObject; +char * _PREHASH_IsEstateManager; +char * _PREHASH_CancelAuction; +char * _PREHASH_ObjectDetach; +char * _PREHASH_Compressed; +char * _PREHASH_PathBegin; +char * _PREHASH_BypassRaycast; +char * _PREHASH_WinnerID; +char * _PREHASH_ChannelType; +char * _PREHASH_NonExemptMembers; +char * _PREHASH_Agents; +char * _PREHASH_SimulatorStart; +char * _PREHASH_Enable; +char * _PREHASH_MemberData; +char * _PREHASH_ToGroupID; +char * _PREHASH_ImageNotInDatabase; +char * _PREHASH_StartDate; +char * _PREHASH_AnimID; +char * _PREHASH_Serial; +char * _PREHASH_ControlPort; +char * _PREHASH_ModifyLand; +char * _PREHASH_Digest; +char * _PREHASH_Victim; +char * _PREHASH_Script; +char * _PREHASH_TemplateChecksumReply; +char * _PREHASH_PickInfoReply; +char * _PREHASH_MoneyBalanceReply; +char * _PREHASH_RoutedMoneyBalanceReply; +char * _PREHASH_RoleID; +char * _PREHASH_RegionInfo; +char * _PREHASH_Sequence; +char * _PREHASH_GodUpdateRegionInfo; +char * _PREHASH_LocalX; +char * _PREHASH_LocalY; +char * _PREHASH_StartAnim; +char * _PREHASH_Location; +char * _PREHASH_Action; +char * _PREHASH_Rights; +char * _PREHASH_SearchDir; +char * _PREHASH_Active; +char * _PREHASH_TransferRequest; +char * _PREHASH_ScriptSensorRequest; +char * _PREHASH_MoneyTransferRequest; +char * _PREHASH_EjectGroupMemberRequest; +char * _PREHASH_SkillsText; +char * _PREHASH_Resent; +char * _PREHASH_Center; +char * _PREHASH_SharedData; +char * _PREHASH_PSBlock; +char * _PREHASH_UUIDNameBlock; +char * _PREHASH_Viewer; +char * _PREHASH_GroupNoticeDelete; +char * _PREHASH_GroupTitleUpdate; +char * _PREHASH_Method; +char * _PREHASH_TouchName; +char * _PREHASH_UpdateType; +char * _PREHASH_KickedFromEstateID; +char * _PREHASH_CandidateID; +char * _PREHASH_ParamData; +char * _PREHASH_GodlikeMessage; +char * _PREHASH_SystemMessage; +char * _PREHASH_BodyRotation; +char * _PREHASH_SearchRegions; +char * _PREHASH_Ignore; +char * _PREHASH_AnimationData; +char * _PREHASH_StatID; +char * _PREHASH_ItemID; +char * _PREHASH_AvatarStatisticsReply; +char * _PREHASH_ScriptDialogReply; +char * _PREHASH_RegionIDAndHandleReply; +char * _PREHASH_CameraAtOffset; +char * _PREHASH_VoteID; +char * _PREHASH_ParcelGodForceOwner; +char * _PREHASH_Filter; +char * _PREHASH_InviteData; +char * _PREHASH_PCode; +char * _PREHASH_SearchPos; +char * _PREHASH_PreyID; +char * _PREHASH_TerrainLowerLimit; +char * _PREHASH_EventFlags; +char * _PREHASH_TallyVotes; +char * _PREHASH_Result; +char * _PREHASH_LookAt; +char * _PREHASH_PayButton; +char * _PREHASH_SelfCount; +char * _PREHASH_PacketCount; +char * _PREHASH_ParcelBuyPass; +char * _PREHASH_Identified; +char * _PREHASH_OldItemID; +char * _PREHASH_RegionPort; +char * _PREHASH_PriceEnergyUnit; +char * _PREHASH_Bitmap; +char * _PREHASH_TrackAgentSession; +char * _PREHASH_CacheMissType; +char * _PREHASH_VFileID; +char * _PREHASH_GroupInsigniaID; +char * _PREHASH_FromID; +char * _PREHASH_Online; +char * _PREHASH_KickFlags; +char * _PREHASH_CovenantID; +char * _PREHASH_SysCPU; +char * _PREHASH_EMail; +char * _PREHASH_AggregatePermTextures; +char * _PREHASH_ChatChannel; +char * _PREHASH_ReturnID; +char * _PREHASH_ObjectAttach; +char * _PREHASH_TargetPort; +char * _PREHASH_ObjectSpinStop; +char * _PREHASH_FullID; +char * _PREHASH_ActivateGroup; +char * _PREHASH_SysGPU; +char * _PREHASH_AvatarInterestsReply; +char * _PREHASH_StartLure; +char * _PREHASH_SysRAM; +char * _PREHASH_ObjectPosition; +char * _PREHASH_SitPosition; +char * _PREHASH_StartTime; +char * _PREHASH_BornOn; +char * _PREHASH_CameraCollidePlane; +char * _PREHASH_EconomyDataRequest; +char * _PREHASH_TeleportLureRequest; +char * _PREHASH_FolderID; +char * _PREHASH_RegionHandleRequest; +char * _PREHASH_GestureRequest; +char * _PREHASH_ScriptDataRequest; +char * _PREHASH_GroupRoleDataRequest; +char * _PREHASH_GroupTitlesRequest; +char * _PREHASH_AgentWearablesRequest; +char * _PREHASH_MapBlockRequest; +char * _PREHASH_LureID; +char * _PREHASH_CopyCenters; +char * _PREHASH_ParamList; +char * _PREHASH_InventorySerial; +char * _PREHASH_EdgeDataPacket; +char * _PREHASH_AvatarPickerReply; +char * _PREHASH_ParcelDwellReply; +char * _PREHASH_IsForSale; +char * _PREHASH_MuteID; +char * _PREHASH_MeanCollisionAlert; +char * _PREHASH_CanAcceptTasks; +char * _PREHASH_ItemData; +char * _PREHASH_AnimationList; +char * _PREHASH_PassObject; +char * _PREHASH_Reputation; +char * _PREHASH_IntValue; +char * _PREHASH_TargetType; +char * _PREHASH_Amount; +char * _PREHASH_HasAttachment; +char * _PREHASH_UpdateAttachment; +char * _PREHASH_RemoveAttachment; +char * _PREHASH_HeightWidthBlock; +char * _PREHASH_RequestObjectPropertiesFamily; +char * _PREHASH_ObjectPropertiesFamily; +char * _PREHASH_UserData; +char * _PREHASH_IsReadable; +char * _PREHASH_PathCurve; +char * _PREHASH_Status; +char * _PREHASH_FromGroup; +char * _PREHASH_AlreadyVoted; +char * _PREHASH_PlacesReply; +char * _PREHASH_DirPlacesReply; +char * _PREHASH_ParcelBuy; +char * _PREHASH_DirFindQueryBackend; +char * _PREHASH_DirPlacesQueryBackend; +char * _PREHASH_DirClassifiedQueryBackend; +char * _PREHASH_DirPicksQueryBackend; +char * _PREHASH_DirLandQueryBackend; +char * _PREHASH_DirPopularQueryBackend; +char * _PREHASH_LogoutDemand; +char * _PREHASH_HistoryData; +char * _PREHASH_SnapshotID; +char * _PREHASH_Aspect; +char * _PREHASH_ParamSize; +char * _PREHASH_VoteCast; +char * _PREHASH_CastsShadows; +char * _PREHASH_EveryoneMask; +char * _PREHASH_SetSunPhase; +char * _PREHASH_ObjectSpinUpdate; +char * _PREHASH_MaturePublish; +char * _PREHASH_UseExistingAsset; +char * _PREHASH_Powers; +char * _PREHASH_ParcelLocalID; +char * _PREHASH_TeleportCancel; +char * _PREHASH_UnixTime; +char * _PREHASH_QueryFlags; +char * _PREHASH_LastExecFroze; +char * _PREHASH_AlwaysRun; +char * _PREHASH_Bottom; +char * _PREHASH_ButtonData; +char * _PREHASH_SoundData; +char * _PREHASH_ViewerStats; +char * _PREHASH_RegionHandshake; +char * _PREHASH_ObjectDescription; +char * _PREHASH_Description; +char * _PREHASH_ParamType; +char * _PREHASH_UUIDNameReply; +char * _PREHASH_UUIDGroupNameReply; +char * _PREHASH_SaveAssetIntoInventory; +char * _PREHASH_UserInfo; +char * _PREHASH_AnimSequenceID; +char * _PREHASH_NVPairs; +char * _PREHASH_GroupNoticesListRequest; +char * _PREHASH_ParcelAccessListRequest; +char * _PREHASH_MuteListRequest; +char * _PREHASH_StartPeriod; +char * _PREHASH_RpcChannelRequest; +char * _PREHASH_LandStatRequest; +char * _PREHASH_PlacesQuery; +char * _PREHASH_DirPlacesQuery; +char * _PREHASH_SortOrder; +char * _PREHASH_Hunter; +char * _PREHASH_SunAngVelocity; +char * _PREHASH_BinaryBucket; +char * _PREHASH_ImagePacket; +char * _PREHASH_StartGroupProposal; +char * _PREHASH_EnergyLevel; +char * _PREHASH_PriceForListing; +char * _PREHASH_Scale; +char * _PREHASH_EstateCovenantReply; +char * _PREHASH_ParentEstateID; +char * _PREHASH_Extra2; +char * _PREHASH_Throttle; +char * _PREHASH_SimIP; +char * _PREHASH_GodID; +char * _PREHASH_TeleportMinPrice; +char * _PREHASH_VoteItem; +char * _PREHASH_ObjectRotation; +char * _PREHASH_SitRotation; +char * _PREHASH_SnapSelection; +char * _PREHASH_SoundTrigger; +char * _PREHASH_TerrainRaiseLimit; +char * _PREHASH_Quorum; +char * _PREHASH_TokenBlock; +char * _PREHASH_AgentBlock; +char * _PREHASH_CommandBlock; +char * _PREHASH_PricePublicObjectDecay; +char * _PREHASH_SpawnPointPos; +char * _PREHASH_AttachedSoundCutoffRadius; +char * _PREHASH_VolumeDetail; +char * _PREHASH_FromAgentName; +char * _PREHASH_Range; +char * _PREHASH_DirectoryVisibility; +char * _PREHASH_PublicIP; +char * _PREHASH_TeleportFailed; +char * _PREHASH_OnlineStatusReply; +char * _PREHASH_RequestAvatarInfo; +char * _PREHASH_PreloadSound; +char * _PREHASH_ScreenshotID; +char * _PREHASH_CovenantTimestamp; +char * _PREHASH_OldestUnacked; +char * _PREHASH_SimulatorIP; +char * _PREHASH_ObjectImport; +char * _PREHASH_Value; +char * _PREHASH_JointAxisOrAnchor; +char * _PREHASH_Test0; +char * _PREHASH_Test1; +char * _PREHASH_Test2; +char * _PREHASH_SunPhase; +char * _PREHASH_Place; +char * _PREHASH_Phase; +char * _PREHASH_ParcelDivide; +char * _PREHASH_PriceObjectClaim; +char * _PREHASH_Field; +char * _PREHASH_Ratio; +char * _PREHASH_JoinGroupReply; +char * _PREHASH_LiveHelpGroupReply; +char * _PREHASH_Score; +char * _PREHASH_ExpungeData; +char * _PREHASH_Image; +char * _PREHASH_ObjectClickAction; +char * _PREHASH_Delta; +char * _PREHASH_InitiateUpload; +char * _PREHASH_Parameter; +char * _PREHASH_Flags; +char * _PREHASH_Plane; +char * _PREHASH_Width; +char * _PREHASH_Right; +char * _PREHASH_DirFindQuery; +char * _PREHASH_Textures; +char * _PREHASH_EventData; +char * _PREHASH_Final; +char * _PREHASH_TelehubPos; +char * _PREHASH_ReportAutosaveCrash; +char * _PREHASH_Reset; +char * _PREHASH_CreateTrustedCircuit; +char * _PREHASH_DenyTrustedCircuit; +char * _PREHASH_RequestTrustedCircuit; +char * _PREHASH_Codec; +char * _PREHASH_Level; +char * _PREHASH_Modal; +char * _PREHASH_ChildAgentUnknown; +char * _PREHASH_LandingType; +char * _PREHASH_ScriptRunningReply; +char * _PREHASH_MoneyDetailsReply; +char * _PREHASH_Reply; +char * _PREHASH_TelehubRot; +char * _PREHASH_RequestFriendship; +char * _PREHASH_AcceptFriendship; +char * _PREHASH_GroupAccountDetailsReply; +char * _PREHASH_DwellInfo; +char * _PREHASH_AgentResume; +char * _PREHASH_ItemType; +char * _PREHASH_MailFilter; +char * _PREHASH_Disconnect; +char * _PREHASH_SimPosition; +char * _PREHASH_SimWideTotalPrims; +char * _PREHASH_Index; +char * _PREHASH_BaseFilename; +char * _PREHASH_SimFilename; +char * _PREHASH_LastOwnerID; +char * _PREHASH_GroupNoticeRequest; +char * _PREHASH_EmailMessageRequest; +char * _PREHASH_MapItemRequest; +char * _PREHASH_AgentCount; +char * _PREHASH_MessageBlock; +char * _PREHASH_FuseBlock; +char * _PREHASH_AgentGroupData; +char * _PREHASH_ClassifiedInfoUpdate; +char * _PREHASH_RegionPos; +char * _PREHASH_ParcelMediaUpdate; +char * _PREHASH_NoticeID; +char * _PREHASH_GridX; +char * _PREHASH_GridY; +char * _PREHASH_Title; +char * _PREHASH_AuctionID; +char * _PREHASH_VoteType; +char * _PREHASH_CategoryID; +char * _PREHASH_Token; +char * _PREHASH_AggregatePerms; +char * _PREHASH_StartParcelRemoveAck; +char * _PREHASH_ObjectSelect; +char * _PREHASH_ForceObjectSelect; +char * _PREHASH_Price; +char * _PREHASH_SunDirection; +char * _PREHASH_FromName; +char * _PREHASH_ChangeInventoryItemFlags; +char * _PREHASH_Force; +char * _PREHASH_TransactionBlock; +char * _PREHASH_PowersMask; +char * _PREHASH_Stamp; +char * _PREHASH_TotalCredits; +char * _PREHASH_State; +char * _PREHASH_TextureIndex; +char * _PREHASH_InviteeID; +char * _PREHASH_ParcelReclaim; +char * _PREHASH_Money; +char * _PREHASH_PathTwist; +char * _PREHASH_AuthBuyerID; +char * _PREHASH_Color; +char * _PREHASH_SourceType; +char * _PREHASH_World; +char * _PREHASH_QueryData; +char * _PREHASH_Users; +char * _PREHASH_SysOS; +char * _PREHASH_Notes; +char * _PREHASH_AvatarID; +char * _PREHASH_FounderID; +char * _PREHASH_EndPointID; +char * _PREHASH_StipendEstimate; +char * _PREHASH_LocationLookAt; +char * _PREHASH_Sound; +char * _PREHASH_Cover; +char * _PREHASH_TotalObjectCount; +char * _PREHASH_TextureEntry; +char * _PREHASH_SquareMetersCommitted; +char * _PREHASH_ChannelID; +char * _PREHASH_Dwell; +char * _PREHASH_North; +char * _PREHASH_AgentUpdate; +char * _PREHASH_PickGodDelete; +char * _PREHASH_HostName; +char * _PREHASH_PriceParcelClaim; +char * _PREHASH_ParcelClaim; +char * _PREHASH_AgentPowers; +char * _PREHASH_ProfileHollow; +char * _PREHASH_GroupRoleChanges; +char * _PREHASH_Count; +char * _PREHASH_South; +char * _PREHASH_Entry; +char * _PREHASH_ObjectUpdateCompressed; +char * _PREHASH_MuteFlags; +char * _PREHASH_Group; +char * _PREHASH_AgentPause; +char * _PREHASH_LanguagesText; +char * _PREHASH_InternalScriptMail; +char * _PREHASH_FindAgent; +char * _PREHASH_AgentData; +char * _PREHASH_FolderData; +char * _PREHASH_AssetBlock; +char * _PREHASH_AcceptNotices; +char * _PREHASH_SetGroupAcceptNotices; +char * _PREHASH_CloseCircuit; +char * _PREHASH_LogControl; +char * _PREHASH_TeleportFinish; +char * _PREHASH_PathRevolutions; +char * _PREHASH_ClassifiedInfoReply; +char * _PREHASH_ParcelInfoReply; +char * _PREHASH_AutosaveData; +char * _PREHASH_SetStartLocation; +char * _PREHASH_PassHours; +char * _PREHASH_AttachmentPt; +char * _PREHASH_ParcelFlags; +char * _PREHASH_NumVotes; +char * _PREHASH_AvatarPickerRequest; +char * _PREHASH_TeleportLocationRequest; +char * _PREHASH_DataHomeLocationRequest; +char * _PREHASH_EventNotificationAddRequest; +char * _PREHASH_ParcelDwellRequest; +char * _PREHASH_EventLocationRequest; +char * _PREHASH_EndPeriod; +char * _PREHASH_SetStartLocationRequest; +char * _PREHASH_QueryStart; +char * _PREHASH_EjectData; +char * _PREHASH_AvatarTextureUpdate; +char * _PREHASH_RPCServerPort; +char * _PREHASH_Bytes; +char * _PREHASH_Extra; +char * _PREHASH_ForceScriptControlRelease; +char * _PREHASH_ParcelRelease; +char * _PREHASH_VFileType; +char * _PREHASH_EjectGroupMemberReply; +char * _PREHASH_ImageData; +char * _PREHASH_SpaceServerSimulatorTimeMessage; +char * _PREHASH_SimulatorViewerTimeMessage; +char * _PREHASH_Rotation; +char * _PREHASH_Selection; +char * _PREHASH_TransactionData; +char * _PREHASH_OperationData; +char * _PREHASH_ExpirationDate; +char * _PREHASH_ParcelDeedToGroup; +char * _PREHASH_DirPicksReply; +char * _PREHASH_AvatarPicksReply; +char * _PREHASH_GroupTitlesReply; +char * _PREHASH_AgentInfo; +char * _PREHASH_MoneyTransferBackend; +char * _PREHASH_NextOwnerMask; +char * _PREHASH_MuteData; +char * _PREHASH_PassPrice; +char * _PREHASH_SourceID; +char * _PREHASH_ChangeUserRights; +char * _PREHASH_TeleportFlags; +char * _PREHASH_AssetData; +char * _PREHASH_SlaveParcelData; +char * _PREHASH_MultipleObjectUpdate; +char * _PREHASH_ObjectUpdate; +char * _PREHASH_ImprovedTerseObjectUpdate; +char * _PREHASH_ConfirmXferPacket; +char * _PREHASH_StartPingCheck; +char * _PREHASH_SimWideDeletes; +char * _PREHASH_LandStatReply; +char * _PREHASH_IsPhantom; +char * _PREHASH_AgentList; +char * _PREHASH_SimApproved; +char * _PREHASH_RezObject; +char * _PREHASH_TaskLocalID; +char * _PREHASH_ClaimDate; +char * _PREHASH_MergeParcel; +char * _PREHASH_Priority; +char * _PREHASH_Building; +char * _PREHASH_QueryText; +char * _PREHASH_GroupNoticeAdd; +char * _PREHASH_ReturnType; +char * _PREHASH_FetchFolders; +char * _PREHASH_SimulatorPublicHostBlock; +char * _PREHASH_HeaderData; +char * _PREHASH_RequestMultipleObjects; +char * _PREHASH_RetrieveInstantMessages; +char * _PREHASH_DequeueInstantMessages; +char * _PREHASH_OpenCircuit; +char * _PREHASH_SecureSessionID; +char * _PREHASH_CrossedRegion; +char * _PREHASH_DirGroupsReply; +char * _PREHASH_AvatarGroupsReply; +char * _PREHASH_EmailMessageReply; +char * _PREHASH_GroupVoteHistoryItemReply; +char * _PREHASH_ViewerPosition; +char * _PREHASH_Position; +char * _PREHASH_ParentEstate; +char * _PREHASH_EstateName; +char * _PREHASH_MuteName; +char * _PREHASH_StartParcelRename; +char * _PREHASH_BulkParcelRename; +char * _PREHASH_ParcelRename; +char * _PREHASH_ViewerFilename; +char * _PREHASH_Positive; +char * _PREHASH_UserReportInternal; +char * _PREHASH_AvatarPropertiesRequest; +char * _PREHASH_ParcelPropertiesRequest; +char * _PREHASH_GroupProfileRequest; +char * _PREHASH_AgentDataUpdateRequest; +char * _PREHASH_PriceObjectScaleFactor; +char * _PREHASH_DirPicksQuery; +char * _PREHASH_OpenEnrollment; +char * _PREHASH_GroupData; +char * _PREHASH_RequestGodlikePowers; +char * _PREHASH_GrantGodlikePowers; +char * _PREHASH_TransactionID; +char * _PREHASH_DestinationID; +char * _PREHASH_Controls; +char * _PREHASH_FirstDetachAll; +char * _PREHASH_EstateID; +char * _PREHASH_ImprovedInstantMessage; +char * _PREHASH_AgentQuit; +char * _PREHASH_CheckParcelSales; +char * _PREHASH_ParcelSales; +char * _PREHASH_CurrentInterval; +char * _PREHASH_PriceRentLight; +char * _PREHASH_MediaAutoScale; +char * _PREHASH_NeighborBlock; +char * _PREHASH_LayerData; +char * _PREHASH_NVPairData; +char * _PREHASH_TeleportLocal; +char * _PREHASH_EjecteeID; +char * _PREHASH_VoteInitiator; +char * _PREHASH_TypeData; +char * _PREHASH_OwnerIDs; +char * _PREHASH_SystemKickUser; +char * _PREHASH_TransactionTime; +char * _PREHASH_TimeToLive; +char * _PREHASH_StartParcelRemove; +char * _PREHASH_BulkParcelRemove; +char * _PREHASH_OldAgentID; +char * _PREHASH_BonusEstimate; +char * _PREHASH_MusicURL; +char * _PREHASH_CompleteLure; +char * _PREHASH_ParcelPrimBonus; +char * _PREHASH_EjectUser; +char * _PREHASH_CoarseLocationUpdate; +char * _PREHASH_ChildAgentPositionUpdate; +char * _PREHASH_StoreLocal; +char * _PREHASH_GroupName; +char * _PREHASH_PriceParcelRent; +char * _PREHASH_SimStatus; +char * _PREHASH_TransactionSuccess; +char * _PREHASH_LureType; +char * _PREHASH_GroupMask; +char * _PREHASH_SitObject; +char * _PREHASH_Override; +char * _PREHASH_LocomotionState; +char * _PREHASH_PriceUpload; +char * _PREHASH_RemoveParcel; +char * _PREHASH_ConfirmAuctionStart; +char * _PREHASH_RpcScriptRequestInbound; +char * _PREHASH_ActiveGroupID; +char * _PREHASH_ParcelReturnObjects; +char * _PREHASH_TotalObjects; +char * _PREHASH_ObjectExtraParams; +char * _PREHASH_Questions; +char * _PREHASH_TransferAbort; +char * _PREHASH_TransferInventory; +char * _PREHASH_RayTargetID; +char * _PREHASH_ClaimPrice; +char * _PREHASH_ObjectProperties; +char * _PREHASH_ParcelProperties; +char * _PREHASH_EstateOwnerID; +char * _PREHASH_LogoutRequest; +char * _PREHASH_AssetUploadRequest; +char * _PREHASH_ReputationIndividualRequest; +char * _PREHASH_MajorVersion; +char * _PREHASH_MinorVersion; +char * _PREHASH_SimulatorAssign; +char * _PREHASH_TransactionType; +char * _PREHASH_AvatarPropertiesUpdate; +char * _PREHASH_ParcelPropertiesUpdate; +char * _PREHASH_FetchItems; +char * _PREHASH_AbortXfer; +char * _PREHASH_DeRezAck; +char * _PREHASH_TakeControls; +char * _PREHASH_DirLandReply; +char * _PREHASH_SpaceLocationTeleportReply; +char * _PREHASH_MuteType; +char * _PREHASH_IMViaEMail; +char * _PREHASH_StartExpungeProcessAck; +char * _PREHASH_RentPrice; +char * _PREHASH_GenericMessage; +char * _PREHASH_ChildAgentAlive; +char * _PREHASH_AssetType; +char * _PREHASH_SpawnPointBlock; +char * _PREHASH_AttachmentBlock; +char * _PREHASH_ObjectMaterial; +char * _PREHASH_OwnerName; +char * _PREHASH_AvatarNotesReply; +char * _PREHASH_CacheID; +char * _PREHASH_OwnerMask; +char * _PREHASH_TransferInventoryAck; + +void init_prehash_data() +{ + _PREHASH_X = gMessageStringTable.getString("X"); + _PREHASH_Y = gMessageStringTable.getString("Y"); + _PREHASH_Z = gMessageStringTable.getString("Z"); + _PREHASH_AddFlags = gMessageStringTable.getString("AddFlags"); + _PREHASH_ReservedNewbie = gMessageStringTable.getString("ReservedNewbie"); + _PREHASH_FailureInfo = gMessageStringTable.getString("FailureInfo"); + _PREHASH_MapData = gMessageStringTable.getString("MapData"); + _PREHASH_AddItem = gMessageStringTable.getString("AddItem"); + _PREHASH_MeanCollision = gMessageStringTable.getString("MeanCollision"); + _PREHASH_RezScript = gMessageStringTable.getString("RezScript"); + _PREHASH_AvatarSitResponse = gMessageStringTable.getString("AvatarSitResponse"); + _PREHASH_InventoryAssetResponse = gMessageStringTable.getString("InventoryAssetResponse"); + _PREHASH_KillObject = gMessageStringTable.getString("KillObject"); + _PREHASH_ProposalID = gMessageStringTable.getString("ProposalID"); + _PREHASH_SerialNum = gMessageStringTable.getString("SerialNum"); + _PREHASH_Duration = gMessageStringTable.getString("Duration"); + _PREHASH_ScriptQuestion = gMessageStringTable.getString("ScriptQuestion"); + _PREHASH_AddCircuitCode = gMessageStringTable.getString("AddCircuitCode"); + _PREHASH_UseCircuitCode = gMessageStringTable.getString("UseCircuitCode"); + _PREHASH_ViewerCircuitCode = gMessageStringTable.getString("ViewerCircuitCode"); + _PREHASH_ScriptAnswerYes = gMessageStringTable.getString("ScriptAnswerYes"); + _PREHASH_PartnerID = gMessageStringTable.getString("PartnerID"); + _PREHASH_DirLandQuery = gMessageStringTable.getString("DirLandQuery"); + _PREHASH_TeleportStart = gMessageStringTable.getString("TeleportStart"); + _PREHASH_LogMessages = gMessageStringTable.getString("LogMessages"); + _PREHASH_AboutText = gMessageStringTable.getString("AboutText"); + _PREHASH_VisualParam = gMessageStringTable.getString("VisualParam"); + _PREHASH_GroupPrims = gMessageStringTable.getString("GroupPrims"); + _PREHASH_SelectedPrims = gMessageStringTable.getString("SelectedPrims"); + _PREHASH_ID = gMessageStringTable.getString("ID"); + _PREHASH_UUIDNameRequest = gMessageStringTable.getString("UUIDNameRequest"); + _PREHASH_UUIDGroupNameRequest = gMessageStringTable.getString("UUIDGroupNameRequest"); + _PREHASH_MoneyTransactionsRequest = gMessageStringTable.getString("MoneyTransactionsRequest"); + _PREHASH_GroupAccountTransactionsRequest = gMessageStringTable.getString("GroupAccountTransactionsRequest"); + _PREHASH_MapNameRequest = gMessageStringTable.getString("MapNameRequest"); + _PREHASH_MailTaskSimRequest = gMessageStringTable.getString("MailTaskSimRequest"); + _PREHASH_UpdateSimulator = gMessageStringTable.getString("UpdateSimulator"); + _PREHASH_BillableFactor = gMessageStringTable.getString("BillableFactor"); + _PREHASH_ObjectBonusFactor = gMessageStringTable.getString("ObjectBonusFactor"); + _PREHASH_EnableSimulator = gMessageStringTable.getString("EnableSimulator"); + _PREHASH_DisableSimulator = gMessageStringTable.getString("DisableSimulator"); + _PREHASH_ConfirmEnableSimulator = gMessageStringTable.getString("ConfirmEnableSimulator"); + _PREHASH_LayerType = gMessageStringTable.getString("LayerType"); + _PREHASH_OwnerRole = gMessageStringTable.getString("OwnerRole"); + _PREHASH_ParcelOverlay = gMessageStringTable.getString("ParcelOverlay"); + _PREHASH_AdjustBalance = gMessageStringTable.getString("AdjustBalance"); + _PREHASH_GroupOwned = gMessageStringTable.getString("GroupOwned"); + _PREHASH_IP = gMessageStringTable.getString("IP"); + _PREHASH_ChatFromViewer = gMessageStringTable.getString("ChatFromViewer"); + _PREHASH_AvgAgentsInView = gMessageStringTable.getString("AvgAgentsInView"); + _PREHASH_AgentsInView = gMessageStringTable.getString("AgentsInView"); + _PREHASH_GroupTitle = gMessageStringTable.getString("GroupTitle"); + _PREHASH_MapLayerReply = gMessageStringTable.getString("MapLayerReply"); + _PREHASH_CompoundMsgID = gMessageStringTable.getString("CompoundMsgID"); + _PREHASH_CameraConstraint = gMessageStringTable.getString("CameraConstraint"); + _PREHASH_DownloadTotals = gMessageStringTable.getString("DownloadTotals"); + _PREHASH_GenCounter = gMessageStringTable.getString("GenCounter"); + _PREHASH_FrozenData = gMessageStringTable.getString("FrozenData"); + _PREHASH_ChildAgentDying = gMessageStringTable.getString("ChildAgentDying"); + _PREHASH_To = gMessageStringTable.getString("To"); + _PREHASH_CopyInventoryFromNotecard = gMessageStringTable.getString("CopyInventoryFromNotecard"); + _PREHASH_RezObjectFromNotecard = gMessageStringTable.getString("RezObjectFromNotecard"); + _PREHASH_ParcelDirFeeCurrent = gMessageStringTable.getString("ParcelDirFeeCurrent"); + _PREHASH_SeedCapability = gMessageStringTable.getString("SeedCapability"); + _PREHASH_ObjectDuplicate = gMessageStringTable.getString("ObjectDuplicate"); + _PREHASH_InventoryData = gMessageStringTable.getString("InventoryData"); + _PREHASH_ReplyData = gMessageStringTable.getString("ReplyData"); + _PREHASH_ResetList = gMessageStringTable.getString("ResetList"); + _PREHASH_MediaID = gMessageStringTable.getString("MediaID"); + _PREHASH_RelatedRights = gMessageStringTable.getString("RelatedRights"); + _PREHASH_RedirectGridX = gMessageStringTable.getString("RedirectGridX"); + _PREHASH_RedirectGridY = gMessageStringTable.getString("RedirectGridY"); + _PREHASH_TransferID = gMessageStringTable.getString("TransferID"); + _PREHASH_Transacted = gMessageStringTable.getString("Transacted"); + _PREHASH_TexturesChanged = gMessageStringTable.getString("TexturesChanged"); + _PREHASH_UserLookAt = gMessageStringTable.getString("UserLookAt"); + _PREHASH_TestBlock1 = gMessageStringTable.getString("TestBlock1"); + _PREHASH_SensedData = gMessageStringTable.getString("SensedData"); + _PREHASH_UpdateBlock = gMessageStringTable.getString("UpdateBlock"); + _PREHASH_ClassifiedGodDelete = gMessageStringTable.getString("ClassifiedGodDelete"); + _PREHASH_ObjectGrabUpdate = gMessageStringTable.getString("ObjectGrabUpdate"); + _PREHASH_TaxDate = gMessageStringTable.getString("TaxDate"); + _PREHASH_LocationPos = gMessageStringTable.getString("LocationPos"); + _PREHASH_StartDateTime = gMessageStringTable.getString("StartDateTime"); + _PREHASH_ObjectUpdateCached = gMessageStringTable.getString("ObjectUpdateCached"); + _PREHASH_Packets = gMessageStringTable.getString("Packets"); + _PREHASH_FailureType = gMessageStringTable.getString("FailureType"); + _PREHASH_UpdateGroupInfo = gMessageStringTable.getString("UpdateGroupInfo"); + _PREHASH_ObjectPermissions = gMessageStringTable.getString("ObjectPermissions"); + _PREHASH_RevokePermissions = gMessageStringTable.getString("RevokePermissions"); + _PREHASH_UpdateFlags = gMessageStringTable.getString("UpdateFlags"); + _PREHASH_ObjectExportSelected = gMessageStringTable.getString("ObjectExportSelected"); + _PREHASH_RezSelected = gMessageStringTable.getString("RezSelected"); + _PREHASH_AutoPilot = gMessageStringTable.getString("AutoPilot"); + _PREHASH_UpdateMuteListEntry = gMessageStringTable.getString("UpdateMuteListEntry"); + _PREHASH_RemoveMuteListEntry = gMessageStringTable.getString("RemoveMuteListEntry"); + _PREHASH_SetSimStatusInDatabase = gMessageStringTable.getString("SetSimStatusInDatabase"); + _PREHASH_SetSimPresenceInDatabase = gMessageStringTable.getString("SetSimPresenceInDatabase"); + _PREHASH_CameraProperty = gMessageStringTable.getString("CameraProperty"); + _PREHASH_BrushSize = gMessageStringTable.getString("BrushSize"); + _PREHASH_StartExpungeProcess = gMessageStringTable.getString("StartExpungeProcess"); + _PREHASH_SimulatorSetMap = gMessageStringTable.getString("SimulatorSetMap"); + _PREHASH_RegionPresenceRequestByRegionID = gMessageStringTable.getString("RegionPresenceRequestByRegionID"); + _PREHASH_ParcelObjectOwnersReply = gMessageStringTable.getString("ParcelObjectOwnersReply"); + _PREHASH_GroupMembersReply = gMessageStringTable.getString("GroupMembersReply"); + _PREHASH_GroupRoleMembersReply = gMessageStringTable.getString("GroupRoleMembersReply"); + _PREHASH_RequestRegionInfo = gMessageStringTable.getString("RequestRegionInfo"); + _PREHASH_AABBMax = gMessageStringTable.getString("AABBMax"); + _PREHASH_RequestPayPrice = gMessageStringTable.getString("RequestPayPrice"); + _PREHASH_SimulatorPresentAtLocation = gMessageStringTable.getString("SimulatorPresentAtLocation"); + _PREHASH_AgentRequestSit = gMessageStringTable.getString("AgentRequestSit"); + _PREHASH_AABBMin = gMessageStringTable.getString("AABBMin"); + _PREHASH_ClassifiedFlags = gMessageStringTable.getString("ClassifiedFlags"); + _PREHASH_ControlFlags = gMessageStringTable.getString("ControlFlags"); + _PREHASH_TeleportRequest = gMessageStringTable.getString("TeleportRequest"); + _PREHASH_SpaceLocationTeleportRequest = gMessageStringTable.getString("SpaceLocationTeleportRequest"); + _PREHASH_LeaderBoardRequest = gMessageStringTable.getString("LeaderBoardRequest"); + _PREHASH_ScriptTeleportRequest = gMessageStringTable.getString("ScriptTeleportRequest"); + _PREHASH_DateUTC = gMessageStringTable.getString("DateUTC"); + _PREHASH_TaskIDs = gMessageStringTable.getString("TaskIDs"); + _PREHASH_EstateCovenantRequest = gMessageStringTable.getString("EstateCovenantRequest"); + _PREHASH_RequestResult = gMessageStringTable.getString("RequestResult"); + _PREHASH_ReputationAgentAssign = gMessageStringTable.getString("ReputationAgentAssign"); + _PREHASH_CanAcceptAgents = gMessageStringTable.getString("CanAcceptAgents"); + _PREHASH_ObjectSaleInfo = gMessageStringTable.getString("ObjectSaleInfo"); + _PREHASH_KillChildAgents = gMessageStringTable.getString("KillChildAgents"); + _PREHASH_Balance = gMessageStringTable.getString("Balance"); + _PREHASH_DerezContainer = gMessageStringTable.getString("DerezContainer"); + _PREHASH_ObjectData = gMessageStringTable.getString("ObjectData"); + _PREHASH_CameraAtAxis = gMessageStringTable.getString("CameraAtAxis"); + _PREHASH_InfoBlock = gMessageStringTable.getString("InfoBlock"); + _PREHASH_OwnershipCost = gMessageStringTable.getString("OwnershipCost"); + _PREHASH_AvatarNotesUpdate = gMessageStringTable.getString("AvatarNotesUpdate"); + _PREHASH_PID = gMessageStringTable.getString("PID"); + _PREHASH_TimeString = gMessageStringTable.getString("TimeString"); + _PREHASH_DirPopularReply = gMessageStringTable.getString("DirPopularReply"); + _PREHASH_TerrainHeightRange00 = gMessageStringTable.getString("TerrainHeightRange00"); + _PREHASH_SimData = gMessageStringTable.getString("SimData"); + _PREHASH_TerrainHeightRange01 = gMessageStringTable.getString("TerrainHeightRange01"); + _PREHASH_TerrainHeightRange10 = gMessageStringTable.getString("TerrainHeightRange10"); + _PREHASH_TerrainHeightRange11 = gMessageStringTable.getString("TerrainHeightRange11"); + _PREHASH_UpdateInventoryItem = gMessageStringTable.getString("UpdateInventoryItem"); + _PREHASH_UpdateCreateInventoryItem = gMessageStringTable.getString("UpdateCreateInventoryItem"); + _PREHASH_MoveInventoryItem = gMessageStringTable.getString("MoveInventoryItem"); + _PREHASH_CopyInventoryItem = gMessageStringTable.getString("CopyInventoryItem"); + _PREHASH_RemoveInventoryItem = gMessageStringTable.getString("RemoveInventoryItem"); + _PREHASH_CreateInventoryItem = gMessageStringTable.getString("CreateInventoryItem"); + _PREHASH_PathTwistBegin = gMessageStringTable.getString("PathTwistBegin"); + _PREHASH_CRC = gMessageStringTable.getString("CRC"); + _PREHASH_AttachmentPoint = gMessageStringTable.getString("AttachmentPoint"); + _PREHASH_TelehubBlock = gMessageStringTable.getString("TelehubBlock"); + _PREHASH_FOVBlock = gMessageStringTable.getString("FOVBlock"); + _PREHASH_StartLocationData = gMessageStringTable.getString("StartLocationData"); + _PREHASH_PositionData = gMessageStringTable.getString("PositionData"); + _PREHASH_TimeSinceLast = gMessageStringTable.getString("TimeSinceLast"); + _PREHASH_MapImage = gMessageStringTable.getString("MapImage"); + _PREHASH_Objects = gMessageStringTable.getString("Objects"); + _PREHASH_URL = gMessageStringTable.getString("URL"); + _PREHASH_CreationDate = gMessageStringTable.getString("CreationDate"); + _PREHASH_JointPivot = gMessageStringTable.getString("JointPivot"); + _PREHASH_RateeID = gMessageStringTable.getString("RateeID"); + _PREHASH_FPS = gMessageStringTable.getString("FPS"); + _PREHASH_HasTelehub = gMessageStringTable.getString("HasTelehub"); + _PREHASH_PathEnd = gMessageStringTable.getString("PathEnd"); + _PREHASH_ScriptDataReply = gMessageStringTable.getString("ScriptDataReply"); + _PREHASH_MapBlockReply = gMessageStringTable.getString("MapBlockReply"); + _PREHASH_PropertiesData = gMessageStringTable.getString("PropertiesData"); + _PREHASH_ViewerEffect = gMessageStringTable.getString("ViewerEffect"); + _PREHASH_FreezeUser = gMessageStringTable.getString("FreezeUser"); + _PREHASH_OwnerPrims = gMessageStringTable.getString("OwnerPrims"); + _PREHASH_ObjectGrab = gMessageStringTable.getString("ObjectGrab"); + _PREHASH_ToAgentID = gMessageStringTable.getString("ToAgentID"); + _PREHASH_SimulatorMapUpdate = gMessageStringTable.getString("SimulatorMapUpdate"); + _PREHASH_TransferPacket = gMessageStringTable.getString("TransferPacket"); + _PREHASH_ObjectName = gMessageStringTable.getString("ObjectName"); + _PREHASH_GroupPowers = gMessageStringTable.getString("GroupPowers"); + _PREHASH_OriginalName = gMessageStringTable.getString("OriginalName"); + _PREHASH_CompletePingCheck = gMessageStringTable.getString("CompletePingCheck"); + _PREHASH_OnlineStatus = gMessageStringTable.getString("OnlineStatus"); + _PREHASH_ObjectDrop = gMessageStringTable.getString("ObjectDrop"); + _PREHASH_UseBigPackets = gMessageStringTable.getString("UseBigPackets"); + _PREHASH_GroupNoticesListReply = gMessageStringTable.getString("GroupNoticesListReply"); + _PREHASH_ParcelAccessListReply = gMessageStringTable.getString("ParcelAccessListReply"); + _PREHASH_RpcChannelReply = gMessageStringTable.getString("RpcChannelReply"); + _PREHASH_RegionPresenceResponse = gMessageStringTable.getString("RegionPresenceResponse"); + _PREHASH_AgentPresenceResponse = gMessageStringTable.getString("AgentPresenceResponse"); + _PREHASH_CharterMember = gMessageStringTable.getString("CharterMember"); + _PREHASH_EdgeData = gMessageStringTable.getString("EdgeData"); + _PREHASH_NameData = gMessageStringTable.getString("NameData"); + _PREHASH_RegionPushOverride = gMessageStringTable.getString("RegionPushOverride"); + _PREHASH_SimName = gMessageStringTable.getString("SimName"); + _PREHASH_UserReport = gMessageStringTable.getString("UserReport"); + _PREHASH_DownloadPriority = gMessageStringTable.getString("DownloadPriority"); + _PREHASH_ToAgentId = gMessageStringTable.getString("ToAgentId"); + _PREHASH_Mag = gMessageStringTable.getString("Mag"); + _PREHASH_DirPopularQuery = gMessageStringTable.getString("DirPopularQuery"); + _PREHASH_ParcelPropertiesRequestByID = gMessageStringTable.getString("ParcelPropertiesRequestByID"); + _PREHASH_ObjectLink = gMessageStringTable.getString("ObjectLink"); + _PREHASH_RpcScriptReplyInbound = gMessageStringTable.getString("RpcScriptReplyInbound"); + _PREHASH_BoardData = gMessageStringTable.getString("BoardData"); + _PREHASH_RezData = gMessageStringTable.getString("RezData"); + _PREHASH_RemoveInventoryObjects = gMessageStringTable.getString("RemoveInventoryObjects"); + _PREHASH_GroupProposalBallot = gMessageStringTable.getString("GroupProposalBallot"); + _PREHASH_RPCServerIP = gMessageStringTable.getString("RPCServerIP"); + _PREHASH_Far = gMessageStringTable.getString("Far"); + _PREHASH_GodSessionID = gMessageStringTable.getString("GodSessionID"); + _PREHASH_ViewerDigest = gMessageStringTable.getString("ViewerDigest"); + _PREHASH_FLAboutText = gMessageStringTable.getString("FLAboutText"); + _PREHASH_RegionHandshakeReply = gMessageStringTable.getString("RegionHandshakeReply"); + _PREHASH_GroupActiveProposalItemReply = gMessageStringTable.getString("GroupActiveProposalItemReply"); + _PREHASH_MapItemReply = gMessageStringTable.getString("MapItemReply"); + _PREHASH_Seconds = gMessageStringTable.getString("Seconds"); + _PREHASH_UpdateUserInfo = gMessageStringTable.getString("UpdateUserInfo"); + _PREHASH_AggregatePermTexturesOwner = gMessageStringTable.getString("AggregatePermTexturesOwner"); + _PREHASH_Set = gMessageStringTable.getString("Set"); + _PREHASH_NewName = gMessageStringTable.getString("NewName"); + _PREHASH_Key = gMessageStringTable.getString("Key"); + _PREHASH_AgentID = gMessageStringTable.getString("AgentID"); + _PREHASH_OnlineStatusRequest = gMessageStringTable.getString("OnlineStatusRequest"); + _PREHASH_EventNotificationRemoveRequest = gMessageStringTable.getString("EventNotificationRemoveRequest"); + _PREHASH_NewFolderID = gMessageStringTable.getString("NewFolderID"); + _PREHASH_Arc = gMessageStringTable.getString("Arc"); + _PREHASH_RegionX = gMessageStringTable.getString("RegionX"); + _PREHASH_RegionY = gMessageStringTable.getString("RegionY"); + _PREHASH_RequestData = gMessageStringTable.getString("RequestData"); + _PREHASH_Msg = gMessageStringTable.getString("Msg"); + _PREHASH_Top = gMessageStringTable.getString("Top"); + _PREHASH_MiscStats = gMessageStringTable.getString("MiscStats"); + _PREHASH_ImageID = gMessageStringTable.getString("ImageID"); + _PREHASH_DataPacket = gMessageStringTable.getString("DataPacket"); + _PREHASH_ObjectDehinge = gMessageStringTable.getString("ObjectDehinge"); + _PREHASH_You = gMessageStringTable.getString("You"); + _PREHASH_ScriptControlChange = gMessageStringTable.getString("ScriptControlChange"); + _PREHASH_LoadURL = gMessageStringTable.getString("LoadURL"); + _PREHASH_SetCPURatio = gMessageStringTable.getString("SetCPURatio"); + _PREHASH_NameValueData = gMessageStringTable.getString("NameValueData"); + _PREHASH_AtomicPassObject = gMessageStringTable.getString("AtomicPassObject"); + _PREHASH_ErrorMessage = gMessageStringTable.getString("ErrorMessage"); + _PREHASH_ViewerFrozenMessage = gMessageStringTable.getString("ViewerFrozenMessage"); + _PREHASH_HealthMessage = gMessageStringTable.getString("HealthMessage"); + _PREHASH_LogTextMessage = gMessageStringTable.getString("LogTextMessage"); + _PREHASH_TimeDilation = gMessageStringTable.getString("TimeDilation"); + _PREHASH_RemoveContribution = gMessageStringTable.getString("RemoveContribution"); + _PREHASH_Contribution = gMessageStringTable.getString("Contribution"); + _PREHASH_SetGroupContribution = gMessageStringTable.getString("SetGroupContribution"); + _PREHASH_Offline = gMessageStringTable.getString("Offline"); + _PREHASH_AgentIsNowWearing = gMessageStringTable.getString("AgentIsNowWearing"); + _PREHASH_SecPerDay = gMessageStringTable.getString("SecPerDay"); + _PREHASH_Members = gMessageStringTable.getString("Members"); + _PREHASH_FailedResends = gMessageStringTable.getString("FailedResends"); + _PREHASH_CameraCenter = gMessageStringTable.getString("CameraCenter"); + _PREHASH_CameraLeftAxis = gMessageStringTable.getString("CameraLeftAxis"); + _PREHASH_ExBlock = gMessageStringTable.getString("ExBlock"); + _PREHASH_Channel = gMessageStringTable.getString("Channel"); + _PREHASH_NetTest = gMessageStringTable.getString("NetTest"); + _PREHASH_DiscardLevel = gMessageStringTable.getString("DiscardLevel"); + _PREHASH_LayerID = gMessageStringTable.getString("LayerID"); + _PREHASH_RatorID = gMessageStringTable.getString("RatorID"); + _PREHASH_GrabOffset = gMessageStringTable.getString("GrabOffset"); + _PREHASH_SimPort = gMessageStringTable.getString("SimPort"); + _PREHASH_PricePerMeter = gMessageStringTable.getString("PricePerMeter"); + _PREHASH_RegionFlags = gMessageStringTable.getString("RegionFlags"); + _PREHASH_VoteResult = gMessageStringTable.getString("VoteResult"); + _PREHASH_ParcelDirFeeEstimate = gMessageStringTable.getString("ParcelDirFeeEstimate"); + _PREHASH_ModifyBlock = gMessageStringTable.getString("ModifyBlock"); + _PREHASH_InventoryBlock = gMessageStringTable.getString("InventoryBlock"); + _PREHASH_ReplyBlock = gMessageStringTable.getString("ReplyBlock"); + _PREHASH_ValidUntil = gMessageStringTable.getString("ValidUntil"); + _PREHASH_VelocityInterpolateOn = gMessageStringTable.getString("VelocityInterpolateOn"); + _PREHASH_ClassifiedDelete = gMessageStringTable.getString("ClassifiedDelete"); + _PREHASH_RegionDenyAnonymous = gMessageStringTable.getString("RegionDenyAnonymous"); + _PREHASH_FLImageID = gMessageStringTable.getString("FLImageID"); + _PREHASH_AllowPublish = gMessageStringTable.getString("AllowPublish"); + _PREHASH_SitName = gMessageStringTable.getString("SitName"); + _PREHASH_RegionsVisited = gMessageStringTable.getString("RegionsVisited"); + _PREHASH_DirClassifiedReply = gMessageStringTable.getString("DirClassifiedReply"); + _PREHASH_AvatarClassifiedReply = gMessageStringTable.getString("AvatarClassifiedReply"); + _PREHASH_ReputationIndividualReply = gMessageStringTable.getString("ReputationIndividualReply"); + _PREHASH_MediaURL = gMessageStringTable.getString("MediaURL"); + _PREHASH_CompleteAgentMovement = gMessageStringTable.getString("CompleteAgentMovement"); + _PREHASH_SpaceIP = gMessageStringTable.getString("SpaceIP"); + _PREHASH_ClassifiedID = gMessageStringTable.getString("ClassifiedID"); + _PREHASH_LocalID = gMessageStringTable.getString("LocalID"); + _PREHASH_RemoveItem = gMessageStringTable.getString("RemoveItem"); + _PREHASH_LogFailedMoneyTransaction = gMessageStringTable.getString("LogFailedMoneyTransaction"); + _PREHASH_ViewerStartAuction = gMessageStringTable.getString("ViewerStartAuction"); + _PREHASH_StartAuction = gMessageStringTable.getString("StartAuction"); + _PREHASH_NameValueName = gMessageStringTable.getString("NameValueName"); + _PREHASH_AngVelX = gMessageStringTable.getString("AngVelX"); + _PREHASH_DuplicateFlags = gMessageStringTable.getString("DuplicateFlags"); + _PREHASH_AngVelY = gMessageStringTable.getString("AngVelY"); + _PREHASH_AngVelZ = gMessageStringTable.getString("AngVelZ"); + _PREHASH_TextColor = gMessageStringTable.getString("TextColor"); + _PREHASH_SlaveID = gMessageStringTable.getString("SlaveID"); + _PREHASH_Charter = gMessageStringTable.getString("Charter"); + _PREHASH_AlertData = gMessageStringTable.getString("AlertData"); + _PREHASH_TargetBlock = gMessageStringTable.getString("TargetBlock"); + _PREHASH_CheckParcelAuctions = gMessageStringTable.getString("CheckParcelAuctions"); + _PREHASH_ParcelAuctions = gMessageStringTable.getString("ParcelAuctions"); + _PREHASH_OwnerIsGroup = gMessageStringTable.getString("OwnerIsGroup"); + _PREHASH_NameValuePair = gMessageStringTable.getString("NameValuePair"); + _PREHASH_RemoveNameValuePair = gMessageStringTable.getString("RemoveNameValuePair"); + _PREHASH_GetNameValuePair = gMessageStringTable.getString("GetNameValuePair"); + _PREHASH_BulkUpdateInventory = gMessageStringTable.getString("BulkUpdateInventory"); + _PREHASH_UpdateTaskInventory = gMessageStringTable.getString("UpdateTaskInventory"); + _PREHASH_RemoveTaskInventory = gMessageStringTable.getString("RemoveTaskInventory"); + _PREHASH_MoveTaskInventory = gMessageStringTable.getString("MoveTaskInventory"); + _PREHASH_RequestTaskInventory = gMessageStringTable.getString("RequestTaskInventory"); + _PREHASH_ReplyTaskInventory = gMessageStringTable.getString("ReplyTaskInventory"); + _PREHASH_DeclineInventory = gMessageStringTable.getString("DeclineInventory"); + _PREHASH_AggregatePermInventory = gMessageStringTable.getString("AggregatePermInventory"); + _PREHASH_SimulatorInfo = gMessageStringTable.getString("SimulatorInfo"); + _PREHASH_MoneyTransactionsReply = gMessageStringTable.getString("MoneyTransactionsReply"); + _PREHASH_GroupAccountTransactionsReply = gMessageStringTable.getString("GroupAccountTransactionsReply"); + _PREHASH_MailTaskSimReply = gMessageStringTable.getString("MailTaskSimReply"); + _PREHASH_WearableData = gMessageStringTable.getString("WearableData"); + _PREHASH_StatisticsData = gMessageStringTable.getString("StatisticsData"); + _PREHASH_Enabled = gMessageStringTable.getString("Enabled"); + _PREHASH_Savings = gMessageStringTable.getString("Savings"); + _PREHASH_SimulatorLoad = gMessageStringTable.getString("SimulatorLoad"); + _PREHASH_InternalRegionIP = gMessageStringTable.getString("InternalRegionIP"); + _PREHASH_ExternalRegionIP = gMessageStringTable.getString("ExternalRegionIP"); + _PREHASH_TotalPairs = gMessageStringTable.getString("TotalPairs"); + _PREHASH_CreateGroupRequest = gMessageStringTable.getString("CreateGroupRequest"); + _PREHASH_JoinGroupRequest = gMessageStringTable.getString("JoinGroupRequest"); + _PREHASH_LeaveGroupRequest = gMessageStringTable.getString("LeaveGroupRequest"); + _PREHASH_InviteGroupRequest = gMessageStringTable.getString("InviteGroupRequest"); + _PREHASH_LiveHelpGroupRequest = gMessageStringTable.getString("LiveHelpGroupRequest"); + _PREHASH_ServerVersion = gMessageStringTable.getString("ServerVersion"); + _PREHASH_PriceParcelClaimFactor = gMessageStringTable.getString("PriceParcelClaimFactor"); + _PREHASH_BillableArea = gMessageStringTable.getString("BillableArea"); + _PREHASH_ObjectID = gMessageStringTable.getString("ObjectID"); + _PREHASH_ObjectFlagUpdate = gMessageStringTable.getString("ObjectFlagUpdate"); + _PREHASH_GroupRoleUpdate = gMessageStringTable.getString("GroupRoleUpdate"); + _PREHASH_RequestInventoryAsset = gMessageStringTable.getString("RequestInventoryAsset"); + _PREHASH_RedoLand = gMessageStringTable.getString("RedoLand"); + _PREHASH_TravelAccess = gMessageStringTable.getString("TravelAccess"); + _PREHASH_ChangedGrid = gMessageStringTable.getString("ChangedGrid"); + _PREHASH_AgentDropGroup = gMessageStringTable.getString("AgentDropGroup"); + _PREHASH_Details = gMessageStringTable.getString("Details"); + _PREHASH_LocationX = gMessageStringTable.getString("LocationX"); + _PREHASH_SaleType = gMessageStringTable.getString("SaleType"); + _PREHASH_LocationY = gMessageStringTable.getString("LocationY"); + _PREHASH_LocationZ = gMessageStringTable.getString("LocationZ"); + _PREHASH_EconomyData = gMessageStringTable.getString("EconomyData"); + _PREHASH_HeadRotation = gMessageStringTable.getString("HeadRotation"); + _PREHASH_DeleteOnCompletion = gMessageStringTable.getString("DeleteOnCompletion"); + _PREHASH_PublicPort = gMessageStringTable.getString("PublicPort"); + _PREHASH_DirClassifiedQuery = gMessageStringTable.getString("DirClassifiedQuery"); + _PREHASH_CallbackID = gMessageStringTable.getString("CallbackID"); + _PREHASH_RequestParcelTransfer = gMessageStringTable.getString("RequestParcelTransfer"); + _PREHASH_RoleCount = gMessageStringTable.getString("RoleCount"); + _PREHASH_ObjectCapacity = gMessageStringTable.getString("ObjectCapacity"); + _PREHASH_RequestID = gMessageStringTable.getString("RequestID"); + _PREHASH_RequestXfer = gMessageStringTable.getString("RequestXfer"); + _PREHASH_ObjectTaxCurrent = gMessageStringTable.getString("ObjectTaxCurrent"); + _PREHASH_LightTaxCurrent = gMessageStringTable.getString("LightTaxCurrent"); + _PREHASH_LandTaxCurrent = gMessageStringTable.getString("LandTaxCurrent"); + _PREHASH_GroupTaxCurrent = gMessageStringTable.getString("GroupTaxCurrent"); + _PREHASH_FetchInventoryDescendents = gMessageStringTable.getString("FetchInventoryDescendents"); + _PREHASH_InventoryDescendents = gMessageStringTable.getString("InventoryDescendents"); + _PREHASH_Descendents = gMessageStringTable.getString("Descendents"); + _PREHASH_PurgeInventoryDescendents = gMessageStringTable.getString("PurgeInventoryDescendents"); + _PREHASH_ShowDir = gMessageStringTable.getString("ShowDir"); + _PREHASH_IsOwner = gMessageStringTable.getString("IsOwner"); + _PREHASH_Timestamp = gMessageStringTable.getString("Timestamp"); + _PREHASH_GlobalPos = gMessageStringTable.getString("GlobalPos"); + _PREHASH_GrabOffsetInitial = gMessageStringTable.getString("GrabOffsetInitial"); + _PREHASH_IsTrial = gMessageStringTable.getString("IsTrial"); + _PREHASH_FinalizeLogout = gMessageStringTable.getString("FinalizeLogout"); + _PREHASH_ObjectDuplicateOnRay = gMessageStringTable.getString("ObjectDuplicateOnRay"); + _PREHASH_GroupMembershipCount = gMessageStringTable.getString("GroupMembershipCount"); + _PREHASH_MethodData = gMessageStringTable.getString("MethodData"); + _PREHASH_ActivateGestures = gMessageStringTable.getString("ActivateGestures"); + _PREHASH_DeactivateGestures = gMessageStringTable.getString("DeactivateGestures"); + _PREHASH_ProposalData = gMessageStringTable.getString("ProposalData"); + _PREHASH_PosGlobal = gMessageStringTable.getString("PosGlobal"); + _PREHASH_SearchID = gMessageStringTable.getString("SearchID"); + _PREHASH_RezMultipleAttachmentsFromInv = gMessageStringTable.getString("RezMultipleAttachmentsFromInv"); + _PREHASH_SearchName = gMessageStringTable.getString("SearchName"); + _PREHASH_VersionString = gMessageStringTable.getString("VersionString"); + _PREHASH_CreateGroupReply = gMessageStringTable.getString("CreateGroupReply"); + _PREHASH_LeaveGroupReply = gMessageStringTable.getString("LeaveGroupReply"); + _PREHASH_ActualArea = gMessageStringTable.getString("ActualArea"); + _PREHASH_Message = gMessageStringTable.getString("Message"); + _PREHASH_ClickAction = gMessageStringTable.getString("ClickAction"); + _PREHASH_AssetUploadComplete = gMessageStringTable.getString("AssetUploadComplete"); + _PREHASH_RequestType = gMessageStringTable.getString("RequestType"); + _PREHASH_UUID = gMessageStringTable.getString("UUID"); + _PREHASH_BaseMask = gMessageStringTable.getString("BaseMask"); + _PREHASH_NetBlock = gMessageStringTable.getString("NetBlock"); + _PREHASH_GlobalX = gMessageStringTable.getString("GlobalX"); + _PREHASH_GlobalY = gMessageStringTable.getString("GlobalY"); + _PREHASH_CopyRotates = gMessageStringTable.getString("CopyRotates"); + _PREHASH_KickUserAck = gMessageStringTable.getString("KickUserAck"); + _PREHASH_TopPick = gMessageStringTable.getString("TopPick"); + _PREHASH_SessionID = gMessageStringTable.getString("SessionID"); + _PREHASH_GlobalZ = gMessageStringTable.getString("GlobalZ"); + _PREHASH_DeclineFriendship = gMessageStringTable.getString("DeclineFriendship"); + _PREHASH_FormFriendship = gMessageStringTable.getString("FormFriendship"); + _PREHASH_TerminateFriendship = gMessageStringTable.getString("TerminateFriendship"); + _PREHASH_TaskData = gMessageStringTable.getString("TaskData"); + _PREHASH_SimWideMaxPrims = gMessageStringTable.getString("SimWideMaxPrims"); + _PREHASH_TotalPrims = gMessageStringTable.getString("TotalPrims"); + _PREHASH_SourceFilename = gMessageStringTable.getString("SourceFilename"); + _PREHASH_ProfileBegin = gMessageStringTable.getString("ProfileBegin"); + _PREHASH_MoneyDetailsRequest = gMessageStringTable.getString("MoneyDetailsRequest"); + _PREHASH_Request = gMessageStringTable.getString("Request"); + _PREHASH_GroupAccountDetailsRequest = gMessageStringTable.getString("GroupAccountDetailsRequest"); + _PREHASH_GroupActiveProposalsRequest = gMessageStringTable.getString("GroupActiveProposalsRequest"); + _PREHASH_StringValue = gMessageStringTable.getString("StringValue"); + _PREHASH_ClosestSimulator = gMessageStringTable.getString("ClosestSimulator"); + _PREHASH_Version = gMessageStringTable.getString("Version"); + _PREHASH_OtherCount = gMessageStringTable.getString("OtherCount"); + _PREHASH_MemberCount = gMessageStringTable.getString("MemberCount"); + _PREHASH_ChatData = gMessageStringTable.getString("ChatData"); + _PREHASH_IsGroupOwned = gMessageStringTable.getString("IsGroupOwned"); + _PREHASH_EnergyEfficiency = gMessageStringTable.getString("EnergyEfficiency"); + _PREHASH_MaxPlace = gMessageStringTable.getString("MaxPlace"); + _PREHASH_PickInfoUpdate = gMessageStringTable.getString("PickInfoUpdate"); + _PREHASH_PickDelete = gMessageStringTable.getString("PickDelete"); + _PREHASH_ScriptReset = gMessageStringTable.getString("ScriptReset"); + _PREHASH_Requester = gMessageStringTable.getString("Requester"); + _PREHASH_ForSale = gMessageStringTable.getString("ForSale"); + _PREHASH_NearestLandingRegionReply = gMessageStringTable.getString("NearestLandingRegionReply"); + _PREHASH_RecordAgentPresence = gMessageStringTable.getString("RecordAgentPresence"); + _PREHASH_EraseAgentPresence = gMessageStringTable.getString("EraseAgentPresence"); + _PREHASH_ParcelID = gMessageStringTable.getString("ParcelID"); + _PREHASH_Godlike = gMessageStringTable.getString("Godlike"); + _PREHASH_TotalDebits = gMessageStringTable.getString("TotalDebits"); + _PREHASH_Direction = gMessageStringTable.getString("Direction"); + _PREHASH_Appearance = gMessageStringTable.getString("Appearance"); + _PREHASH_HealthData = gMessageStringTable.getString("HealthData"); + _PREHASH_LeftAxis = gMessageStringTable.getString("LeftAxis"); + _PREHASH_LocationBlock = gMessageStringTable.getString("LocationBlock"); + _PREHASH_ObjectImage = gMessageStringTable.getString("ObjectImage"); + _PREHASH_TerrainStartHeight00 = gMessageStringTable.getString("TerrainStartHeight00"); + _PREHASH_TerrainStartHeight01 = gMessageStringTable.getString("TerrainStartHeight01"); + _PREHASH_TerrainStartHeight10 = gMessageStringTable.getString("TerrainStartHeight10"); + _PREHASH_ObjectHinge = gMessageStringTable.getString("ObjectHinge"); + _PREHASH_TerrainStartHeight11 = gMessageStringTable.getString("TerrainStartHeight11"); + _PREHASH_MetersPerGrid = gMessageStringTable.getString("MetersPerGrid"); + _PREHASH_WaterHeight = gMessageStringTable.getString("WaterHeight"); + _PREHASH_FetchInventoryReply = gMessageStringTable.getString("FetchInventoryReply"); + _PREHASH_MoneySummaryReply = gMessageStringTable.getString("MoneySummaryReply"); + _PREHASH_GroupAccountSummaryReply = gMessageStringTable.getString("GroupAccountSummaryReply"); + _PREHASH_AttachedSound = gMessageStringTable.getString("AttachedSound"); + _PREHASH_ParamInUse = gMessageStringTable.getString("ParamInUse"); + _PREHASH_GodKickUser = gMessageStringTable.getString("GodKickUser"); + _PREHASH_PickName = gMessageStringTable.getString("PickName"); + _PREHASH_TaskName = gMessageStringTable.getString("TaskName"); + _PREHASH_ParcelGodReserveForNewbie = gMessageStringTable.getString("ParcelGodReserveForNewbie"); + _PREHASH_SubType = gMessageStringTable.getString("SubType"); + _PREHASH_ObjectCount = gMessageStringTable.getString("ObjectCount"); + _PREHASH_RegionPresenceRequestByHandle = gMessageStringTable.getString("RegionPresenceRequestByHandle"); + _PREHASH_RezSingleAttachmentFromInv = gMessageStringTable.getString("RezSingleAttachmentFromInv"); + _PREHASH_ChildAgentUpdate = gMessageStringTable.getString("ChildAgentUpdate"); + _PREHASH_ToID = gMessageStringTable.getString("ToID"); + _PREHASH_ViewerPort = gMessageStringTable.getString("ViewerPort"); + _PREHASH_IsOwnerGroup = gMessageStringTable.getString("IsOwnerGroup"); + _PREHASH_AgentHeightWidth = gMessageStringTable.getString("AgentHeightWidth"); + _PREHASH_VerticalAngle = gMessageStringTable.getString("VerticalAngle"); + _PREHASH_WearableType = gMessageStringTable.getString("WearableType"); + _PREHASH_AggregatePermNextOwner = gMessageStringTable.getString("AggregatePermNextOwner"); + _PREHASH_ShowInList = gMessageStringTable.getString("ShowInList"); + _PREHASH_PositionSuggestion = gMessageStringTable.getString("PositionSuggestion"); + _PREHASH_UpdateParcel = gMessageStringTable.getString("UpdateParcel"); + _PREHASH_ClearAgentSessions = gMessageStringTable.getString("ClearAgentSessions"); + _PREHASH_SetAlwaysRun = gMessageStringTable.getString("SetAlwaysRun"); + _PREHASH_NVPair = gMessageStringTable.getString("NVPair"); + _PREHASH_ObjectSpinStart = gMessageStringTable.getString("ObjectSpinStart"); + _PREHASH_UseEstateSun = gMessageStringTable.getString("UseEstateSun"); + _PREHASH_LogoutBlock = gMessageStringTable.getString("LogoutBlock"); + _PREHASH_RelayLogControl = gMessageStringTable.getString("RelayLogControl"); + _PREHASH_RegionID = gMessageStringTable.getString("RegionID"); + _PREHASH_Creator = gMessageStringTable.getString("Creator"); + _PREHASH_ProposalText = gMessageStringTable.getString("ProposalText"); + _PREHASH_DirEventsReply = gMessageStringTable.getString("DirEventsReply"); + _PREHASH_EventInfoReply = gMessageStringTable.getString("EventInfoReply"); + _PREHASH_UserInfoReply = gMessageStringTable.getString("UserInfoReply"); + _PREHASH_PathRadiusOffset = gMessageStringTable.getString("PathRadiusOffset"); + _PREHASH_SessionInfo = gMessageStringTable.getString("SessionInfo"); + _PREHASH_TextureData = gMessageStringTable.getString("TextureData"); + _PREHASH_ChatPass = gMessageStringTable.getString("ChatPass"); + _PREHASH_TargetID = gMessageStringTable.getString("TargetID"); + _PREHASH_DefaultPayPrice = gMessageStringTable.getString("DefaultPayPrice"); + _PREHASH_UserLocation = gMessageStringTable.getString("UserLocation"); + _PREHASH_MaxPrims = gMessageStringTable.getString("MaxPrims"); + _PREHASH_RegionIP = gMessageStringTable.getString("RegionIP"); + _PREHASH_LandmarkID = gMessageStringTable.getString("LandmarkID"); + _PREHASH_InitiateDownload = gMessageStringTable.getString("InitiateDownload"); + _PREHASH_Name = gMessageStringTable.getString("Name"); + _PREHASH_OtherCleanTime = gMessageStringTable.getString("OtherCleanTime"); + _PREHASH_ParcelSetOtherCleanTime = gMessageStringTable.getString("ParcelSetOtherCleanTime"); + _PREHASH_TeleportPriceExponent = gMessageStringTable.getString("TeleportPriceExponent"); + _PREHASH_Gain = gMessageStringTable.getString("Gain"); + _PREHASH_VelX = gMessageStringTable.getString("VelX"); + _PREHASH_PacketAck = gMessageStringTable.getString("PacketAck"); + _PREHASH_PathSkew = gMessageStringTable.getString("PathSkew"); + _PREHASH_Negative = gMessageStringTable.getString("Negative"); + _PREHASH_VelY = gMessageStringTable.getString("VelY"); + _PREHASH_SimulatorShutdownRequest = gMessageStringTable.getString("SimulatorShutdownRequest"); + _PREHASH_NearestLandingRegionRequest = gMessageStringTable.getString("NearestLandingRegionRequest"); + _PREHASH_VelZ = gMessageStringTable.getString("VelZ"); + _PREHASH_OtherID = gMessageStringTable.getString("OtherID"); + _PREHASH_MemberID = gMessageStringTable.getString("MemberID"); + _PREHASH_MapLayerRequest = gMessageStringTable.getString("MapLayerRequest"); + _PREHASH_PatchVersion = gMessageStringTable.getString("PatchVersion"); + _PREHASH_ObjectScale = gMessageStringTable.getString("ObjectScale"); + _PREHASH_TargetIP = gMessageStringTable.getString("TargetIP"); + _PREHASH_Redo = gMessageStringTable.getString("Redo"); + _PREHASH_MoneyBalance = gMessageStringTable.getString("MoneyBalance"); + _PREHASH_TrackAgent = gMessageStringTable.getString("TrackAgent"); + _PREHASH_MaxX = gMessageStringTable.getString("MaxX"); + _PREHASH_Data = gMessageStringTable.getString("Data"); + _PREHASH_MaxY = gMessageStringTable.getString("MaxY"); + _PREHASH_TextureAnim = gMessageStringTable.getString("TextureAnim"); + _PREHASH_ReturnIDs = gMessageStringTable.getString("ReturnIDs"); + _PREHASH_Date = gMessageStringTable.getString("Date"); + _PREHASH_GestureUpdate = gMessageStringTable.getString("GestureUpdate"); + _PREHASH_AgentWearablesUpdate = gMessageStringTable.getString("AgentWearablesUpdate"); + _PREHASH_AgentDataUpdate = gMessageStringTable.getString("AgentDataUpdate"); + _PREHASH_Hash = gMessageStringTable.getString("Hash"); + _PREHASH_GroupDataUpdate = gMessageStringTable.getString("GroupDataUpdate"); + _PREHASH_AgentGroupDataUpdate = gMessageStringTable.getString("AgentGroupDataUpdate"); + _PREHASH_Left = gMessageStringTable.getString("Left"); + _PREHASH_Mask = gMessageStringTable.getString("Mask"); + _PREHASH_ForceMouselook = gMessageStringTable.getString("ForceMouselook"); + _PREHASH_Success = gMessageStringTable.getString("Success"); + _PREHASH_ObjectGroup = gMessageStringTable.getString("ObjectGroup"); + _PREHASH_SunHour = gMessageStringTable.getString("SunHour"); + _PREHASH_MinX = gMessageStringTable.getString("MinX"); + _PREHASH_ScriptSensorReply = gMessageStringTable.getString("ScriptSensorReply"); + _PREHASH_MinY = gMessageStringTable.getString("MinY"); + _PREHASH_Command = gMessageStringTable.getString("Command"); + _PREHASH_Desc = gMessageStringTable.getString("Desc"); + _PREHASH_AttachmentNeedsSave = gMessageStringTable.getString("AttachmentNeedsSave"); + _PREHASH_HistoryItemData = gMessageStringTable.getString("HistoryItemData"); + _PREHASH_AgentCachedTexture = gMessageStringTable.getString("AgentCachedTexture"); + _PREHASH_Subject = gMessageStringTable.getString("Subject"); + _PREHASH_East = gMessageStringTable.getString("East"); + _PREHASH_GodExpungeUser = gMessageStringTable.getString("GodExpungeUser"); + _PREHASH_QueryReplies = gMessageStringTable.getString("QueryReplies"); + _PREHASH_ObjectCategory = gMessageStringTable.getString("ObjectCategory"); + _PREHASH_Time = gMessageStringTable.getString("Time"); + _PREHASH_CreateLandmarkForEvent = gMessageStringTable.getString("CreateLandmarkForEvent"); + _PREHASH_ParentID = gMessageStringTable.getString("ParentID"); + _PREHASH_Ping = gMessageStringTable.getString("Ping"); + _PREHASH_Perp = gMessageStringTable.getString("Perp"); + _PREHASH_Code = gMessageStringTable.getString("Code"); + _PREHASH_InvType = gMessageStringTable.getString("InvType"); + _PREHASH_AgentFOV = gMessageStringTable.getString("AgentFOV"); + _PREHASH_BulkMoneyTransfer = gMessageStringTable.getString("BulkMoneyTransfer"); + _PREHASH_Audible = gMessageStringTable.getString("Audible"); + _PREHASH_AuctionData = gMessageStringTable.getString("AuctionData"); + _PREHASH_IDBlock = gMessageStringTable.getString("IDBlock"); + _PREHASH_ReputationData = gMessageStringTable.getString("ReputationData"); + _PREHASH_West = gMessageStringTable.getString("West"); + _PREHASH_Undo = gMessageStringTable.getString("Undo"); + _PREHASH_TotalNumItems = gMessageStringTable.getString("TotalNumItems"); + _PREHASH_Info = gMessageStringTable.getString("Info"); + _PREHASH_Area = gMessageStringTable.getString("Area"); + _PREHASH_Behavior = gMessageStringTable.getString("Behavior"); + _PREHASH_SimCrashed = gMessageStringTable.getString("SimCrashed"); + _PREHASH_Text = gMessageStringTable.getString("Text"); + _PREHASH_AgentToNewRegion = gMessageStringTable.getString("AgentToNewRegion"); + _PREHASH_PriceGroupCreate = gMessageStringTable.getString("PriceGroupCreate"); + _PREHASH_ObjectShape = gMessageStringTable.getString("ObjectShape"); + _PREHASH_GroupRoleDataReply = gMessageStringTable.getString("GroupRoleDataReply"); + _PREHASH_PosX = gMessageStringTable.getString("PosX"); + _PREHASH_PosY = gMessageStringTable.getString("PosY"); + _PREHASH_MuteCRC = gMessageStringTable.getString("MuteCRC"); + _PREHASH_PosZ = gMessageStringTable.getString("PosZ"); + _PREHASH_Size = gMessageStringTable.getString("Size"); + _PREHASH_FromAddress = gMessageStringTable.getString("FromAddress"); + _PREHASH_Body = gMessageStringTable.getString("Body"); + _PREHASH_FileData = gMessageStringTable.getString("FileData"); + _PREHASH_List = gMessageStringTable.getString("List"); + _PREHASH_KickUser = gMessageStringTable.getString("KickUser"); + _PREHASH_OtherPrims = gMessageStringTable.getString("OtherPrims"); + _PREHASH_RunTime = gMessageStringTable.getString("RunTime"); + _PREHASH_GrantUserRights = gMessageStringTable.getString("GrantUserRights"); + _PREHASH_RpcScriptRequestInboundForward = gMessageStringTable.getString("RpcScriptRequestInboundForward"); + _PREHASH_More = gMessageStringTable.getString("More"); + _PREHASH_Majority = gMessageStringTable.getString("Majority"); + _PREHASH_MetersTraveled = gMessageStringTable.getString("MetersTraveled"); + _PREHASH_Stat = gMessageStringTable.getString("Stat"); + _PREHASH_SoundID = gMessageStringTable.getString("SoundID"); + _PREHASH_Item = gMessageStringTable.getString("Item"); + _PREHASH_User = gMessageStringTable.getString("User"); + _PREHASH_RemoteInfos = gMessageStringTable.getString("RemoteInfos"); + _PREHASH_Prey = gMessageStringTable.getString("Prey"); + _PREHASH_UsecSinceStart = gMessageStringTable.getString("UsecSinceStart"); + _PREHASH_RayStart = gMessageStringTable.getString("RayStart"); + _PREHASH_ParcelData = gMessageStringTable.getString("ParcelData"); + _PREHASH_CameraUpAxis = gMessageStringTable.getString("CameraUpAxis"); + _PREHASH_ScriptDialog = gMessageStringTable.getString("ScriptDialog"); + _PREHASH_MasterParcelData = gMessageStringTable.getString("MasterParcelData"); + _PREHASH_Invalid = gMessageStringTable.getString("Invalid"); + _PREHASH_MinPlace = gMessageStringTable.getString("MinPlace"); + _PREHASH_ProfileCurve = gMessageStringTable.getString("ProfileCurve"); + _PREHASH_ParcelAccessListUpdate = gMessageStringTable.getString("ParcelAccessListUpdate"); + _PREHASH_MuteListUpdate = gMessageStringTable.getString("MuteListUpdate"); + _PREHASH_SendPacket = gMessageStringTable.getString("SendPacket"); + _PREHASH_SendXferPacket = gMessageStringTable.getString("SendXferPacket"); + _PREHASH_RegionDenyIdentified = gMessageStringTable.getString("RegionDenyIdentified"); + _PREHASH_NotecardItemID = gMessageStringTable.getString("NotecardItemID"); + _PREHASH_LastName = gMessageStringTable.getString("LastName"); + _PREHASH_From = gMessageStringTable.getString("From"); + _PREHASH_RoleChange = gMessageStringTable.getString("RoleChange"); + _PREHASH_Port = gMessageStringTable.getString("Port"); + _PREHASH_MemberTitle = gMessageStringTable.getString("MemberTitle"); + _PREHASH_LogParcelChanges = gMessageStringTable.getString("LogParcelChanges"); + _PREHASH_AgentCachedTextureResponse = gMessageStringTable.getString("AgentCachedTextureResponse"); + _PREHASH_DeRezObject = gMessageStringTable.getString("DeRezObject"); + _PREHASH_IsTemporary = gMessageStringTable.getString("IsTemporary"); + _PREHASH_InsigniaID = gMessageStringTable.getString("InsigniaID"); + _PREHASH_CheckFlags = gMessageStringTable.getString("CheckFlags"); + _PREHASH_TransferPriority = gMessageStringTable.getString("TransferPriority"); + _PREHASH_EventID = gMessageStringTable.getString("EventID"); + _PREHASH_Selected = gMessageStringTable.getString("Selected"); + _PREHASH_FromAgentId = gMessageStringTable.getString("FromAgentId"); + _PREHASH_Type = gMessageStringTable.getString("Type"); + _PREHASH_ChatType = gMessageStringTable.getString("ChatType"); + _PREHASH_ReportData = gMessageStringTable.getString("ReportData"); + _PREHASH_LeaderBoardData = gMessageStringTable.getString("LeaderBoardData"); + _PREHASH_RequestBlock = gMessageStringTable.getString("RequestBlock"); + _PREHASH_GrantData = gMessageStringTable.getString("GrantData"); + _PREHASH_DetachAttachmentIntoInv = gMessageStringTable.getString("DetachAttachmentIntoInv"); + _PREHASH_ParcelDisableObjects = gMessageStringTable.getString("ParcelDisableObjects"); + _PREHASH_Sections = gMessageStringTable.getString("Sections"); + _PREHASH_GodLevel = gMessageStringTable.getString("GodLevel"); + _PREHASH_PayPriceReply = gMessageStringTable.getString("PayPriceReply"); + _PREHASH_QueryID = gMessageStringTable.getString("QueryID"); + _PREHASH_CameraEyeOffset = gMessageStringTable.getString("CameraEyeOffset"); + _PREHASH_AgentPosition = gMessageStringTable.getString("AgentPosition"); + _PREHASH_GrabPosition = gMessageStringTable.getString("GrabPosition"); + _PREHASH_OnlineNotification = gMessageStringTable.getString("OnlineNotification"); + _PREHASH_OfflineNotification = gMessageStringTable.getString("OfflineNotification"); + _PREHASH_SendPostcard = gMessageStringTable.getString("SendPostcard"); + _PREHASH_RequestFlags = gMessageStringTable.getString("RequestFlags"); + _PREHASH_MoneyHistoryRequest = gMessageStringTable.getString("MoneyHistoryRequest"); + _PREHASH_MoneySummaryRequest = gMessageStringTable.getString("MoneySummaryRequest"); + _PREHASH_GroupAccountSummaryRequest = gMessageStringTable.getString("GroupAccountSummaryRequest"); + _PREHASH_GroupVoteHistoryRequest = gMessageStringTable.getString("GroupVoteHistoryRequest"); + _PREHASH_ParamValue = gMessageStringTable.getString("ParamValue"); + _PREHASH_Checksum = gMessageStringTable.getString("Checksum"); + _PREHASH_MaxAgents = gMessageStringTable.getString("MaxAgents"); + _PREHASH_CreateNewOutfitAttachments = gMessageStringTable.getString("CreateNewOutfitAttachments"); + _PREHASH_RegionHandle = gMessageStringTable.getString("RegionHandle"); + _PREHASH_TeleportProgress = gMessageStringTable.getString("TeleportProgress"); + _PREHASH_AgentQuitCopy = gMessageStringTable.getString("AgentQuitCopy"); + _PREHASH_ToViewer = gMessageStringTable.getString("ToViewer"); + _PREHASH_AvatarInterestsUpdate = gMessageStringTable.getString("AvatarInterestsUpdate"); + _PREHASH_GroupNoticeID = gMessageStringTable.getString("GroupNoticeID"); + _PREHASH_ParcelName = gMessageStringTable.getString("ParcelName"); + _PREHASH_PriceObjectRent = gMessageStringTable.getString("PriceObjectRent"); + _PREHASH_ConnectAgentToUserserver = gMessageStringTable.getString("ConnectAgentToUserserver"); + _PREHASH_ConnectToUserserver = gMessageStringTable.getString("ConnectToUserserver"); + _PREHASH_OfferCallingCard = gMessageStringTable.getString("OfferCallingCard"); + _PREHASH_AgentAccess = gMessageStringTable.getString("AgentAccess"); + _PREHASH_AcceptCallingCard = gMessageStringTable.getString("AcceptCallingCard"); + _PREHASH_DeclineCallingCard = gMessageStringTable.getString("DeclineCallingCard"); + _PREHASH_DataHomeLocationReply = gMessageStringTable.getString("DataHomeLocationReply"); + _PREHASH_EventLocationReply = gMessageStringTable.getString("EventLocationReply"); + _PREHASH_TerseDateID = gMessageStringTable.getString("TerseDateID"); + _PREHASH_ObjectOwner = gMessageStringTable.getString("ObjectOwner"); + _PREHASH_AssetID = gMessageStringTable.getString("AssetID"); + _PREHASH_AlertMessage = gMessageStringTable.getString("AlertMessage"); + _PREHASH_AgentAlertMessage = gMessageStringTable.getString("AgentAlertMessage"); + _PREHASH_EstateOwnerMessage = gMessageStringTable.getString("EstateOwnerMessage"); + _PREHASH_ParcelMediaCommandMessage = gMessageStringTable.getString("ParcelMediaCommandMessage"); + _PREHASH_Auction = gMessageStringTable.getString("Auction"); + _PREHASH_Category = gMessageStringTable.getString("Category"); + _PREHASH_FilePath = gMessageStringTable.getString("FilePath"); + _PREHASH_ItemFlags = gMessageStringTable.getString("ItemFlags"); + _PREHASH_Invoice = gMessageStringTable.getString("Invoice"); + _PREHASH_IntervalDays = gMessageStringTable.getString("IntervalDays"); + _PREHASH_PathScaleX = gMessageStringTable.getString("PathScaleX"); + _PREHASH_FromTaskID = gMessageStringTable.getString("FromTaskID"); + _PREHASH_TimeInfo = gMessageStringTable.getString("TimeInfo"); + _PREHASH_PathScaleY = gMessageStringTable.getString("PathScaleY"); + _PREHASH_PublicCount = gMessageStringTable.getString("PublicCount"); + _PREHASH_ParcelJoin = gMessageStringTable.getString("ParcelJoin"); + _PREHASH_GroupRolesCount = gMessageStringTable.getString("GroupRolesCount"); + _PREHASH_SimulatorBlock = gMessageStringTable.getString("SimulatorBlock"); + _PREHASH_GroupID = gMessageStringTable.getString("GroupID"); + _PREHASH_AgentVel = gMessageStringTable.getString("AgentVel"); + _PREHASH_RequestImage = gMessageStringTable.getString("RequestImage"); + _PREHASH_NetStats = gMessageStringTable.getString("NetStats"); + _PREHASH_AgentPos = gMessageStringTable.getString("AgentPos"); + _PREHASH_AgentSit = gMessageStringTable.getString("AgentSit"); + _PREHASH_Material = gMessageStringTable.getString("Material"); + _PREHASH_ObjectDeGrab = gMessageStringTable.getString("ObjectDeGrab"); + _PREHASH_VelocityInterpolateOff = gMessageStringTable.getString("VelocityInterpolateOff"); + _PREHASH_AuthorizedBuyerID = gMessageStringTable.getString("AuthorizedBuyerID"); + _PREHASH_AvatarPropertiesReply = gMessageStringTable.getString("AvatarPropertiesReply"); + _PREHASH_GroupProfileReply = gMessageStringTable.getString("GroupProfileReply"); + _PREHASH_SimOwner = gMessageStringTable.getString("SimOwner"); + _PREHASH_SalePrice = gMessageStringTable.getString("SalePrice"); + _PREHASH_Animation = gMessageStringTable.getString("Animation"); + _PREHASH_OwnerID = gMessageStringTable.getString("OwnerID"); + _PREHASH_NearestLandingRegionUpdated = gMessageStringTable.getString("NearestLandingRegionUpdated"); + _PREHASH_PassToAgent = gMessageStringTable.getString("PassToAgent"); + _PREHASH_PreyAgent = gMessageStringTable.getString("PreyAgent"); + _PREHASH_SimStats = gMessageStringTable.getString("SimStats"); + _PREHASH_Options = gMessageStringTable.getString("Options"); + _PREHASH_LogoutReply = gMessageStringTable.getString("LogoutReply"); + _PREHASH_FeatureDisabled = gMessageStringTable.getString("FeatureDisabled"); + _PREHASH_ObjectLocalID = gMessageStringTable.getString("ObjectLocalID"); + _PREHASH_Dropped = gMessageStringTable.getString("Dropped"); + _PREHASH_WebProfilesDisabled = gMessageStringTable.getString("WebProfilesDisabled"); + _PREHASH_Destination = gMessageStringTable.getString("Destination"); + _PREHASH_MasterID = gMessageStringTable.getString("MasterID"); + _PREHASH_TransferData = gMessageStringTable.getString("TransferData"); + _PREHASH_WantToMask = gMessageStringTable.getString("WantToMask"); + _PREHASH_AvatarData = gMessageStringTable.getString("AvatarData"); + _PREHASH_ParcelSelectObjects = gMessageStringTable.getString("ParcelSelectObjects"); + _PREHASH_ExtraParams = gMessageStringTable.getString("ExtraParams"); + _PREHASH_LogLogin = gMessageStringTable.getString("LogLogin"); + _PREHASH_CreatorID = gMessageStringTable.getString("CreatorID"); + _PREHASH_Summary = gMessageStringTable.getString("Summary"); + _PREHASH_BuyObjectInventory = gMessageStringTable.getString("BuyObjectInventory"); + _PREHASH_FetchInventory = gMessageStringTable.getString("FetchInventory"); + _PREHASH_InventoryID = gMessageStringTable.getString("InventoryID"); + _PREHASH_PacketNumber = gMessageStringTable.getString("PacketNumber"); + _PREHASH_SetFollowCamProperties = gMessageStringTable.getString("SetFollowCamProperties"); + _PREHASH_ClearFollowCamProperties = gMessageStringTable.getString("ClearFollowCamProperties"); + _PREHASH_SequenceID = gMessageStringTable.getString("SequenceID"); + _PREHASH_DataServerLogout = gMessageStringTable.getString("DataServerLogout"); + _PREHASH_NameValue = gMessageStringTable.getString("NameValue"); + _PREHASH_PathShearX = gMessageStringTable.getString("PathShearX"); + _PREHASH_PathShearY = gMessageStringTable.getString("PathShearY"); + _PREHASH_Velocity = gMessageStringTable.getString("Velocity"); + _PREHASH_SecPerYear = gMessageStringTable.getString("SecPerYear"); + _PREHASH_FirstName = gMessageStringTable.getString("FirstName"); + _PREHASH_AttachedSoundGainChange = gMessageStringTable.getString("AttachedSoundGainChange"); + _PREHASH_LocationID = gMessageStringTable.getString("LocationID"); + _PREHASH_Running = gMessageStringTable.getString("Running"); + _PREHASH_AgentThrottle = gMessageStringTable.getString("AgentThrottle"); + _PREHASH_NeighborList = gMessageStringTable.getString("NeighborList"); + _PREHASH_PathTaperX = gMessageStringTable.getString("PathTaperX"); + _PREHASH_PathTaperY = gMessageStringTable.getString("PathTaperY"); + _PREHASH_AgentRelated = gMessageStringTable.getString("AgentRelated"); + _PREHASH_GranterBlock = gMessageStringTable.getString("GranterBlock"); + _PREHASH_UseCachedMuteList = gMessageStringTable.getString("UseCachedMuteList"); + _PREHASH_FailStats = gMessageStringTable.getString("FailStats"); + _PREHASH_Tempfile = gMessageStringTable.getString("Tempfile"); + _PREHASH_BuyerID = gMessageStringTable.getString("BuyerID"); + _PREHASH_DirPeopleReply = gMessageStringTable.getString("DirPeopleReply"); + _PREHASH_TransferInfo = gMessageStringTable.getString("TransferInfo"); + _PREHASH_AvatarPickerRequestBackend = gMessageStringTable.getString("AvatarPickerRequestBackend"); + _PREHASH_AvatarPropertiesRequestBackend = gMessageStringTable.getString("AvatarPropertiesRequestBackend"); + _PREHASH_UpdateData = gMessageStringTable.getString("UpdateData"); + _PREHASH_SimFPS = gMessageStringTable.getString("SimFPS"); + _PREHASH_ReporterID = gMessageStringTable.getString("ReporterID"); + _PREHASH_ButtonLabel = gMessageStringTable.getString("ButtonLabel"); + _PREHASH_GranterID = gMessageStringTable.getString("GranterID"); + _PREHASH_WantToText = gMessageStringTable.getString("WantToText"); + _PREHASH_ReportType = gMessageStringTable.getString("ReportType"); + _PREHASH_DataBlock = gMessageStringTable.getString("DataBlock"); + _PREHASH_SimulatorReady = gMessageStringTable.getString("SimulatorReady"); + _PREHASH_AnimationSourceList = gMessageStringTable.getString("AnimationSourceList"); + _PREHASH_SubscribeLoad = gMessageStringTable.getString("SubscribeLoad"); + _PREHASH_UnsubscribeLoad = gMessageStringTable.getString("UnsubscribeLoad"); + _PREHASH_Packet = gMessageStringTable.getString("Packet"); + _PREHASH_UndoLand = gMessageStringTable.getString("UndoLand"); + _PREHASH_SimAccess = gMessageStringTable.getString("SimAccess"); + _PREHASH_MembershipFee = gMessageStringTable.getString("MembershipFee"); + _PREHASH_InviteGroupResponse = gMessageStringTable.getString("InviteGroupResponse"); + _PREHASH_CreateInventoryFolder = gMessageStringTable.getString("CreateInventoryFolder"); + _PREHASH_UpdateInventoryFolder = gMessageStringTable.getString("UpdateInventoryFolder"); + _PREHASH_MoveInventoryFolder = gMessageStringTable.getString("MoveInventoryFolder"); + _PREHASH_RemoveInventoryFolder = gMessageStringTable.getString("RemoveInventoryFolder"); + _PREHASH_MoneyData = gMessageStringTable.getString("MoneyData"); + _PREHASH_ObjectDeselect = gMessageStringTable.getString("ObjectDeselect"); + _PREHASH_NewAssetID = gMessageStringTable.getString("NewAssetID"); + _PREHASH_ObjectAdd = gMessageStringTable.getString("ObjectAdd"); + _PREHASH_RayEndIsIntersection = gMessageStringTable.getString("RayEndIsIntersection"); + _PREHASH_CompleteAuction = gMessageStringTable.getString("CompleteAuction"); + _PREHASH_CircuitCode = gMessageStringTable.getString("CircuitCode"); + _PREHASH_AgentMovementComplete = gMessageStringTable.getString("AgentMovementComplete"); + _PREHASH_ViewerIP = gMessageStringTable.getString("ViewerIP"); + _PREHASH_Header = gMessageStringTable.getString("Header"); + _PREHASH_GestureFlags = gMessageStringTable.getString("GestureFlags"); + _PREHASH_XferID = gMessageStringTable.getString("XferID"); + _PREHASH_StatValue = gMessageStringTable.getString("StatValue"); + _PREHASH_PickID = gMessageStringTable.getString("PickID"); + _PREHASH_TaskID = gMessageStringTable.getString("TaskID"); + _PREHASH_GridsPerEdge = gMessageStringTable.getString("GridsPerEdge"); + _PREHASH_RayEnd = gMessageStringTable.getString("RayEnd"); + _PREHASH_Throttles = gMessageStringTable.getString("Throttles"); + _PREHASH_RebakeAvatarTextures = gMessageStringTable.getString("RebakeAvatarTextures"); + _PREHASH_UpAxis = gMessageStringTable.getString("UpAxis"); + _PREHASH_AgentTextures = gMessageStringTable.getString("AgentTextures"); + _PREHASH_NotecardData = gMessageStringTable.getString("NotecardData"); + _PREHASH_Radius = gMessageStringTable.getString("Radius"); + _PREHASH_OffCircuit = gMessageStringTable.getString("OffCircuit"); + _PREHASH_Access = gMessageStringTable.getString("Access"); + _PREHASH_TitleRoleID = gMessageStringTable.getString("TitleRoleID"); + _PREHASH_SquareMetersCredit = gMessageStringTable.getString("SquareMetersCredit"); + _PREHASH_Filename = gMessageStringTable.getString("Filename"); + _PREHASH_SecuredTemplateChecksumRequest = gMessageStringTable.getString("SecuredTemplateChecksumRequest"); + _PREHASH_TemplateChecksumRequest = gMessageStringTable.getString("TemplateChecksumRequest"); + _PREHASH_AgentPresenceRequest = gMessageStringTable.getString("AgentPresenceRequest"); + _PREHASH_ClassifiedInfoRequest = gMessageStringTable.getString("ClassifiedInfoRequest"); + _PREHASH_ParcelInfoRequest = gMessageStringTable.getString("ParcelInfoRequest"); + _PREHASH_ParcelObjectOwnersRequest = gMessageStringTable.getString("ParcelObjectOwnersRequest"); + _PREHASH_TeleportLandmarkRequest = gMessageStringTable.getString("TeleportLandmarkRequest"); + _PREHASH_EventInfoRequest = gMessageStringTable.getString("EventInfoRequest"); + _PREHASH_ChatFromSimulator = gMessageStringTable.getString("ChatFromSimulator"); + _PREHASH_PickInfoRequest = gMessageStringTable.getString("PickInfoRequest"); + _PREHASH_MoneyBalanceRequest = gMessageStringTable.getString("MoneyBalanceRequest"); + _PREHASH_GroupMembersRequest = gMessageStringTable.getString("GroupMembersRequest"); + _PREHASH_GroupRoleMembersRequest = gMessageStringTable.getString("GroupRoleMembersRequest"); + _PREHASH_OldFolderID = gMessageStringTable.getString("OldFolderID"); + _PREHASH_UserInfoRequest = gMessageStringTable.getString("UserInfoRequest"); + _PREHASH_TextureID = gMessageStringTable.getString("TextureID"); + _PREHASH_ProfileURL = gMessageStringTable.getString("ProfileURL"); + _PREHASH_Handle = gMessageStringTable.getString("Handle"); + _PREHASH_StartParcelRenameAck = gMessageStringTable.getString("StartParcelRenameAck"); + _PREHASH_ButtonIndex = gMessageStringTable.getString("ButtonIndex"); + _PREHASH_GetScriptRunning = gMessageStringTable.getString("GetScriptRunning"); + _PREHASH_SetScriptRunning = gMessageStringTable.getString("SetScriptRunning"); + _PREHASH_Health = gMessageStringTable.getString("Health"); + _PREHASH_FileID = gMessageStringTable.getString("FileID"); + _PREHASH_CircuitInfo = gMessageStringTable.getString("CircuitInfo"); + _PREHASH_ObjectBuy = gMessageStringTable.getString("ObjectBuy"); + _PREHASH_ProfileEnd = gMessageStringTable.getString("ProfileEnd"); + _PREHASH_Effect = gMessageStringTable.getString("Effect"); + _PREHASH_TestMessage = gMessageStringTable.getString("TestMessage"); + _PREHASH_ScriptMailRegistration = gMessageStringTable.getString("ScriptMailRegistration"); + _PREHASH_AgentSetAppearance = gMessageStringTable.getString("AgentSetAppearance"); + _PREHASH_AvatarAppearance = gMessageStringTable.getString("AvatarAppearance"); + _PREHASH_RegionData = gMessageStringTable.getString("RegionData"); + _PREHASH_RequestingRegionData = gMessageStringTable.getString("RequestingRegionData"); + _PREHASH_LandingRegionData = gMessageStringTable.getString("LandingRegionData"); + _PREHASH_SitTransform = gMessageStringTable.getString("SitTransform"); + _PREHASH_TerrainBase0 = gMessageStringTable.getString("TerrainBase0"); + _PREHASH_SkillsMask = gMessageStringTable.getString("SkillsMask"); + _PREHASH_AtAxis = gMessageStringTable.getString("AtAxis"); + _PREHASH_TerrainBase1 = gMessageStringTable.getString("TerrainBase1"); + _PREHASH_Reason = gMessageStringTable.getString("Reason"); + _PREHASH_TerrainBase2 = gMessageStringTable.getString("TerrainBase2"); + _PREHASH_TerrainBase3 = gMessageStringTable.getString("TerrainBase3"); + _PREHASH_Params = gMessageStringTable.getString("Params"); + _PREHASH_PingID = gMessageStringTable.getString("PingID"); + _PREHASH_Change = gMessageStringTable.getString("Change"); + _PREHASH_Height = gMessageStringTable.getString("Height"); + _PREHASH_Region = gMessageStringTable.getString("Region"); + _PREHASH_MoneyHistoryReply = gMessageStringTable.getString("MoneyHistoryReply"); + _PREHASH_TelehubInfo = gMessageStringTable.getString("TelehubInfo"); + _PREHASH_StateSave = gMessageStringTable.getString("StateSave"); + _PREHASH_RoleData = gMessageStringTable.getString("RoleData"); + _PREHASH_AgentAnimation = gMessageStringTable.getString("AgentAnimation"); + _PREHASH_AvatarAnimation = gMessageStringTable.getString("AvatarAnimation"); + _PREHASH_LogDwellTime = gMessageStringTable.getString("LogDwellTime"); + _PREHASH_ParcelGodMarkAsContent = gMessageStringTable.getString("ParcelGodMarkAsContent"); + _PREHASH_UsePhysics = gMessageStringTable.getString("UsePhysics"); + _PREHASH_RegionDenyTransacted = gMessageStringTable.getString("RegionDenyTransacted"); + _PREHASH_JointType = gMessageStringTable.getString("JointType"); + _PREHASH_TaxEstimate = gMessageStringTable.getString("TaxEstimate"); + _PREHASH_ObjectTaxEstimate = gMessageStringTable.getString("ObjectTaxEstimate"); + _PREHASH_LightTaxEstimate = gMessageStringTable.getString("LightTaxEstimate"); + _PREHASH_TeleportLandingStatusChanged = gMessageStringTable.getString("TeleportLandingStatusChanged"); + _PREHASH_LandTaxEstimate = gMessageStringTable.getString("LandTaxEstimate"); + _PREHASH_GroupTaxEstimate = gMessageStringTable.getString("GroupTaxEstimate"); + _PREHASH_AvgViewerFPS = gMessageStringTable.getString("AvgViewerFPS"); + _PREHASH_Buttons = gMessageStringTable.getString("Buttons"); + _PREHASH_Sender = gMessageStringTable.getString("Sender"); + _PREHASH_Dialog = gMessageStringTable.getString("Dialog"); + _PREHASH_TargetData = gMessageStringTable.getString("TargetData"); + _PREHASH_DestID = gMessageStringTable.getString("DestID"); + _PREHASH_PricePublicObjectDelete = gMessageStringTable.getString("PricePublicObjectDelete"); + _PREHASH_ObjectDelete = gMessageStringTable.getString("ObjectDelete"); + _PREHASH_Delete = gMessageStringTable.getString("Delete"); + _PREHASH_EventGodDelete = gMessageStringTable.getString("EventGodDelete"); + _PREHASH_LastTaxDate = gMessageStringTable.getString("LastTaxDate"); + _PREHASH_MapImageID = gMessageStringTable.getString("MapImageID"); + _PREHASH_EndDateTime = gMessageStringTable.getString("EndDateTime"); + _PREHASH_TerrainDetail0 = gMessageStringTable.getString("TerrainDetail0"); + _PREHASH_TerrainDetail1 = gMessageStringTable.getString("TerrainDetail1"); + _PREHASH_TerrainDetail2 = gMessageStringTable.getString("TerrainDetail2"); + _PREHASH_TerrainDetail3 = gMessageStringTable.getString("TerrainDetail3"); + _PREHASH_Offset = gMessageStringTable.getString("Offset"); + _PREHASH_ObjectDelink = gMessageStringTable.getString("ObjectDelink"); + _PREHASH_TargetObject = gMessageStringTable.getString("TargetObject"); + _PREHASH_IsEstateManager = gMessageStringTable.getString("IsEstateManager"); + _PREHASH_CancelAuction = gMessageStringTable.getString("CancelAuction"); + _PREHASH_ObjectDetach = gMessageStringTable.getString("ObjectDetach"); + _PREHASH_Compressed = gMessageStringTable.getString("Compressed"); + _PREHASH_PathBegin = gMessageStringTable.getString("PathBegin"); + _PREHASH_BypassRaycast = gMessageStringTable.getString("BypassRaycast"); + _PREHASH_WinnerID = gMessageStringTable.getString("WinnerID"); + _PREHASH_ChannelType = gMessageStringTable.getString("ChannelType"); + _PREHASH_NonExemptMembers = gMessageStringTable.getString("NonExemptMembers"); + _PREHASH_Agents = gMessageStringTable.getString("Agents"); + _PREHASH_SimulatorStart = gMessageStringTable.getString("SimulatorStart"); + _PREHASH_Enable = gMessageStringTable.getString("Enable"); + _PREHASH_MemberData = gMessageStringTable.getString("MemberData"); + _PREHASH_ToGroupID = gMessageStringTable.getString("ToGroupID"); + _PREHASH_ImageNotInDatabase = gMessageStringTable.getString("ImageNotInDatabase"); + _PREHASH_StartDate = gMessageStringTable.getString("StartDate"); + _PREHASH_AnimID = gMessageStringTable.getString("AnimID"); + _PREHASH_Serial = gMessageStringTable.getString("Serial"); + _PREHASH_ControlPort = gMessageStringTable.getString("ControlPort"); + _PREHASH_ModifyLand = gMessageStringTable.getString("ModifyLand"); + _PREHASH_Digest = gMessageStringTable.getString("Digest"); + _PREHASH_Victim = gMessageStringTable.getString("Victim"); + _PREHASH_Script = gMessageStringTable.getString("Script"); + _PREHASH_TemplateChecksumReply = gMessageStringTable.getString("TemplateChecksumReply"); + _PREHASH_PickInfoReply = gMessageStringTable.getString("PickInfoReply"); + _PREHASH_MoneyBalanceReply = gMessageStringTable.getString("MoneyBalanceReply"); + _PREHASH_RoutedMoneyBalanceReply = gMessageStringTable.getString("RoutedMoneyBalanceReply"); + _PREHASH_RoleID = gMessageStringTable.getString("RoleID"); + _PREHASH_RegionInfo = gMessageStringTable.getString("RegionInfo"); + _PREHASH_Sequence = gMessageStringTable.getString("Sequence"); + _PREHASH_GodUpdateRegionInfo = gMessageStringTable.getString("GodUpdateRegionInfo"); + _PREHASH_LocalX = gMessageStringTable.getString("LocalX"); + _PREHASH_LocalY = gMessageStringTable.getString("LocalY"); + _PREHASH_StartAnim = gMessageStringTable.getString("StartAnim"); + _PREHASH_Location = gMessageStringTable.getString("Location"); + _PREHASH_Action = gMessageStringTable.getString("Action"); + _PREHASH_Rights = gMessageStringTable.getString("Rights"); + _PREHASH_SearchDir = gMessageStringTable.getString("SearchDir"); + _PREHASH_Active = gMessageStringTable.getString("Active"); + _PREHASH_TransferRequest = gMessageStringTable.getString("TransferRequest"); + _PREHASH_ScriptSensorRequest = gMessageStringTable.getString("ScriptSensorRequest"); + _PREHASH_MoneyTransferRequest = gMessageStringTable.getString("MoneyTransferRequest"); + _PREHASH_EjectGroupMemberRequest = gMessageStringTable.getString("EjectGroupMemberRequest"); + _PREHASH_SkillsText = gMessageStringTable.getString("SkillsText"); + _PREHASH_Resent = gMessageStringTable.getString("Resent"); + _PREHASH_Center = gMessageStringTable.getString("Center"); + _PREHASH_SharedData = gMessageStringTable.getString("SharedData"); + _PREHASH_PSBlock = gMessageStringTable.getString("PSBlock"); + _PREHASH_UUIDNameBlock = gMessageStringTable.getString("UUIDNameBlock"); + _PREHASH_Viewer = gMessageStringTable.getString("Viewer"); + _PREHASH_GroupNoticeDelete = gMessageStringTable.getString("GroupNoticeDelete"); + _PREHASH_GroupTitleUpdate = gMessageStringTable.getString("GroupTitleUpdate"); + _PREHASH_Method = gMessageStringTable.getString("Method"); + _PREHASH_TouchName = gMessageStringTable.getString("TouchName"); + _PREHASH_UpdateType = gMessageStringTable.getString("UpdateType"); + _PREHASH_KickedFromEstateID = gMessageStringTable.getString("KickedFromEstateID"); + _PREHASH_CandidateID = gMessageStringTable.getString("CandidateID"); + _PREHASH_ParamData = gMessageStringTable.getString("ParamData"); + _PREHASH_GodlikeMessage = gMessageStringTable.getString("GodlikeMessage"); + _PREHASH_SystemMessage = gMessageStringTable.getString("SystemMessage"); + _PREHASH_BodyRotation = gMessageStringTable.getString("BodyRotation"); + _PREHASH_SearchRegions = gMessageStringTable.getString("SearchRegions"); + _PREHASH_Ignore = gMessageStringTable.getString("Ignore"); + _PREHASH_AnimationData = gMessageStringTable.getString("AnimationData"); + _PREHASH_StatID = gMessageStringTable.getString("StatID"); + _PREHASH_ItemID = gMessageStringTable.getString("ItemID"); + _PREHASH_AvatarStatisticsReply = gMessageStringTable.getString("AvatarStatisticsReply"); + _PREHASH_ScriptDialogReply = gMessageStringTable.getString("ScriptDialogReply"); + _PREHASH_RegionIDAndHandleReply = gMessageStringTable.getString("RegionIDAndHandleReply"); + _PREHASH_CameraAtOffset = gMessageStringTable.getString("CameraAtOffset"); + _PREHASH_VoteID = gMessageStringTable.getString("VoteID"); + _PREHASH_ParcelGodForceOwner = gMessageStringTable.getString("ParcelGodForceOwner"); + _PREHASH_Filter = gMessageStringTable.getString("Filter"); + _PREHASH_InviteData = gMessageStringTable.getString("InviteData"); + _PREHASH_PCode = gMessageStringTable.getString("PCode"); + _PREHASH_SearchPos = gMessageStringTable.getString("SearchPos"); + _PREHASH_PreyID = gMessageStringTable.getString("PreyID"); + _PREHASH_TerrainLowerLimit = gMessageStringTable.getString("TerrainLowerLimit"); + _PREHASH_EventFlags = gMessageStringTable.getString("EventFlags"); + _PREHASH_TallyVotes = gMessageStringTable.getString("TallyVotes"); + _PREHASH_Result = gMessageStringTable.getString("Result"); + _PREHASH_LookAt = gMessageStringTable.getString("LookAt"); + _PREHASH_PayButton = gMessageStringTable.getString("PayButton"); + _PREHASH_SelfCount = gMessageStringTable.getString("SelfCount"); + _PREHASH_PacketCount = gMessageStringTable.getString("PacketCount"); + _PREHASH_ParcelBuyPass = gMessageStringTable.getString("ParcelBuyPass"); + _PREHASH_Identified = gMessageStringTable.getString("Identified"); + _PREHASH_OldItemID = gMessageStringTable.getString("OldItemID"); + _PREHASH_RegionPort = gMessageStringTable.getString("RegionPort"); + _PREHASH_PriceEnergyUnit = gMessageStringTable.getString("PriceEnergyUnit"); + _PREHASH_Bitmap = gMessageStringTable.getString("Bitmap"); + _PREHASH_TrackAgentSession = gMessageStringTable.getString("TrackAgentSession"); + _PREHASH_CacheMissType = gMessageStringTable.getString("CacheMissType"); + _PREHASH_VFileID = gMessageStringTable.getString("VFileID"); + _PREHASH_GroupInsigniaID = gMessageStringTable.getString("GroupInsigniaID"); + _PREHASH_FromID = gMessageStringTable.getString("FromID"); + _PREHASH_Online = gMessageStringTable.getString("Online"); + _PREHASH_KickFlags = gMessageStringTable.getString("KickFlags"); + _PREHASH_CovenantID = gMessageStringTable.getString("CovenantID"); + _PREHASH_SysCPU = gMessageStringTable.getString("SysCPU"); + _PREHASH_EMail = gMessageStringTable.getString("EMail"); + _PREHASH_AggregatePermTextures = gMessageStringTable.getString("AggregatePermTextures"); + _PREHASH_ChatChannel = gMessageStringTable.getString("ChatChannel"); + _PREHASH_ReturnID = gMessageStringTable.getString("ReturnID"); + _PREHASH_ObjectAttach = gMessageStringTable.getString("ObjectAttach"); + _PREHASH_TargetPort = gMessageStringTable.getString("TargetPort"); + _PREHASH_ObjectSpinStop = gMessageStringTable.getString("ObjectSpinStop"); + _PREHASH_FullID = gMessageStringTable.getString("FullID"); + _PREHASH_ActivateGroup = gMessageStringTable.getString("ActivateGroup"); + _PREHASH_SysGPU = gMessageStringTable.getString("SysGPU"); + _PREHASH_AvatarInterestsReply = gMessageStringTable.getString("AvatarInterestsReply"); + _PREHASH_StartLure = gMessageStringTable.getString("StartLure"); + _PREHASH_SysRAM = gMessageStringTable.getString("SysRAM"); + _PREHASH_ObjectPosition = gMessageStringTable.getString("ObjectPosition"); + _PREHASH_SitPosition = gMessageStringTable.getString("SitPosition"); + _PREHASH_StartTime = gMessageStringTable.getString("StartTime"); + _PREHASH_BornOn = gMessageStringTable.getString("BornOn"); + _PREHASH_CameraCollidePlane = gMessageStringTable.getString("CameraCollidePlane"); + _PREHASH_EconomyDataRequest = gMessageStringTable.getString("EconomyDataRequest"); + _PREHASH_TeleportLureRequest = gMessageStringTable.getString("TeleportLureRequest"); + _PREHASH_FolderID = gMessageStringTable.getString("FolderID"); + _PREHASH_RegionHandleRequest = gMessageStringTable.getString("RegionHandleRequest"); + _PREHASH_GestureRequest = gMessageStringTable.getString("GestureRequest"); + _PREHASH_ScriptDataRequest = gMessageStringTable.getString("ScriptDataRequest"); + _PREHASH_GroupRoleDataRequest = gMessageStringTable.getString("GroupRoleDataRequest"); + _PREHASH_GroupTitlesRequest = gMessageStringTable.getString("GroupTitlesRequest"); + _PREHASH_AgentWearablesRequest = gMessageStringTable.getString("AgentWearablesRequest"); + _PREHASH_MapBlockRequest = gMessageStringTable.getString("MapBlockRequest"); + _PREHASH_LureID = gMessageStringTable.getString("LureID"); + _PREHASH_CopyCenters = gMessageStringTable.getString("CopyCenters"); + _PREHASH_ParamList = gMessageStringTable.getString("ParamList"); + _PREHASH_InventorySerial = gMessageStringTable.getString("InventorySerial"); + _PREHASH_EdgeDataPacket = gMessageStringTable.getString("EdgeDataPacket"); + _PREHASH_AvatarPickerReply = gMessageStringTable.getString("AvatarPickerReply"); + _PREHASH_ParcelDwellReply = gMessageStringTable.getString("ParcelDwellReply"); + _PREHASH_IsForSale = gMessageStringTable.getString("IsForSale"); + _PREHASH_MuteID = gMessageStringTable.getString("MuteID"); + _PREHASH_MeanCollisionAlert = gMessageStringTable.getString("MeanCollisionAlert"); + _PREHASH_CanAcceptTasks = gMessageStringTable.getString("CanAcceptTasks"); + _PREHASH_ItemData = gMessageStringTable.getString("ItemData"); + _PREHASH_AnimationList = gMessageStringTable.getString("AnimationList"); + _PREHASH_PassObject = gMessageStringTable.getString("PassObject"); + _PREHASH_Reputation = gMessageStringTable.getString("Reputation"); + _PREHASH_IntValue = gMessageStringTable.getString("IntValue"); + _PREHASH_TargetType = gMessageStringTable.getString("TargetType"); + _PREHASH_Amount = gMessageStringTable.getString("Amount"); + _PREHASH_HasAttachment = gMessageStringTable.getString("HasAttachment"); + _PREHASH_UpdateAttachment = gMessageStringTable.getString("UpdateAttachment"); + _PREHASH_RemoveAttachment = gMessageStringTable.getString("RemoveAttachment"); + _PREHASH_HeightWidthBlock = gMessageStringTable.getString("HeightWidthBlock"); + _PREHASH_RequestObjectPropertiesFamily = gMessageStringTable.getString("RequestObjectPropertiesFamily"); + _PREHASH_ObjectPropertiesFamily = gMessageStringTable.getString("ObjectPropertiesFamily"); + _PREHASH_UserData = gMessageStringTable.getString("UserData"); + _PREHASH_IsReadable = gMessageStringTable.getString("IsReadable"); + _PREHASH_PathCurve = gMessageStringTable.getString("PathCurve"); + _PREHASH_Status = gMessageStringTable.getString("Status"); + _PREHASH_FromGroup = gMessageStringTable.getString("FromGroup"); + _PREHASH_AlreadyVoted = gMessageStringTable.getString("AlreadyVoted"); + _PREHASH_PlacesReply = gMessageStringTable.getString("PlacesReply"); + _PREHASH_DirPlacesReply = gMessageStringTable.getString("DirPlacesReply"); + _PREHASH_ParcelBuy = gMessageStringTable.getString("ParcelBuy"); + _PREHASH_DirFindQueryBackend = gMessageStringTable.getString("DirFindQueryBackend"); + _PREHASH_DirPlacesQueryBackend = gMessageStringTable.getString("DirPlacesQueryBackend"); + _PREHASH_DirClassifiedQueryBackend = gMessageStringTable.getString("DirClassifiedQueryBackend"); + _PREHASH_DirPicksQueryBackend = gMessageStringTable.getString("DirPicksQueryBackend"); + _PREHASH_DirLandQueryBackend = gMessageStringTable.getString("DirLandQueryBackend"); + _PREHASH_DirPopularQueryBackend = gMessageStringTable.getString("DirPopularQueryBackend"); + _PREHASH_LogoutDemand = gMessageStringTable.getString("LogoutDemand"); + _PREHASH_HistoryData = gMessageStringTable.getString("HistoryData"); + _PREHASH_SnapshotID = gMessageStringTable.getString("SnapshotID"); + _PREHASH_Aspect = gMessageStringTable.getString("Aspect"); + _PREHASH_ParamSize = gMessageStringTable.getString("ParamSize"); + _PREHASH_VoteCast = gMessageStringTable.getString("VoteCast"); + _PREHASH_CastsShadows = gMessageStringTable.getString("CastsShadows"); + _PREHASH_EveryoneMask = gMessageStringTable.getString("EveryoneMask"); + _PREHASH_SetSunPhase = gMessageStringTable.getString("SetSunPhase"); + _PREHASH_ObjectSpinUpdate = gMessageStringTable.getString("ObjectSpinUpdate"); + _PREHASH_MaturePublish = gMessageStringTable.getString("MaturePublish"); + _PREHASH_UseExistingAsset = gMessageStringTable.getString("UseExistingAsset"); + _PREHASH_Powers = gMessageStringTable.getString("Powers"); + _PREHASH_ParcelLocalID = gMessageStringTable.getString("ParcelLocalID"); + _PREHASH_TeleportCancel = gMessageStringTable.getString("TeleportCancel"); + _PREHASH_UnixTime = gMessageStringTable.getString("UnixTime"); + _PREHASH_QueryFlags = gMessageStringTable.getString("QueryFlags"); + _PREHASH_LastExecFroze = gMessageStringTable.getString("LastExecFroze"); + _PREHASH_AlwaysRun = gMessageStringTable.getString("AlwaysRun"); + _PREHASH_Bottom = gMessageStringTable.getString("Bottom"); + _PREHASH_ButtonData = gMessageStringTable.getString("ButtonData"); + _PREHASH_SoundData = gMessageStringTable.getString("SoundData"); + _PREHASH_ViewerStats = gMessageStringTable.getString("ViewerStats"); + _PREHASH_RegionHandshake = gMessageStringTable.getString("RegionHandshake"); + _PREHASH_ObjectDescription = gMessageStringTable.getString("ObjectDescription"); + _PREHASH_Description = gMessageStringTable.getString("Description"); + _PREHASH_ParamType = gMessageStringTable.getString("ParamType"); + _PREHASH_UUIDNameReply = gMessageStringTable.getString("UUIDNameReply"); + _PREHASH_UUIDGroupNameReply = gMessageStringTable.getString("UUIDGroupNameReply"); + _PREHASH_SaveAssetIntoInventory = gMessageStringTable.getString("SaveAssetIntoInventory"); + _PREHASH_UserInfo = gMessageStringTable.getString("UserInfo"); + _PREHASH_AnimSequenceID = gMessageStringTable.getString("AnimSequenceID"); + _PREHASH_NVPairs = gMessageStringTable.getString("NVPairs"); + _PREHASH_GroupNoticesListRequest = gMessageStringTable.getString("GroupNoticesListRequest"); + _PREHASH_ParcelAccessListRequest = gMessageStringTable.getString("ParcelAccessListRequest"); + _PREHASH_MuteListRequest = gMessageStringTable.getString("MuteListRequest"); + _PREHASH_StartPeriod = gMessageStringTable.getString("StartPeriod"); + _PREHASH_RpcChannelRequest = gMessageStringTable.getString("RpcChannelRequest"); + _PREHASH_LandStatRequest = gMessageStringTable.getString("LandStatRequest"); + _PREHASH_PlacesQuery = gMessageStringTable.getString("PlacesQuery"); + _PREHASH_DirPlacesQuery = gMessageStringTable.getString("DirPlacesQuery"); + _PREHASH_SortOrder = gMessageStringTable.getString("SortOrder"); + _PREHASH_Hunter = gMessageStringTable.getString("Hunter"); + _PREHASH_SunAngVelocity = gMessageStringTable.getString("SunAngVelocity"); + _PREHASH_BinaryBucket = gMessageStringTable.getString("BinaryBucket"); + _PREHASH_ImagePacket = gMessageStringTable.getString("ImagePacket"); + _PREHASH_StartGroupProposal = gMessageStringTable.getString("StartGroupProposal"); + _PREHASH_EnergyLevel = gMessageStringTable.getString("EnergyLevel"); + _PREHASH_PriceForListing = gMessageStringTable.getString("PriceForListing"); + _PREHASH_Scale = gMessageStringTable.getString("Scale"); + _PREHASH_EstateCovenantReply = gMessageStringTable.getString("EstateCovenantReply"); + _PREHASH_ParentEstateID = gMessageStringTable.getString("ParentEstateID"); + _PREHASH_Extra2 = gMessageStringTable.getString("Extra2"); + _PREHASH_Throttle = gMessageStringTable.getString("Throttle"); + _PREHASH_SimIP = gMessageStringTable.getString("SimIP"); + _PREHASH_GodID = gMessageStringTable.getString("GodID"); + _PREHASH_TeleportMinPrice = gMessageStringTable.getString("TeleportMinPrice"); + _PREHASH_VoteItem = gMessageStringTable.getString("VoteItem"); + _PREHASH_ObjectRotation = gMessageStringTable.getString("ObjectRotation"); + _PREHASH_SitRotation = gMessageStringTable.getString("SitRotation"); + _PREHASH_SnapSelection = gMessageStringTable.getString("SnapSelection"); + _PREHASH_SoundTrigger = gMessageStringTable.getString("SoundTrigger"); + _PREHASH_TerrainRaiseLimit = gMessageStringTable.getString("TerrainRaiseLimit"); + _PREHASH_Quorum = gMessageStringTable.getString("Quorum"); + _PREHASH_TokenBlock = gMessageStringTable.getString("TokenBlock"); + _PREHASH_AgentBlock = gMessageStringTable.getString("AgentBlock"); + _PREHASH_CommandBlock = gMessageStringTable.getString("CommandBlock"); + _PREHASH_PricePublicObjectDecay = gMessageStringTable.getString("PricePublicObjectDecay"); + _PREHASH_SpawnPointPos = gMessageStringTable.getString("SpawnPointPos"); + _PREHASH_AttachedSoundCutoffRadius = gMessageStringTable.getString("AttachedSoundCutoffRadius"); + _PREHASH_VolumeDetail = gMessageStringTable.getString("VolumeDetail"); + _PREHASH_FromAgentName = gMessageStringTable.getString("FromAgentName"); + _PREHASH_Range = gMessageStringTable.getString("Range"); + _PREHASH_DirectoryVisibility = gMessageStringTable.getString("DirectoryVisibility"); + _PREHASH_PublicIP = gMessageStringTable.getString("PublicIP"); + _PREHASH_TeleportFailed = gMessageStringTable.getString("TeleportFailed"); + _PREHASH_OnlineStatusReply = gMessageStringTable.getString("OnlineStatusReply"); + _PREHASH_RequestAvatarInfo = gMessageStringTable.getString("RequestAvatarInfo"); + _PREHASH_PreloadSound = gMessageStringTable.getString("PreloadSound"); + _PREHASH_ScreenshotID = gMessageStringTable.getString("ScreenshotID"); + _PREHASH_CovenantTimestamp = gMessageStringTable.getString("CovenantTimestamp"); + _PREHASH_OldestUnacked = gMessageStringTable.getString("OldestUnacked"); + _PREHASH_SimulatorIP = gMessageStringTable.getString("SimulatorIP"); + _PREHASH_ObjectImport = gMessageStringTable.getString("ObjectImport"); + _PREHASH_Value = gMessageStringTable.getString("Value"); + _PREHASH_JointAxisOrAnchor = gMessageStringTable.getString("JointAxisOrAnchor"); + _PREHASH_Test0 = gMessageStringTable.getString("Test0"); + _PREHASH_Test1 = gMessageStringTable.getString("Test1"); + _PREHASH_Test2 = gMessageStringTable.getString("Test2"); + _PREHASH_SunPhase = gMessageStringTable.getString("SunPhase"); + _PREHASH_Place = gMessageStringTable.getString("Place"); + _PREHASH_Phase = gMessageStringTable.getString("Phase"); + _PREHASH_ParcelDivide = gMessageStringTable.getString("ParcelDivide"); + _PREHASH_PriceObjectClaim = gMessageStringTable.getString("PriceObjectClaim"); + _PREHASH_Field = gMessageStringTable.getString("Field"); + _PREHASH_Ratio = gMessageStringTable.getString("Ratio"); + _PREHASH_JoinGroupReply = gMessageStringTable.getString("JoinGroupReply"); + _PREHASH_LiveHelpGroupReply = gMessageStringTable.getString("LiveHelpGroupReply"); + _PREHASH_Score = gMessageStringTable.getString("Score"); + _PREHASH_ExpungeData = gMessageStringTable.getString("ExpungeData"); + _PREHASH_Image = gMessageStringTable.getString("Image"); + _PREHASH_ObjectClickAction = gMessageStringTable.getString("ObjectClickAction"); + _PREHASH_Delta = gMessageStringTable.getString("Delta"); + _PREHASH_InitiateUpload = gMessageStringTable.getString("InitiateUpload"); + _PREHASH_Parameter = gMessageStringTable.getString("Parameter"); + _PREHASH_Flags = gMessageStringTable.getString("Flags"); + _PREHASH_Plane = gMessageStringTable.getString("Plane"); + _PREHASH_Width = gMessageStringTable.getString("Width"); + _PREHASH_Right = gMessageStringTable.getString("Right"); + _PREHASH_DirFindQuery = gMessageStringTable.getString("DirFindQuery"); + _PREHASH_Textures = gMessageStringTable.getString("Textures"); + _PREHASH_EventData = gMessageStringTable.getString("EventData"); + _PREHASH_Final = gMessageStringTable.getString("Final"); + _PREHASH_TelehubPos = gMessageStringTable.getString("TelehubPos"); + _PREHASH_ReportAutosaveCrash = gMessageStringTable.getString("ReportAutosaveCrash"); + _PREHASH_Reset = gMessageStringTable.getString("Reset"); + _PREHASH_CreateTrustedCircuit = gMessageStringTable.getString("CreateTrustedCircuit"); + _PREHASH_DenyTrustedCircuit = gMessageStringTable.getString("DenyTrustedCircuit"); + _PREHASH_RequestTrustedCircuit = gMessageStringTable.getString("RequestTrustedCircuit"); + _PREHASH_Codec = gMessageStringTable.getString("Codec"); + _PREHASH_Level = gMessageStringTable.getString("Level"); + _PREHASH_Modal = gMessageStringTable.getString("Modal"); + _PREHASH_ChildAgentUnknown = gMessageStringTable.getString("ChildAgentUnknown"); + _PREHASH_LandingType = gMessageStringTable.getString("LandingType"); + _PREHASH_ScriptRunningReply = gMessageStringTable.getString("ScriptRunningReply"); + _PREHASH_MoneyDetailsReply = gMessageStringTable.getString("MoneyDetailsReply"); + _PREHASH_Reply = gMessageStringTable.getString("Reply"); + _PREHASH_TelehubRot = gMessageStringTable.getString("TelehubRot"); + _PREHASH_RequestFriendship = gMessageStringTable.getString("RequestFriendship"); + _PREHASH_AcceptFriendship = gMessageStringTable.getString("AcceptFriendship"); + _PREHASH_GroupAccountDetailsReply = gMessageStringTable.getString("GroupAccountDetailsReply"); + _PREHASH_DwellInfo = gMessageStringTable.getString("DwellInfo"); + _PREHASH_AgentResume = gMessageStringTable.getString("AgentResume"); + _PREHASH_ItemType = gMessageStringTable.getString("ItemType"); + _PREHASH_MailFilter = gMessageStringTable.getString("MailFilter"); + _PREHASH_Disconnect = gMessageStringTable.getString("Disconnect"); + _PREHASH_SimPosition = gMessageStringTable.getString("SimPosition"); + _PREHASH_SimWideTotalPrims = gMessageStringTable.getString("SimWideTotalPrims"); + _PREHASH_Index = gMessageStringTable.getString("Index"); + _PREHASH_BaseFilename = gMessageStringTable.getString("BaseFilename"); + _PREHASH_SimFilename = gMessageStringTable.getString("SimFilename"); + _PREHASH_LastOwnerID = gMessageStringTable.getString("LastOwnerID"); + _PREHASH_GroupNoticeRequest = gMessageStringTable.getString("GroupNoticeRequest"); + _PREHASH_EmailMessageRequest = gMessageStringTable.getString("EmailMessageRequest"); + _PREHASH_MapItemRequest = gMessageStringTable.getString("MapItemRequest"); + _PREHASH_AgentCount = gMessageStringTable.getString("AgentCount"); + _PREHASH_MessageBlock = gMessageStringTable.getString("MessageBlock"); + _PREHASH_FuseBlock = gMessageStringTable.getString("FuseBlock"); + _PREHASH_AgentGroupData = gMessageStringTable.getString("AgentGroupData"); + _PREHASH_ClassifiedInfoUpdate = gMessageStringTable.getString("ClassifiedInfoUpdate"); + _PREHASH_RegionPos = gMessageStringTable.getString("RegionPos"); + _PREHASH_ParcelMediaUpdate = gMessageStringTable.getString("ParcelMediaUpdate"); + _PREHASH_NoticeID = gMessageStringTable.getString("NoticeID"); + _PREHASH_GridX = gMessageStringTable.getString("GridX"); + _PREHASH_GridY = gMessageStringTable.getString("GridY"); + _PREHASH_Title = gMessageStringTable.getString("Title"); + _PREHASH_AuctionID = gMessageStringTable.getString("AuctionID"); + _PREHASH_VoteType = gMessageStringTable.getString("VoteType"); + _PREHASH_CategoryID = gMessageStringTable.getString("CategoryID"); + _PREHASH_Token = gMessageStringTable.getString("Token"); + _PREHASH_AggregatePerms = gMessageStringTable.getString("AggregatePerms"); + _PREHASH_StartParcelRemoveAck = gMessageStringTable.getString("StartParcelRemoveAck"); + _PREHASH_ObjectSelect = gMessageStringTable.getString("ObjectSelect"); + _PREHASH_ForceObjectSelect = gMessageStringTable.getString("ForceObjectSelect"); + _PREHASH_Price = gMessageStringTable.getString("Price"); + _PREHASH_SunDirection = gMessageStringTable.getString("SunDirection"); + _PREHASH_FromName = gMessageStringTable.getString("FromName"); + _PREHASH_ChangeInventoryItemFlags = gMessageStringTable.getString("ChangeInventoryItemFlags"); + _PREHASH_Force = gMessageStringTable.getString("Force"); + _PREHASH_TransactionBlock = gMessageStringTable.getString("TransactionBlock"); + _PREHASH_PowersMask = gMessageStringTable.getString("PowersMask"); + _PREHASH_Stamp = gMessageStringTable.getString("Stamp"); + _PREHASH_TotalCredits = gMessageStringTable.getString("TotalCredits"); + _PREHASH_State = gMessageStringTable.getString("State"); + _PREHASH_TextureIndex = gMessageStringTable.getString("TextureIndex"); + _PREHASH_InviteeID = gMessageStringTable.getString("InviteeID"); + _PREHASH_ParcelReclaim = gMessageStringTable.getString("ParcelReclaim"); + _PREHASH_Money = gMessageStringTable.getString("Money"); + _PREHASH_PathTwist = gMessageStringTable.getString("PathTwist"); + _PREHASH_AuthBuyerID = gMessageStringTable.getString("AuthBuyerID"); + _PREHASH_Color = gMessageStringTable.getString("Color"); + _PREHASH_SourceType = gMessageStringTable.getString("SourceType"); + _PREHASH_World = gMessageStringTable.getString("World"); + _PREHASH_QueryData = gMessageStringTable.getString("QueryData"); + _PREHASH_Users = gMessageStringTable.getString("Users"); + _PREHASH_SysOS = gMessageStringTable.getString("SysOS"); + _PREHASH_Notes = gMessageStringTable.getString("Notes"); + _PREHASH_AvatarID = gMessageStringTable.getString("AvatarID"); + _PREHASH_FounderID = gMessageStringTable.getString("FounderID"); + _PREHASH_EndPointID = gMessageStringTable.getString("EndPointID"); + _PREHASH_StipendEstimate = gMessageStringTable.getString("StipendEstimate"); + _PREHASH_LocationLookAt = gMessageStringTable.getString("LocationLookAt"); + _PREHASH_Sound = gMessageStringTable.getString("Sound"); + _PREHASH_Cover = gMessageStringTable.getString("Cover"); + _PREHASH_TotalObjectCount = gMessageStringTable.getString("TotalObjectCount"); + _PREHASH_TextureEntry = gMessageStringTable.getString("TextureEntry"); + _PREHASH_SquareMetersCommitted = gMessageStringTable.getString("SquareMetersCommitted"); + _PREHASH_ChannelID = gMessageStringTable.getString("ChannelID"); + _PREHASH_Dwell = gMessageStringTable.getString("Dwell"); + _PREHASH_North = gMessageStringTable.getString("North"); + _PREHASH_AgentUpdate = gMessageStringTable.getString("AgentUpdate"); + _PREHASH_PickGodDelete = gMessageStringTable.getString("PickGodDelete"); + _PREHASH_HostName = gMessageStringTable.getString("HostName"); + _PREHASH_PriceParcelClaim = gMessageStringTable.getString("PriceParcelClaim"); + _PREHASH_ParcelClaim = gMessageStringTable.getString("ParcelClaim"); + _PREHASH_AgentPowers = gMessageStringTable.getString("AgentPowers"); + _PREHASH_ProfileHollow = gMessageStringTable.getString("ProfileHollow"); + _PREHASH_GroupRoleChanges = gMessageStringTable.getString("GroupRoleChanges"); + _PREHASH_Count = gMessageStringTable.getString("Count"); + _PREHASH_South = gMessageStringTable.getString("South"); + _PREHASH_Entry = gMessageStringTable.getString("Entry"); + _PREHASH_ObjectUpdateCompressed = gMessageStringTable.getString("ObjectUpdateCompressed"); + _PREHASH_MuteFlags = gMessageStringTable.getString("MuteFlags"); + _PREHASH_Group = gMessageStringTable.getString("Group"); + _PREHASH_AgentPause = gMessageStringTable.getString("AgentPause"); + _PREHASH_LanguagesText = gMessageStringTable.getString("LanguagesText"); + _PREHASH_InternalScriptMail = gMessageStringTable.getString("InternalScriptMail"); + _PREHASH_FindAgent = gMessageStringTable.getString("FindAgent"); + _PREHASH_AgentData = gMessageStringTable.getString("AgentData"); + _PREHASH_FolderData = gMessageStringTable.getString("FolderData"); + _PREHASH_AssetBlock = gMessageStringTable.getString("AssetBlock"); + _PREHASH_AcceptNotices = gMessageStringTable.getString("AcceptNotices"); + _PREHASH_SetGroupAcceptNotices = gMessageStringTable.getString("SetGroupAcceptNotices"); + _PREHASH_CloseCircuit = gMessageStringTable.getString("CloseCircuit"); + _PREHASH_LogControl = gMessageStringTable.getString("LogControl"); + _PREHASH_TeleportFinish = gMessageStringTable.getString("TeleportFinish"); + _PREHASH_PathRevolutions = gMessageStringTable.getString("PathRevolutions"); + _PREHASH_ClassifiedInfoReply = gMessageStringTable.getString("ClassifiedInfoReply"); + _PREHASH_ParcelInfoReply = gMessageStringTable.getString("ParcelInfoReply"); + _PREHASH_AutosaveData = gMessageStringTable.getString("AutosaveData"); + _PREHASH_SetStartLocation = gMessageStringTable.getString("SetStartLocation"); + _PREHASH_PassHours = gMessageStringTable.getString("PassHours"); + _PREHASH_AttachmentPt = gMessageStringTable.getString("AttachmentPt"); + _PREHASH_ParcelFlags = gMessageStringTable.getString("ParcelFlags"); + _PREHASH_NumVotes = gMessageStringTable.getString("NumVotes"); + _PREHASH_AvatarPickerRequest = gMessageStringTable.getString("AvatarPickerRequest"); + _PREHASH_TeleportLocationRequest = gMessageStringTable.getString("TeleportLocationRequest"); + _PREHASH_DataHomeLocationRequest = gMessageStringTable.getString("DataHomeLocationRequest"); + _PREHASH_EventNotificationAddRequest = gMessageStringTable.getString("EventNotificationAddRequest"); + _PREHASH_ParcelDwellRequest = gMessageStringTable.getString("ParcelDwellRequest"); + _PREHASH_EventLocationRequest = gMessageStringTable.getString("EventLocationRequest"); + _PREHASH_EndPeriod = gMessageStringTable.getString("EndPeriod"); + _PREHASH_SetStartLocationRequest = gMessageStringTable.getString("SetStartLocationRequest"); + _PREHASH_QueryStart = gMessageStringTable.getString("QueryStart"); + _PREHASH_EjectData = gMessageStringTable.getString("EjectData"); + _PREHASH_AvatarTextureUpdate = gMessageStringTable.getString("AvatarTextureUpdate"); + _PREHASH_RPCServerPort = gMessageStringTable.getString("RPCServerPort"); + _PREHASH_Bytes = gMessageStringTable.getString("Bytes"); + _PREHASH_Extra = gMessageStringTable.getString("Extra"); + _PREHASH_ForceScriptControlRelease = gMessageStringTable.getString("ForceScriptControlRelease"); + _PREHASH_ParcelRelease = gMessageStringTable.getString("ParcelRelease"); + _PREHASH_VFileType = gMessageStringTable.getString("VFileType"); + _PREHASH_EjectGroupMemberReply = gMessageStringTable.getString("EjectGroupMemberReply"); + _PREHASH_ImageData = gMessageStringTable.getString("ImageData"); + _PREHASH_SpaceServerSimulatorTimeMessage = gMessageStringTable.getString("SpaceServerSimulatorTimeMessage"); + _PREHASH_SimulatorViewerTimeMessage = gMessageStringTable.getString("SimulatorViewerTimeMessage"); + _PREHASH_Rotation = gMessageStringTable.getString("Rotation"); + _PREHASH_Selection = gMessageStringTable.getString("Selection"); + _PREHASH_TransactionData = gMessageStringTable.getString("TransactionData"); + _PREHASH_OperationData = gMessageStringTable.getString("OperationData"); + _PREHASH_ExpirationDate = gMessageStringTable.getString("ExpirationDate"); + _PREHASH_ParcelDeedToGroup = gMessageStringTable.getString("ParcelDeedToGroup"); + _PREHASH_DirPicksReply = gMessageStringTable.getString("DirPicksReply"); + _PREHASH_AvatarPicksReply = gMessageStringTable.getString("AvatarPicksReply"); + _PREHASH_GroupTitlesReply = gMessageStringTable.getString("GroupTitlesReply"); + _PREHASH_AgentInfo = gMessageStringTable.getString("AgentInfo"); + _PREHASH_MoneyTransferBackend = gMessageStringTable.getString("MoneyTransferBackend"); + _PREHASH_NextOwnerMask = gMessageStringTable.getString("NextOwnerMask"); + _PREHASH_MuteData = gMessageStringTable.getString("MuteData"); + _PREHASH_PassPrice = gMessageStringTable.getString("PassPrice"); + _PREHASH_SourceID = gMessageStringTable.getString("SourceID"); + _PREHASH_ChangeUserRights = gMessageStringTable.getString("ChangeUserRights"); + _PREHASH_TeleportFlags = gMessageStringTable.getString("TeleportFlags"); + _PREHASH_AssetData = gMessageStringTable.getString("AssetData"); + _PREHASH_SlaveParcelData = gMessageStringTable.getString("SlaveParcelData"); + _PREHASH_MultipleObjectUpdate = gMessageStringTable.getString("MultipleObjectUpdate"); + _PREHASH_ObjectUpdate = gMessageStringTable.getString("ObjectUpdate"); + _PREHASH_ImprovedTerseObjectUpdate = gMessageStringTable.getString("ImprovedTerseObjectUpdate"); + _PREHASH_ConfirmXferPacket = gMessageStringTable.getString("ConfirmXferPacket"); + _PREHASH_StartPingCheck = gMessageStringTable.getString("StartPingCheck"); + _PREHASH_SimWideDeletes = gMessageStringTable.getString("SimWideDeletes"); + _PREHASH_LandStatReply = gMessageStringTable.getString("LandStatReply"); + _PREHASH_IsPhantom = gMessageStringTable.getString("IsPhantom"); + _PREHASH_AgentList = gMessageStringTable.getString("AgentList"); + _PREHASH_SimApproved = gMessageStringTable.getString("SimApproved"); + _PREHASH_RezObject = gMessageStringTable.getString("RezObject"); + _PREHASH_TaskLocalID = gMessageStringTable.getString("TaskLocalID"); + _PREHASH_ClaimDate = gMessageStringTable.getString("ClaimDate"); + _PREHASH_MergeParcel = gMessageStringTable.getString("MergeParcel"); + _PREHASH_Priority = gMessageStringTable.getString("Priority"); + _PREHASH_Building = gMessageStringTable.getString("Building"); + _PREHASH_QueryText = gMessageStringTable.getString("QueryText"); + _PREHASH_GroupNoticeAdd = gMessageStringTable.getString("GroupNoticeAdd"); + _PREHASH_ReturnType = gMessageStringTable.getString("ReturnType"); + _PREHASH_FetchFolders = gMessageStringTable.getString("FetchFolders"); + _PREHASH_SimulatorPublicHostBlock = gMessageStringTable.getString("SimulatorPublicHostBlock"); + _PREHASH_HeaderData = gMessageStringTable.getString("HeaderData"); + _PREHASH_RequestMultipleObjects = gMessageStringTable.getString("RequestMultipleObjects"); + _PREHASH_RetrieveInstantMessages = gMessageStringTable.getString("RetrieveInstantMessages"); + _PREHASH_DequeueInstantMessages = gMessageStringTable.getString("DequeueInstantMessages"); + _PREHASH_OpenCircuit = gMessageStringTable.getString("OpenCircuit"); + _PREHASH_SecureSessionID = gMessageStringTable.getString("SecureSessionID"); + _PREHASH_CrossedRegion = gMessageStringTable.getString("CrossedRegion"); + _PREHASH_DirGroupsReply = gMessageStringTable.getString("DirGroupsReply"); + _PREHASH_AvatarGroupsReply = gMessageStringTable.getString("AvatarGroupsReply"); + _PREHASH_EmailMessageReply = gMessageStringTable.getString("EmailMessageReply"); + _PREHASH_GroupVoteHistoryItemReply = gMessageStringTable.getString("GroupVoteHistoryItemReply"); + _PREHASH_ViewerPosition = gMessageStringTable.getString("ViewerPosition"); + _PREHASH_Position = gMessageStringTable.getString("Position"); + _PREHASH_ParentEstate = gMessageStringTable.getString("ParentEstate"); + _PREHASH_EstateName = gMessageStringTable.getString("EstateName"); + _PREHASH_MuteName = gMessageStringTable.getString("MuteName"); + _PREHASH_StartParcelRename = gMessageStringTable.getString("StartParcelRename"); + _PREHASH_BulkParcelRename = gMessageStringTable.getString("BulkParcelRename"); + _PREHASH_ParcelRename = gMessageStringTable.getString("ParcelRename"); + _PREHASH_ViewerFilename = gMessageStringTable.getString("ViewerFilename"); + _PREHASH_Positive = gMessageStringTable.getString("Positive"); + _PREHASH_UserReportInternal = gMessageStringTable.getString("UserReportInternal"); + _PREHASH_AvatarPropertiesRequest = gMessageStringTable.getString("AvatarPropertiesRequest"); + _PREHASH_ParcelPropertiesRequest = gMessageStringTable.getString("ParcelPropertiesRequest"); + _PREHASH_GroupProfileRequest = gMessageStringTable.getString("GroupProfileRequest"); + _PREHASH_AgentDataUpdateRequest = gMessageStringTable.getString("AgentDataUpdateRequest"); + _PREHASH_PriceObjectScaleFactor = gMessageStringTable.getString("PriceObjectScaleFactor"); + _PREHASH_DirPicksQuery = gMessageStringTable.getString("DirPicksQuery"); + _PREHASH_OpenEnrollment = gMessageStringTable.getString("OpenEnrollment"); + _PREHASH_GroupData = gMessageStringTable.getString("GroupData"); + _PREHASH_RequestGodlikePowers = gMessageStringTable.getString("RequestGodlikePowers"); + _PREHASH_GrantGodlikePowers = gMessageStringTable.getString("GrantGodlikePowers"); + _PREHASH_TransactionID = gMessageStringTable.getString("TransactionID"); + _PREHASH_DestinationID = gMessageStringTable.getString("DestinationID"); + _PREHASH_Controls = gMessageStringTable.getString("Controls"); + _PREHASH_FirstDetachAll = gMessageStringTable.getString("FirstDetachAll"); + _PREHASH_EstateID = gMessageStringTable.getString("EstateID"); + _PREHASH_ImprovedInstantMessage = gMessageStringTable.getString("ImprovedInstantMessage"); + _PREHASH_AgentQuit = gMessageStringTable.getString("AgentQuit"); + _PREHASH_CheckParcelSales = gMessageStringTable.getString("CheckParcelSales"); + _PREHASH_ParcelSales = gMessageStringTable.getString("ParcelSales"); + _PREHASH_CurrentInterval = gMessageStringTable.getString("CurrentInterval"); + _PREHASH_PriceRentLight = gMessageStringTable.getString("PriceRentLight"); + _PREHASH_MediaAutoScale = gMessageStringTable.getString("MediaAutoScale"); + _PREHASH_NeighborBlock = gMessageStringTable.getString("NeighborBlock"); + _PREHASH_LayerData = gMessageStringTable.getString("LayerData"); + _PREHASH_NVPairData = gMessageStringTable.getString("NVPairData"); + _PREHASH_TeleportLocal = gMessageStringTable.getString("TeleportLocal"); + _PREHASH_EjecteeID = gMessageStringTable.getString("EjecteeID"); + _PREHASH_VoteInitiator = gMessageStringTable.getString("VoteInitiator"); + _PREHASH_TypeData = gMessageStringTable.getString("TypeData"); + _PREHASH_OwnerIDs = gMessageStringTable.getString("OwnerIDs"); + _PREHASH_SystemKickUser = gMessageStringTable.getString("SystemKickUser"); + _PREHASH_TransactionTime = gMessageStringTable.getString("TransactionTime"); + _PREHASH_TimeToLive = gMessageStringTable.getString("TimeToLive"); + _PREHASH_StartParcelRemove = gMessageStringTable.getString("StartParcelRemove"); + _PREHASH_BulkParcelRemove = gMessageStringTable.getString("BulkParcelRemove"); + _PREHASH_OldAgentID = gMessageStringTable.getString("OldAgentID"); + _PREHASH_BonusEstimate = gMessageStringTable.getString("BonusEstimate"); + _PREHASH_MusicURL = gMessageStringTable.getString("MusicURL"); + _PREHASH_CompleteLure = gMessageStringTable.getString("CompleteLure"); + _PREHASH_ParcelPrimBonus = gMessageStringTable.getString("ParcelPrimBonus"); + _PREHASH_EjectUser = gMessageStringTable.getString("EjectUser"); + _PREHASH_CoarseLocationUpdate = gMessageStringTable.getString("CoarseLocationUpdate"); + _PREHASH_ChildAgentPositionUpdate = gMessageStringTable.getString("ChildAgentPositionUpdate"); + _PREHASH_StoreLocal = gMessageStringTable.getString("StoreLocal"); + _PREHASH_GroupName = gMessageStringTable.getString("GroupName"); + _PREHASH_PriceParcelRent = gMessageStringTable.getString("PriceParcelRent"); + _PREHASH_SimStatus = gMessageStringTable.getString("SimStatus"); + _PREHASH_TransactionSuccess = gMessageStringTable.getString("TransactionSuccess"); + _PREHASH_LureType = gMessageStringTable.getString("LureType"); + _PREHASH_GroupMask = gMessageStringTable.getString("GroupMask"); + _PREHASH_SitObject = gMessageStringTable.getString("SitObject"); + _PREHASH_Override = gMessageStringTable.getString("Override"); + _PREHASH_LocomotionState = gMessageStringTable.getString("LocomotionState"); + _PREHASH_PriceUpload = gMessageStringTable.getString("PriceUpload"); + _PREHASH_RemoveParcel = gMessageStringTable.getString("RemoveParcel"); + _PREHASH_ConfirmAuctionStart = gMessageStringTable.getString("ConfirmAuctionStart"); + _PREHASH_RpcScriptRequestInbound = gMessageStringTable.getString("RpcScriptRequestInbound"); + _PREHASH_ActiveGroupID = gMessageStringTable.getString("ActiveGroupID"); + _PREHASH_ParcelReturnObjects = gMessageStringTable.getString("ParcelReturnObjects"); + _PREHASH_TotalObjects = gMessageStringTable.getString("TotalObjects"); + _PREHASH_ObjectExtraParams = gMessageStringTable.getString("ObjectExtraParams"); + _PREHASH_Questions = gMessageStringTable.getString("Questions"); + _PREHASH_TransferAbort = gMessageStringTable.getString("TransferAbort"); + _PREHASH_TransferInventory = gMessageStringTable.getString("TransferInventory"); + _PREHASH_RayTargetID = gMessageStringTable.getString("RayTargetID"); + _PREHASH_ClaimPrice = gMessageStringTable.getString("ClaimPrice"); + _PREHASH_ObjectProperties = gMessageStringTable.getString("ObjectProperties"); + _PREHASH_ParcelProperties = gMessageStringTable.getString("ParcelProperties"); + _PREHASH_EstateOwnerID = gMessageStringTable.getString("EstateOwnerID"); + _PREHASH_LogoutRequest = gMessageStringTable.getString("LogoutRequest"); + _PREHASH_AssetUploadRequest = gMessageStringTable.getString("AssetUploadRequest"); + _PREHASH_ReputationIndividualRequest = gMessageStringTable.getString("ReputationIndividualRequest"); + _PREHASH_MajorVersion = gMessageStringTable.getString("MajorVersion"); + _PREHASH_MinorVersion = gMessageStringTable.getString("MinorVersion"); + _PREHASH_SimulatorAssign = gMessageStringTable.getString("SimulatorAssign"); + _PREHASH_TransactionType = gMessageStringTable.getString("TransactionType"); + _PREHASH_AvatarPropertiesUpdate = gMessageStringTable.getString("AvatarPropertiesUpdate"); + _PREHASH_ParcelPropertiesUpdate = gMessageStringTable.getString("ParcelPropertiesUpdate"); + _PREHASH_FetchItems = gMessageStringTable.getString("FetchItems"); + _PREHASH_AbortXfer = gMessageStringTable.getString("AbortXfer"); + _PREHASH_DeRezAck = gMessageStringTable.getString("DeRezAck"); + _PREHASH_TakeControls = gMessageStringTable.getString("TakeControls"); + _PREHASH_DirLandReply = gMessageStringTable.getString("DirLandReply"); + _PREHASH_SpaceLocationTeleportReply = gMessageStringTable.getString("SpaceLocationTeleportReply"); + _PREHASH_MuteType = gMessageStringTable.getString("MuteType"); + _PREHASH_IMViaEMail = gMessageStringTable.getString("IMViaEMail"); + _PREHASH_StartExpungeProcessAck = gMessageStringTable.getString("StartExpungeProcessAck"); + _PREHASH_RentPrice = gMessageStringTable.getString("RentPrice"); + _PREHASH_GenericMessage = gMessageStringTable.getString("GenericMessage"); + _PREHASH_ChildAgentAlive = gMessageStringTable.getString("ChildAgentAlive"); + _PREHASH_AssetType = gMessageStringTable.getString("AssetType"); + _PREHASH_SpawnPointBlock = gMessageStringTable.getString("SpawnPointBlock"); + _PREHASH_AttachmentBlock = gMessageStringTable.getString("AttachmentBlock"); + _PREHASH_ObjectMaterial = gMessageStringTable.getString("ObjectMaterial"); + _PREHASH_OwnerName = gMessageStringTable.getString("OwnerName"); + _PREHASH_AvatarNotesReply = gMessageStringTable.getString("AvatarNotesReply"); + _PREHASH_CacheID = gMessageStringTable.getString("CacheID"); + _PREHASH_OwnerMask = gMessageStringTable.getString("OwnerMask"); + _PREHASH_TransferInventoryAck = gMessageStringTable.getString("TransferInventoryAck"); +} diff --git a/indra/llmessage/message_prehash.h b/indra/llmessage/message_prehash.h new file mode 100644 index 0000000000..0da2e02f83 --- /dev/null +++ b/indra/llmessage/message_prehash.h @@ -0,0 +1,1498 @@ +/** + * @file message_prehash.h + * @brief header file of externs of prehashed variables plus defines. + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_MESSAGE_PREHASH_H +#define LL_MESSAGE_PREHASH_H + +/** + * Generated from message template version number 1.053 + */ + + +extern F32 gPrehashVersionNumber; + +extern char * _PREHASH_X; +extern char * _PREHASH_Y; +extern char * _PREHASH_Z; +extern char * _PREHASH_AddFlags; +extern char * _PREHASH_ReservedNewbie; +extern char * _PREHASH_FailureInfo; +extern char * _PREHASH_MapData; +extern char * _PREHASH_AddItem; +extern char * _PREHASH_MeanCollision; +extern char * _PREHASH_RezScript; +extern char * _PREHASH_AvatarSitResponse; +extern char * _PREHASH_InventoryAssetResponse; +extern char * _PREHASH_KillObject; +extern char * _PREHASH_ProposalID; +extern char * _PREHASH_SerialNum; +extern char * _PREHASH_Duration; +extern char * _PREHASH_ScriptQuestion; +extern char * _PREHASH_AddCircuitCode; +extern char * _PREHASH_UseCircuitCode; +extern char * _PREHASH_ViewerCircuitCode; +extern char * _PREHASH_ScriptAnswerYes; +extern char * _PREHASH_PartnerID; +extern char * _PREHASH_DirLandQuery; +extern char * _PREHASH_TeleportStart; +extern char * _PREHASH_LogMessages; +extern char * _PREHASH_AboutText; +extern char * _PREHASH_VisualParam; +extern char * _PREHASH_GroupPrims; +extern char * _PREHASH_SelectedPrims; +extern char * _PREHASH_ID; +extern char * _PREHASH_UUIDNameRequest; +extern char * _PREHASH_UUIDGroupNameRequest; +extern char * _PREHASH_MoneyTransactionsRequest; +extern char * _PREHASH_GroupAccountTransactionsRequest; +extern char * _PREHASH_MapNameRequest; +extern char * _PREHASH_MailTaskSimRequest; +extern char * _PREHASH_UpdateSimulator; +extern char * _PREHASH_BillableFactor; +extern char * _PREHASH_ObjectBonusFactor; +extern char * _PREHASH_EnableSimulator; +extern char * _PREHASH_DisableSimulator; +extern char * _PREHASH_ConfirmEnableSimulator; +extern char * _PREHASH_LayerType; +extern char * _PREHASH_OwnerRole; +extern char * _PREHASH_ParcelOverlay; +extern char * _PREHASH_AdjustBalance; +extern char * _PREHASH_GroupOwned; +extern char * _PREHASH_IP; +extern char * _PREHASH_ChatFromViewer; +extern char * _PREHASH_AvgAgentsInView; +extern char * _PREHASH_AgentsInView; +extern char * _PREHASH_GroupTitle; +extern char * _PREHASH_MapLayerReply; +extern char * _PREHASH_CompoundMsgID; +extern char * _PREHASH_CameraConstraint; +extern char * _PREHASH_DownloadTotals; +extern char * _PREHASH_GenCounter; +extern char * _PREHASH_FrozenData; +extern char * _PREHASH_ChildAgentDying; +extern char * _PREHASH_To; +extern char * _PREHASH_CopyInventoryFromNotecard; +extern char * _PREHASH_RezObjectFromNotecard; +extern char * _PREHASH_ParcelDirFeeCurrent; +extern char * _PREHASH_SeedCapability; +extern char * _PREHASH_ObjectDuplicate; +extern char * _PREHASH_InventoryData; +extern char * _PREHASH_ReplyData; +extern char * _PREHASH_ResetList; +extern char * _PREHASH_MediaID; +extern char * _PREHASH_RelatedRights; +extern char * _PREHASH_RedirectGridX; +extern char * _PREHASH_RedirectGridY; +extern char * _PREHASH_TransferID; +extern char * _PREHASH_Transacted; +extern char * _PREHASH_TexturesChanged; +extern char * _PREHASH_UserLookAt; +extern char * _PREHASH_TestBlock1; +extern char * _PREHASH_SensedData; +extern char * _PREHASH_UpdateBlock; +extern char * _PREHASH_ClassifiedGodDelete; +extern char * _PREHASH_ObjectGrabUpdate; +extern char * _PREHASH_TaxDate; +extern char * _PREHASH_LocationPos; +extern char * _PREHASH_StartDateTime; +extern char * _PREHASH_ObjectUpdateCached; +extern char * _PREHASH_Packets; +extern char * _PREHASH_FailureType; +extern char * _PREHASH_UpdateGroupInfo; +extern char * _PREHASH_ObjectPermissions; +extern char * _PREHASH_RevokePermissions; +extern char * _PREHASH_UpdateFlags; +extern char * _PREHASH_ObjectExportSelected; +extern char * _PREHASH_RezSelected; +extern char * _PREHASH_AutoPilot; +extern char * _PREHASH_UpdateMuteListEntry; +extern char * _PREHASH_RemoveMuteListEntry; +extern char * _PREHASH_SetSimStatusInDatabase; +extern char * _PREHASH_SetSimPresenceInDatabase; +extern char * _PREHASH_CameraProperty; +extern char * _PREHASH_BrushSize; +extern char * _PREHASH_StartExpungeProcess; +extern char * _PREHASH_SimulatorSetMap; +extern char * _PREHASH_RegionPresenceRequestByRegionID; +extern char * _PREHASH_ParcelObjectOwnersReply; +extern char * _PREHASH_GroupMembersReply; +extern char * _PREHASH_GroupRoleMembersReply; +extern char * _PREHASH_RequestRegionInfo; +extern char * _PREHASH_AABBMax; +extern char * _PREHASH_RequestPayPrice; +extern char * _PREHASH_SimulatorPresentAtLocation; +extern char * _PREHASH_AgentRequestSit; +extern char * _PREHASH_AABBMin; +extern char * _PREHASH_ClassifiedFlags; +extern char * _PREHASH_ControlFlags; +extern char * _PREHASH_TeleportRequest; +extern char * _PREHASH_SpaceLocationTeleportRequest; +extern char * _PREHASH_LeaderBoardRequest; +extern char * _PREHASH_ScriptTeleportRequest; +extern char * _PREHASH_DateUTC; +extern char * _PREHASH_TaskIDs; +extern char * _PREHASH_EstateCovenantRequest; +extern char * _PREHASH_RequestResult; +extern char * _PREHASH_ReputationAgentAssign; +extern char * _PREHASH_CanAcceptAgents; +extern char * _PREHASH_ObjectSaleInfo; +extern char * _PREHASH_KillChildAgents; +extern char * _PREHASH_Balance; +extern char * _PREHASH_DerezContainer; +extern char * _PREHASH_ObjectData; +extern char * _PREHASH_CameraAtAxis; +extern char * _PREHASH_InfoBlock; +extern char * _PREHASH_OwnershipCost; +extern char * _PREHASH_AvatarNotesUpdate; +extern char * _PREHASH_PID; +extern char * _PREHASH_TimeString; +extern char * _PREHASH_DirPopularReply; +extern char * _PREHASH_TerrainHeightRange00; +extern char * _PREHASH_SimData; +extern char * _PREHASH_TerrainHeightRange01; +extern char * _PREHASH_TerrainHeightRange10; +extern char * _PREHASH_TerrainHeightRange11; +extern char * _PREHASH_UpdateInventoryItem; +extern char * _PREHASH_UpdateCreateInventoryItem; +extern char * _PREHASH_MoveInventoryItem; +extern char * _PREHASH_CopyInventoryItem; +extern char * _PREHASH_RemoveInventoryItem; +extern char * _PREHASH_CreateInventoryItem; +extern char * _PREHASH_PathTwistBegin; +extern char * _PREHASH_CRC; +extern char * _PREHASH_AttachmentPoint; +extern char * _PREHASH_TelehubBlock; +extern char * _PREHASH_FOVBlock; +extern char * _PREHASH_StartLocationData; +extern char * _PREHASH_PositionData; +extern char * _PREHASH_TimeSinceLast; +extern char * _PREHASH_MapImage; +extern char * _PREHASH_Objects; +extern char * _PREHASH_URL; +extern char * _PREHASH_CreationDate; +extern char * _PREHASH_JointPivot; +extern char * _PREHASH_RateeID; +extern char * _PREHASH_FPS; +extern char * _PREHASH_HasTelehub; +extern char * _PREHASH_PathEnd; +extern char * _PREHASH_ScriptDataReply; +extern char * _PREHASH_MapBlockReply; +extern char * _PREHASH_PropertiesData; +extern char * _PREHASH_ViewerEffect; +extern char * _PREHASH_FreezeUser; +extern char * _PREHASH_OwnerPrims; +extern char * _PREHASH_ObjectGrab; +extern char * _PREHASH_ToAgentID; +extern char * _PREHASH_SimulatorMapUpdate; +extern char * _PREHASH_TransferPacket; +extern char * _PREHASH_ObjectName; +extern char * _PREHASH_GroupPowers; +extern char * _PREHASH_OriginalName; +extern char * _PREHASH_CompletePingCheck; +extern char * _PREHASH_OnlineStatus; +extern char * _PREHASH_ObjectDrop; +extern char * _PREHASH_UseBigPackets; +extern char * _PREHASH_GroupNoticesListReply; +extern char * _PREHASH_ParcelAccessListReply; +extern char * _PREHASH_RpcChannelReply; +extern char * _PREHASH_RegionPresenceResponse; +extern char * _PREHASH_AgentPresenceResponse; +extern char * _PREHASH_CharterMember; +extern char * _PREHASH_EdgeData; +extern char * _PREHASH_NameData; +extern char * _PREHASH_RegionPushOverride; +extern char * _PREHASH_SimName; +extern char * _PREHASH_UserReport; +extern char * _PREHASH_DownloadPriority; +extern char * _PREHASH_ToAgentId; +extern char * _PREHASH_Mag; +extern char * _PREHASH_DirPopularQuery; +extern char * _PREHASH_ParcelPropertiesRequestByID; +extern char * _PREHASH_ObjectLink; +extern char * _PREHASH_RpcScriptReplyInbound; +extern char * _PREHASH_BoardData; +extern char * _PREHASH_RezData; +extern char * _PREHASH_RemoveInventoryObjects; +extern char * _PREHASH_GroupProposalBallot; +extern char * _PREHASH_RPCServerIP; +extern char * _PREHASH_Far; +extern char * _PREHASH_GodSessionID; +extern char * _PREHASH_ViewerDigest; +extern char * _PREHASH_FLAboutText; +extern char * _PREHASH_RegionHandshakeReply; +extern char * _PREHASH_GroupActiveProposalItemReply; +extern char * _PREHASH_MapItemReply; +extern char * _PREHASH_Seconds; +extern char * _PREHASH_UpdateUserInfo; +extern char * _PREHASH_AggregatePermTexturesOwner; +extern char * _PREHASH_Set; +extern char * _PREHASH_NewName; +extern char * _PREHASH_Key; +extern char * _PREHASH_AgentID; +extern char * _PREHASH_OnlineStatusRequest; +extern char * _PREHASH_EventNotificationRemoveRequest; +extern char * _PREHASH_NewFolderID; +extern char * _PREHASH_Arc; +extern char * _PREHASH_RegionX; +extern char * _PREHASH_RegionY; +extern char * _PREHASH_RequestData; +extern char * _PREHASH_Msg; +extern char * _PREHASH_Top; +extern char * _PREHASH_MiscStats; +extern char * _PREHASH_ImageID; +extern char * _PREHASH_DataPacket; +extern char * _PREHASH_ObjectDehinge; +extern char * _PREHASH_You; +extern char * _PREHASH_ScriptControlChange; +extern char * _PREHASH_LoadURL; +extern char * _PREHASH_SetCPURatio; +extern char * _PREHASH_NameValueData; +extern char * _PREHASH_AtomicPassObject; +extern char * _PREHASH_ErrorMessage; +extern char * _PREHASH_ViewerFrozenMessage; +extern char * _PREHASH_HealthMessage; +extern char * _PREHASH_LogTextMessage; +extern char * _PREHASH_TimeDilation; +extern char * _PREHASH_RemoveContribution; +extern char * _PREHASH_Contribution; +extern char * _PREHASH_SetGroupContribution; +extern char * _PREHASH_Offline; +extern char * _PREHASH_AgentIsNowWearing; +extern char * _PREHASH_SecPerDay; +extern char * _PREHASH_Members; +extern char * _PREHASH_FailedResends; +extern char * _PREHASH_CameraCenter; +extern char * _PREHASH_CameraLeftAxis; +extern char * _PREHASH_ExBlock; +extern char * _PREHASH_Channel; +extern char * _PREHASH_NetTest; +extern char * _PREHASH_DiscardLevel; +extern char * _PREHASH_LayerID; +extern char * _PREHASH_RatorID; +extern char * _PREHASH_GrabOffset; +extern char * _PREHASH_SimPort; +extern char * _PREHASH_PricePerMeter; +extern char * _PREHASH_RegionFlags; +extern char * _PREHASH_VoteResult; +extern char * _PREHASH_ParcelDirFeeEstimate; +extern char * _PREHASH_ModifyBlock; +extern char * _PREHASH_InventoryBlock; +extern char * _PREHASH_ReplyBlock; +extern char * _PREHASH_ValidUntil; +extern char * _PREHASH_VelocityInterpolateOn; +extern char * _PREHASH_ClassifiedDelete; +extern char * _PREHASH_RegionDenyAnonymous; +extern char * _PREHASH_FLImageID; +extern char * _PREHASH_AllowPublish; +extern char * _PREHASH_SitName; +extern char * _PREHASH_RegionsVisited; +extern char * _PREHASH_DirClassifiedReply; +extern char * _PREHASH_AvatarClassifiedReply; +extern char * _PREHASH_ReputationIndividualReply; +extern char * _PREHASH_MediaURL; +extern char * _PREHASH_CompleteAgentMovement; +extern char * _PREHASH_SpaceIP; +extern char * _PREHASH_ClassifiedID; +extern char * _PREHASH_LocalID; +extern char * _PREHASH_RemoveItem; +extern char * _PREHASH_LogFailedMoneyTransaction; +extern char * _PREHASH_ViewerStartAuction; +extern char * _PREHASH_StartAuction; +extern char * _PREHASH_NameValueName; +extern char * _PREHASH_AngVelX; +extern char * _PREHASH_DuplicateFlags; +extern char * _PREHASH_AngVelY; +extern char * _PREHASH_AngVelZ; +extern char * _PREHASH_TextColor; +extern char * _PREHASH_SlaveID; +extern char * _PREHASH_Charter; +extern char * _PREHASH_AlertData; +extern char * _PREHASH_TargetBlock; +extern char * _PREHASH_CheckParcelAuctions; +extern char * _PREHASH_ParcelAuctions; +extern char * _PREHASH_OwnerIsGroup; +extern char * _PREHASH_NameValuePair; +extern char * _PREHASH_RemoveNameValuePair; +extern char * _PREHASH_GetNameValuePair; +extern char * _PREHASH_BulkUpdateInventory; +extern char * _PREHASH_UpdateTaskInventory; +extern char * _PREHASH_RemoveTaskInventory; +extern char * _PREHASH_MoveTaskInventory; +extern char * _PREHASH_RequestTaskInventory; +extern char * _PREHASH_ReplyTaskInventory; +extern char * _PREHASH_DeclineInventory; +extern char * _PREHASH_AggregatePermInventory; +extern char * _PREHASH_SimulatorInfo; +extern char * _PREHASH_MoneyTransactionsReply; +extern char * _PREHASH_GroupAccountTransactionsReply; +extern char * _PREHASH_MailTaskSimReply; +extern char * _PREHASH_WearableData; +extern char * _PREHASH_StatisticsData; +extern char * _PREHASH_Enabled; +extern char * _PREHASH_Savings; +extern char * _PREHASH_SimulatorLoad; +extern char * _PREHASH_InternalRegionIP; +extern char * _PREHASH_ExternalRegionIP; +extern char * _PREHASH_TotalPairs; +extern char * _PREHASH_CreateGroupRequest; +extern char * _PREHASH_JoinGroupRequest; +extern char * _PREHASH_LeaveGroupRequest; +extern char * _PREHASH_InviteGroupRequest; +extern char * _PREHASH_LiveHelpGroupRequest; +extern char * _PREHASH_ServerVersion; +extern char * _PREHASH_PriceParcelClaimFactor; +extern char * _PREHASH_BillableArea; +extern char * _PREHASH_ObjectID; +extern char * _PREHASH_ObjectFlagUpdate; +extern char * _PREHASH_GroupRoleUpdate; +extern char * _PREHASH_RequestInventoryAsset; +extern char * _PREHASH_RedoLand; +extern char * _PREHASH_TravelAccess; +extern char * _PREHASH_ChangedGrid; +extern char * _PREHASH_AgentDropGroup; +extern char * _PREHASH_Details; +extern char * _PREHASH_LocationX; +extern char * _PREHASH_SaleType; +extern char * _PREHASH_LocationY; +extern char * _PREHASH_LocationZ; +extern char * _PREHASH_EconomyData; +extern char * _PREHASH_HeadRotation; +extern char * _PREHASH_DeleteOnCompletion; +extern char * _PREHASH_PublicPort; +extern char * _PREHASH_DirClassifiedQuery; +extern char * _PREHASH_CallbackID; +extern char * _PREHASH_RequestParcelTransfer; +extern char * _PREHASH_RoleCount; +extern char * _PREHASH_ObjectCapacity; +extern char * _PREHASH_RequestID; +extern char * _PREHASH_RequestXfer; +extern char * _PREHASH_ObjectTaxCurrent; +extern char * _PREHASH_LightTaxCurrent; +extern char * _PREHASH_LandTaxCurrent; +extern char * _PREHASH_GroupTaxCurrent; +extern char * _PREHASH_FetchInventoryDescendents; +extern char * _PREHASH_InventoryDescendents; +extern char * _PREHASH_Descendents; +extern char * _PREHASH_PurgeInventoryDescendents; +extern char * _PREHASH_ShowDir; +extern char * _PREHASH_IsOwner; +extern char * _PREHASH_Timestamp; +extern char * _PREHASH_GlobalPos; +extern char * _PREHASH_GrabOffsetInitial; +extern char * _PREHASH_IsTrial; +extern char * _PREHASH_FinalizeLogout; +extern char * _PREHASH_ObjectDuplicateOnRay; +extern char * _PREHASH_GroupMembershipCount; +extern char * _PREHASH_MethodData; +extern char * _PREHASH_ActivateGestures; +extern char * _PREHASH_DeactivateGestures; +extern char * _PREHASH_ProposalData; +extern char * _PREHASH_PosGlobal; +extern char * _PREHASH_SearchID; +extern char * _PREHASH_RezMultipleAttachmentsFromInv; +extern char * _PREHASH_SearchName; +extern char * _PREHASH_VersionString; +extern char * _PREHASH_CreateGroupReply; +extern char * _PREHASH_LeaveGroupReply; +extern char * _PREHASH_ActualArea; +extern char * _PREHASH_Message; +extern char * _PREHASH_ClickAction; +extern char * _PREHASH_AssetUploadComplete; +extern char * _PREHASH_RequestType; +extern char * _PREHASH_UUID; +extern char * _PREHASH_BaseMask; +extern char * _PREHASH_NetBlock; +extern char * _PREHASH_GlobalX; +extern char * _PREHASH_GlobalY; +extern char * _PREHASH_CopyRotates; +extern char * _PREHASH_KickUserAck; +extern char * _PREHASH_TopPick; +extern char * _PREHASH_SessionID; +extern char * _PREHASH_GlobalZ; +extern char * _PREHASH_DeclineFriendship; +extern char * _PREHASH_FormFriendship; +extern char * _PREHASH_TerminateFriendship; +extern char * _PREHASH_TaskData; +extern char * _PREHASH_SimWideMaxPrims; +extern char * _PREHASH_TotalPrims; +extern char * _PREHASH_SourceFilename; +extern char * _PREHASH_ProfileBegin; +extern char * _PREHASH_MoneyDetailsRequest; +extern char * _PREHASH_Request; +extern char * _PREHASH_GroupAccountDetailsRequest; +extern char * _PREHASH_GroupActiveProposalsRequest; +extern char * _PREHASH_StringValue; +extern char * _PREHASH_ClosestSimulator; +extern char * _PREHASH_Version; +extern char * _PREHASH_OtherCount; +extern char * _PREHASH_MemberCount; +extern char * _PREHASH_ChatData; +extern char * _PREHASH_IsGroupOwned; +extern char * _PREHASH_EnergyEfficiency; +extern char * _PREHASH_MaxPlace; +extern char * _PREHASH_PickInfoUpdate; +extern char * _PREHASH_PickDelete; +extern char * _PREHASH_ScriptReset; +extern char * _PREHASH_Requester; +extern char * _PREHASH_ForSale; +extern char * _PREHASH_NearestLandingRegionReply; +extern char * _PREHASH_RecordAgentPresence; +extern char * _PREHASH_EraseAgentPresence; +extern char * _PREHASH_ParcelID; +extern char * _PREHASH_Godlike; +extern char * _PREHASH_TotalDebits; +extern char * _PREHASH_Direction; +extern char * _PREHASH_Appearance; +extern char * _PREHASH_HealthData; +extern char * _PREHASH_LeftAxis; +extern char * _PREHASH_LocationBlock; +extern char * _PREHASH_ObjectImage; +extern char * _PREHASH_TerrainStartHeight00; +extern char * _PREHASH_TerrainStartHeight01; +extern char * _PREHASH_TerrainStartHeight10; +extern char * _PREHASH_ObjectHinge; +extern char * _PREHASH_TerrainStartHeight11; +extern char * _PREHASH_MetersPerGrid; +extern char * _PREHASH_WaterHeight; +extern char * _PREHASH_FetchInventoryReply; +extern char * _PREHASH_MoneySummaryReply; +extern char * _PREHASH_GroupAccountSummaryReply; +extern char * _PREHASH_AttachedSound; +extern char * _PREHASH_ParamInUse; +extern char * _PREHASH_GodKickUser; +extern char * _PREHASH_PickName; +extern char * _PREHASH_TaskName; +extern char * _PREHASH_ParcelGodReserveForNewbie; +extern char * _PREHASH_SubType; +extern char * _PREHASH_ObjectCount; +extern char * _PREHASH_RegionPresenceRequestByHandle; +extern char * _PREHASH_RezSingleAttachmentFromInv; +extern char * _PREHASH_ChildAgentUpdate; +extern char * _PREHASH_ToID; +extern char * _PREHASH_ViewerPort; +extern char * _PREHASH_IsOwnerGroup; +extern char * _PREHASH_AgentHeightWidth; +extern char * _PREHASH_VerticalAngle; +extern char * _PREHASH_WearableType; +extern char * _PREHASH_AggregatePermNextOwner; +extern char * _PREHASH_ShowInList; +extern char * _PREHASH_PositionSuggestion; +extern char * _PREHASH_UpdateParcel; +extern char * _PREHASH_ClearAgentSessions; +extern char * _PREHASH_SetAlwaysRun; +extern char * _PREHASH_NVPair; +extern char * _PREHASH_ObjectSpinStart; +extern char * _PREHASH_UseEstateSun; +extern char * _PREHASH_LogoutBlock; +extern char * _PREHASH_RelayLogControl; +extern char * _PREHASH_RegionID; +extern char * _PREHASH_Creator; +extern char * _PREHASH_ProposalText; +extern char * _PREHASH_DirEventsReply; +extern char * _PREHASH_EventInfoReply; +extern char * _PREHASH_UserInfoReply; +extern char * _PREHASH_PathRadiusOffset; +extern char * _PREHASH_SessionInfo; +extern char * _PREHASH_TextureData; +extern char * _PREHASH_ChatPass; +extern char * _PREHASH_TargetID; +extern char * _PREHASH_DefaultPayPrice; +extern char * _PREHASH_UserLocation; +extern char * _PREHASH_MaxPrims; +extern char * _PREHASH_RegionIP; +extern char * _PREHASH_LandmarkID; +extern char * _PREHASH_InitiateDownload; +extern char * _PREHASH_Name; +extern char * _PREHASH_OtherCleanTime; +extern char * _PREHASH_ParcelSetOtherCleanTime; +extern char * _PREHASH_TeleportPriceExponent; +extern char * _PREHASH_Gain; +extern char * _PREHASH_VelX; +extern char * _PREHASH_PacketAck; +extern char * _PREHASH_PathSkew; +extern char * _PREHASH_Negative; +extern char * _PREHASH_VelY; +extern char * _PREHASH_SimulatorShutdownRequest; +extern char * _PREHASH_NearestLandingRegionRequest; +extern char * _PREHASH_VelZ; +extern char * _PREHASH_OtherID; +extern char * _PREHASH_MemberID; +extern char * _PREHASH_MapLayerRequest; +extern char * _PREHASH_PatchVersion; +extern char * _PREHASH_ObjectScale; +extern char * _PREHASH_TargetIP; +extern char * _PREHASH_Redo; +extern char * _PREHASH_MoneyBalance; +extern char * _PREHASH_TrackAgent; +extern char * _PREHASH_MaxX; +extern char * _PREHASH_Data; +extern char * _PREHASH_MaxY; +extern char * _PREHASH_TextureAnim; +extern char * _PREHASH_ReturnIDs; +extern char * _PREHASH_Date; +extern char * _PREHASH_GestureUpdate; +extern char * _PREHASH_AgentWearablesUpdate; +extern char * _PREHASH_AgentDataUpdate; +extern char * _PREHASH_Hash; +extern char * _PREHASH_GroupDataUpdate; +extern char * _PREHASH_AgentGroupDataUpdate; +extern char * _PREHASH_Left; +extern char * _PREHASH_Mask; +extern char * _PREHASH_ForceMouselook; +extern char * _PREHASH_Success; +extern char * _PREHASH_ObjectGroup; +extern char * _PREHASH_SunHour; +extern char * _PREHASH_MinX; +extern char * _PREHASH_ScriptSensorReply; +extern char * _PREHASH_MinY; +extern char * _PREHASH_Command; +extern char * _PREHASH_Desc; +extern char * _PREHASH_AttachmentNeedsSave; +extern char * _PREHASH_HistoryItemData; +extern char * _PREHASH_AgentCachedTexture; +extern char * _PREHASH_Subject; +extern char * _PREHASH_East; +extern char * _PREHASH_GodExpungeUser; +extern char * _PREHASH_QueryReplies; +extern char * _PREHASH_ObjectCategory; +extern char * _PREHASH_Time; +extern char * _PREHASH_CreateLandmarkForEvent; +extern char * _PREHASH_ParentID; +extern char * _PREHASH_Ping; +extern char * _PREHASH_Perp; +extern char * _PREHASH_Code; +extern char * _PREHASH_InvType; +extern char * _PREHASH_AgentFOV; +extern char * _PREHASH_BulkMoneyTransfer; +extern char * _PREHASH_Audible; +extern char * _PREHASH_AuctionData; +extern char * _PREHASH_IDBlock; +extern char * _PREHASH_ReputationData; +extern char * _PREHASH_West; +extern char * _PREHASH_Undo; +extern char * _PREHASH_TotalNumItems; +extern char * _PREHASH_Info; +extern char * _PREHASH_Area; +extern char * _PREHASH_Behavior; +extern char * _PREHASH_SimCrashed; +extern char * _PREHASH_Text; +extern char * _PREHASH_AgentToNewRegion; +extern char * _PREHASH_PriceGroupCreate; +extern char * _PREHASH_ObjectShape; +extern char * _PREHASH_GroupRoleDataReply; +extern char * _PREHASH_PosX; +extern char * _PREHASH_PosY; +extern char * _PREHASH_MuteCRC; +extern char * _PREHASH_PosZ; +extern char * _PREHASH_Size; +extern char * _PREHASH_FromAddress; +extern char * _PREHASH_Body; +extern char * _PREHASH_FileData; +extern char * _PREHASH_List; +extern char * _PREHASH_KickUser; +extern char * _PREHASH_OtherPrims; +extern char * _PREHASH_RunTime; +extern char * _PREHASH_GrantUserRights; +extern char * _PREHASH_RpcScriptRequestInboundForward; +extern char * _PREHASH_More; +extern char * _PREHASH_Majority; +extern char * _PREHASH_MetersTraveled; +extern char * _PREHASH_Stat; +extern char * _PREHASH_SoundID; +extern char * _PREHASH_Item; +extern char * _PREHASH_User; +extern char * _PREHASH_RemoteInfos; +extern char * _PREHASH_Prey; +extern char * _PREHASH_UsecSinceStart; +extern char * _PREHASH_RayStart; +extern char * _PREHASH_ParcelData; +extern char * _PREHASH_CameraUpAxis; +extern char * _PREHASH_ScriptDialog; +extern char * _PREHASH_MasterParcelData; +extern char * _PREHASH_Invalid; +extern char * _PREHASH_MinPlace; +extern char * _PREHASH_ProfileCurve; +extern char * _PREHASH_ParcelAccessListUpdate; +extern char * _PREHASH_MuteListUpdate; +extern char * _PREHASH_SendPacket; +extern char * _PREHASH_SendXferPacket; +extern char * _PREHASH_RegionDenyIdentified; +extern char * _PREHASH_NotecardItemID; +extern char * _PREHASH_LastName; +extern char * _PREHASH_From; +extern char * _PREHASH_RoleChange; +extern char * _PREHASH_Port; +extern char * _PREHASH_MemberTitle; +extern char * _PREHASH_LogParcelChanges; +extern char * _PREHASH_AgentCachedTextureResponse; +extern char * _PREHASH_DeRezObject; +extern char * _PREHASH_IsTemporary; +extern char * _PREHASH_InsigniaID; +extern char * _PREHASH_CheckFlags; +extern char * _PREHASH_TransferPriority; +extern char * _PREHASH_EventID; +extern char * _PREHASH_Selected; +extern char * _PREHASH_FromAgentId; +extern char * _PREHASH_Type; +extern char * _PREHASH_ChatType; +extern char * _PREHASH_ReportData; +extern char * _PREHASH_LeaderBoardData; +extern char * _PREHASH_RequestBlock; +extern char * _PREHASH_GrantData; +extern char * _PREHASH_DetachAttachmentIntoInv; +extern char * _PREHASH_ParcelDisableObjects; +extern char * _PREHASH_Sections; +extern char * _PREHASH_GodLevel; +extern char * _PREHASH_PayPriceReply; +extern char * _PREHASH_QueryID; +extern char * _PREHASH_CameraEyeOffset; +extern char * _PREHASH_AgentPosition; +extern char * _PREHASH_GrabPosition; +extern char * _PREHASH_OnlineNotification; +extern char * _PREHASH_OfflineNotification; +extern char * _PREHASH_SendPostcard; +extern char * _PREHASH_RequestFlags; +extern char * _PREHASH_MoneyHistoryRequest; +extern char * _PREHASH_MoneySummaryRequest; +extern char * _PREHASH_GroupAccountSummaryRequest; +extern char * _PREHASH_GroupVoteHistoryRequest; +extern char * _PREHASH_ParamValue; +extern char * _PREHASH_Checksum; +extern char * _PREHASH_MaxAgents; +extern char * _PREHASH_CreateNewOutfitAttachments; +extern char * _PREHASH_RegionHandle; +extern char * _PREHASH_TeleportProgress; +extern char * _PREHASH_AgentQuitCopy; +extern char * _PREHASH_ToViewer; +extern char * _PREHASH_AvatarInterestsUpdate; +extern char * _PREHASH_GroupNoticeID; +extern char * _PREHASH_ParcelName; +extern char * _PREHASH_PriceObjectRent; +extern char * _PREHASH_ConnectAgentToUserserver; +extern char * _PREHASH_ConnectToUserserver; +extern char * _PREHASH_OfferCallingCard; +extern char * _PREHASH_AgentAccess; +extern char * _PREHASH_AcceptCallingCard; +extern char * _PREHASH_DeclineCallingCard; +extern char * _PREHASH_DataHomeLocationReply; +extern char * _PREHASH_EventLocationReply; +extern char * _PREHASH_TerseDateID; +extern char * _PREHASH_ObjectOwner; +extern char * _PREHASH_AssetID; +extern char * _PREHASH_AlertMessage; +extern char * _PREHASH_AgentAlertMessage; +extern char * _PREHASH_EstateOwnerMessage; +extern char * _PREHASH_ParcelMediaCommandMessage; +extern char * _PREHASH_Auction; +extern char * _PREHASH_Category; +extern char * _PREHASH_FilePath; +extern char * _PREHASH_ItemFlags; +extern char * _PREHASH_Invoice; +extern char * _PREHASH_IntervalDays; +extern char * _PREHASH_PathScaleX; +extern char * _PREHASH_FromTaskID; +extern char * _PREHASH_TimeInfo; +extern char * _PREHASH_PathScaleY; +extern char * _PREHASH_PublicCount; +extern char * _PREHASH_ParcelJoin; +extern char * _PREHASH_GroupRolesCount; +extern char * _PREHASH_SimulatorBlock; +extern char * _PREHASH_GroupID; +extern char * _PREHASH_AgentVel; +extern char * _PREHASH_RequestImage; +extern char * _PREHASH_NetStats; +extern char * _PREHASH_AgentPos; +extern char * _PREHASH_AgentSit; +extern char * _PREHASH_Material; +extern char * _PREHASH_ObjectDeGrab; +extern char * _PREHASH_VelocityInterpolateOff; +extern char * _PREHASH_AuthorizedBuyerID; +extern char * _PREHASH_AvatarPropertiesReply; +extern char * _PREHASH_GroupProfileReply; +extern char * _PREHASH_SimOwner; +extern char * _PREHASH_SalePrice; +extern char * _PREHASH_Animation; +extern char * _PREHASH_OwnerID; +extern char * _PREHASH_NearestLandingRegionUpdated; +extern char * _PREHASH_PassToAgent; +extern char * _PREHASH_PreyAgent; +extern char * _PREHASH_SimStats; +extern char * _PREHASH_Options; +extern char * _PREHASH_LogoutReply; +extern char * _PREHASH_FeatureDisabled; +extern char * _PREHASH_ObjectLocalID; +extern char * _PREHASH_Dropped; +extern char * _PREHASH_WebProfilesDisabled; +extern char * _PREHASH_Destination; +extern char * _PREHASH_MasterID; +extern char * _PREHASH_TransferData; +extern char * _PREHASH_WantToMask; +extern char * _PREHASH_AvatarData; +extern char * _PREHASH_ParcelSelectObjects; +extern char * _PREHASH_ExtraParams; +extern char * _PREHASH_LogLogin; +extern char * _PREHASH_CreatorID; +extern char * _PREHASH_Summary; +extern char * _PREHASH_BuyObjectInventory; +extern char * _PREHASH_FetchInventory; +extern char * _PREHASH_InventoryID; +extern char * _PREHASH_PacketNumber; +extern char * _PREHASH_SetFollowCamProperties; +extern char * _PREHASH_ClearFollowCamProperties; +extern char * _PREHASH_SequenceID; +extern char * _PREHASH_DataServerLogout; +extern char * _PREHASH_NameValue; +extern char * _PREHASH_PathShearX; +extern char * _PREHASH_PathShearY; +extern char * _PREHASH_Velocity; +extern char * _PREHASH_SecPerYear; +extern char * _PREHASH_FirstName; +extern char * _PREHASH_AttachedSoundGainChange; +extern char * _PREHASH_LocationID; +extern char * _PREHASH_Running; +extern char * _PREHASH_AgentThrottle; +extern char * _PREHASH_NeighborList; +extern char * _PREHASH_PathTaperX; +extern char * _PREHASH_PathTaperY; +extern char * _PREHASH_AgentRelated; +extern char * _PREHASH_GranterBlock; +extern char * _PREHASH_UseCachedMuteList; +extern char * _PREHASH_FailStats; +extern char * _PREHASH_Tempfile; +extern char * _PREHASH_BuyerID; +extern char * _PREHASH_DirPeopleReply; +extern char * _PREHASH_TransferInfo; +extern char * _PREHASH_AvatarPickerRequestBackend; +extern char * _PREHASH_AvatarPropertiesRequestBackend; +extern char * _PREHASH_UpdateData; +extern char * _PREHASH_SimFPS; +extern char * _PREHASH_ReporterID; +extern char * _PREHASH_ButtonLabel; +extern char * _PREHASH_GranterID; +extern char * _PREHASH_WantToText; +extern char * _PREHASH_ReportType; +extern char * _PREHASH_DataBlock; +extern char * _PREHASH_SimulatorReady; +extern char * _PREHASH_AnimationSourceList; +extern char * _PREHASH_SubscribeLoad; +extern char * _PREHASH_UnsubscribeLoad; +extern char * _PREHASH_Packet; +extern char * _PREHASH_UndoLand; +extern char * _PREHASH_SimAccess; +extern char * _PREHASH_MembershipFee; +extern char * _PREHASH_InviteGroupResponse; +extern char * _PREHASH_CreateInventoryFolder; +extern char * _PREHASH_UpdateInventoryFolder; +extern char * _PREHASH_MoveInventoryFolder; +extern char * _PREHASH_RemoveInventoryFolder; +extern char * _PREHASH_MoneyData; +extern char * _PREHASH_ObjectDeselect; +extern char * _PREHASH_NewAssetID; +extern char * _PREHASH_ObjectAdd; +extern char * _PREHASH_RayEndIsIntersection; +extern char * _PREHASH_CompleteAuction; +extern char * _PREHASH_CircuitCode; +extern char * _PREHASH_AgentMovementComplete; +extern char * _PREHASH_ViewerIP; +extern char * _PREHASH_Header; +extern char * _PREHASH_GestureFlags; +extern char * _PREHASH_XferID; +extern char * _PREHASH_StatValue; +extern char * _PREHASH_PickID; +extern char * _PREHASH_TaskID; +extern char * _PREHASH_GridsPerEdge; +extern char * _PREHASH_RayEnd; +extern char * _PREHASH_Throttles; +extern char * _PREHASH_RebakeAvatarTextures; +extern char * _PREHASH_UpAxis; +extern char * _PREHASH_AgentTextures; +extern char * _PREHASH_NotecardData; +extern char * _PREHASH_Radius; +extern char * _PREHASH_OffCircuit; +extern char * _PREHASH_Access; +extern char * _PREHASH_TitleRoleID; +extern char * _PREHASH_SquareMetersCredit; +extern char * _PREHASH_Filename; +extern char * _PREHASH_SecuredTemplateChecksumRequest; +extern char * _PREHASH_TemplateChecksumRequest; +extern char * _PREHASH_AgentPresenceRequest; +extern char * _PREHASH_ClassifiedInfoRequest; +extern char * _PREHASH_ParcelInfoRequest; +extern char * _PREHASH_ParcelObjectOwnersRequest; +extern char * _PREHASH_TeleportLandmarkRequest; +extern char * _PREHASH_EventInfoRequest; +extern char * _PREHASH_ChatFromSimulator; +extern char * _PREHASH_PickInfoRequest; +extern char * _PREHASH_MoneyBalanceRequest; +extern char * _PREHASH_GroupMembersRequest; +extern char * _PREHASH_GroupRoleMembersRequest; +extern char * _PREHASH_OldFolderID; +extern char * _PREHASH_UserInfoRequest; +extern char * _PREHASH_TextureID; +extern char * _PREHASH_ProfileURL; +extern char * _PREHASH_Handle; +extern char * _PREHASH_StartParcelRenameAck; +extern char * _PREHASH_ButtonIndex; +extern char * _PREHASH_GetScriptRunning; +extern char * _PREHASH_SetScriptRunning; +extern char * _PREHASH_Health; +extern char * _PREHASH_FileID; +extern char * _PREHASH_CircuitInfo; +extern char * _PREHASH_ObjectBuy; +extern char * _PREHASH_ProfileEnd; +extern char * _PREHASH_Effect; +extern char * _PREHASH_TestMessage; +extern char * _PREHASH_ScriptMailRegistration; +extern char * _PREHASH_AgentSetAppearance; +extern char * _PREHASH_AvatarAppearance; +extern char * _PREHASH_RegionData; +extern char * _PREHASH_RequestingRegionData; +extern char * _PREHASH_LandingRegionData; +extern char * _PREHASH_SitTransform; +extern char * _PREHASH_TerrainBase0; +extern char * _PREHASH_SkillsMask; +extern char * _PREHASH_AtAxis; +extern char * _PREHASH_TerrainBase1; +extern char * _PREHASH_Reason; +extern char * _PREHASH_TerrainBase2; +extern char * _PREHASH_TerrainBase3; +extern char * _PREHASH_Params; +extern char * _PREHASH_PingID; +extern char * _PREHASH_Change; +extern char * _PREHASH_Height; +extern char * _PREHASH_Region; +extern char * _PREHASH_MoneyHistoryReply; +extern char * _PREHASH_TelehubInfo; +extern char * _PREHASH_StateSave; +extern char * _PREHASH_RoleData; +extern char * _PREHASH_AgentAnimation; +extern char * _PREHASH_AvatarAnimation; +extern char * _PREHASH_LogDwellTime; +extern char * _PREHASH_ParcelGodMarkAsContent; +extern char * _PREHASH_UsePhysics; +extern char * _PREHASH_RegionDenyTransacted; +extern char * _PREHASH_JointType; +extern char * _PREHASH_TaxEstimate; +extern char * _PREHASH_ObjectTaxEstimate; +extern char * _PREHASH_LightTaxEstimate; +extern char * _PREHASH_TeleportLandingStatusChanged; +extern char * _PREHASH_LandTaxEstimate; +extern char * _PREHASH_GroupTaxEstimate; +extern char * _PREHASH_AvgViewerFPS; +extern char * _PREHASH_Buttons; +extern char * _PREHASH_Sender; +extern char * _PREHASH_Dialog; +extern char * _PREHASH_TargetData; +extern char * _PREHASH_DestID; +extern char * _PREHASH_PricePublicObjectDelete; +extern char * _PREHASH_ObjectDelete; +extern char * _PREHASH_Delete; +extern char * _PREHASH_EventGodDelete; +extern char * _PREHASH_LastTaxDate; +extern char * _PREHASH_MapImageID; +extern char * _PREHASH_EndDateTime; +extern char * _PREHASH_TerrainDetail0; +extern char * _PREHASH_TerrainDetail1; +extern char * _PREHASH_TerrainDetail2; +extern char * _PREHASH_TerrainDetail3; +extern char * _PREHASH_Offset; +extern char * _PREHASH_ObjectDelink; +extern char * _PREHASH_TargetObject; +extern char * _PREHASH_IsEstateManager; +extern char * _PREHASH_CancelAuction; +extern char * _PREHASH_ObjectDetach; +extern char * _PREHASH_Compressed; +extern char * _PREHASH_PathBegin; +extern char * _PREHASH_BypassRaycast; +extern char * _PREHASH_WinnerID; +extern char * _PREHASH_ChannelType; +extern char * _PREHASH_NonExemptMembers; +extern char * _PREHASH_Agents; +extern char * _PREHASH_SimulatorStart; +extern char * _PREHASH_Enable; +extern char * _PREHASH_MemberData; +extern char * _PREHASH_ToGroupID; +extern char * _PREHASH_ImageNotInDatabase; +extern char * _PREHASH_StartDate; +extern char * _PREHASH_AnimID; +extern char * _PREHASH_Serial; +extern char * _PREHASH_ControlPort; +extern char * _PREHASH_ModifyLand; +extern char * _PREHASH_Digest; +extern char * _PREHASH_Victim; +extern char * _PREHASH_Script; +extern char * _PREHASH_TemplateChecksumReply; +extern char * _PREHASH_PickInfoReply; +extern char * _PREHASH_MoneyBalanceReply; +extern char * _PREHASH_RoutedMoneyBalanceReply; +extern char * _PREHASH_RoleID; +extern char * _PREHASH_RegionInfo; +extern char * _PREHASH_Sequence; +extern char * _PREHASH_GodUpdateRegionInfo; +extern char * _PREHASH_LocalX; +extern char * _PREHASH_LocalY; +extern char * _PREHASH_StartAnim; +extern char * _PREHASH_Location; +extern char * _PREHASH_Action; +extern char * _PREHASH_Rights; +extern char * _PREHASH_SearchDir; +extern char * _PREHASH_Active; +extern char * _PREHASH_TransferRequest; +extern char * _PREHASH_ScriptSensorRequest; +extern char * _PREHASH_MoneyTransferRequest; +extern char * _PREHASH_EjectGroupMemberRequest; +extern char * _PREHASH_SkillsText; +extern char * _PREHASH_Resent; +extern char * _PREHASH_Center; +extern char * _PREHASH_SharedData; +extern char * _PREHASH_PSBlock; +extern char * _PREHASH_UUIDNameBlock; +extern char * _PREHASH_Viewer; +extern char * _PREHASH_GroupNoticeDelete; +extern char * _PREHASH_GroupTitleUpdate; +extern char * _PREHASH_Method; +extern char * _PREHASH_TouchName; +extern char * _PREHASH_UpdateType; +extern char * _PREHASH_KickedFromEstateID; +extern char * _PREHASH_CandidateID; +extern char * _PREHASH_ParamData; +extern char * _PREHASH_GodlikeMessage; +extern char * _PREHASH_SystemMessage; +extern char * _PREHASH_BodyRotation; +extern char * _PREHASH_SearchRegions; +extern char * _PREHASH_Ignore; +extern char * _PREHASH_AnimationData; +extern char * _PREHASH_StatID; +extern char * _PREHASH_ItemID; +extern char * _PREHASH_AvatarStatisticsReply; +extern char * _PREHASH_ScriptDialogReply; +extern char * _PREHASH_RegionIDAndHandleReply; +extern char * _PREHASH_CameraAtOffset; +extern char * _PREHASH_VoteID; +extern char * _PREHASH_ParcelGodForceOwner; +extern char * _PREHASH_Filter; +extern char * _PREHASH_InviteData; +extern char * _PREHASH_PCode; +extern char * _PREHASH_SearchPos; +extern char * _PREHASH_PreyID; +extern char * _PREHASH_TerrainLowerLimit; +extern char * _PREHASH_EventFlags; +extern char * _PREHASH_TallyVotes; +extern char * _PREHASH_Result; +extern char * _PREHASH_LookAt; +extern char * _PREHASH_PayButton; +extern char * _PREHASH_SelfCount; +extern char * _PREHASH_PacketCount; +extern char * _PREHASH_ParcelBuyPass; +extern char * _PREHASH_Identified; +extern char * _PREHASH_OldItemID; +extern char * _PREHASH_RegionPort; +extern char * _PREHASH_PriceEnergyUnit; +extern char * _PREHASH_Bitmap; +extern char * _PREHASH_TrackAgentSession; +extern char * _PREHASH_CacheMissType; +extern char * _PREHASH_VFileID; +extern char * _PREHASH_GroupInsigniaID; +extern char * _PREHASH_FromID; +extern char * _PREHASH_Online; +extern char * _PREHASH_KickFlags; +extern char * _PREHASH_CovenantID; +extern char * _PREHASH_SysCPU; +extern char * _PREHASH_EMail; +extern char * _PREHASH_AggregatePermTextures; +extern char * _PREHASH_ChatChannel; +extern char * _PREHASH_ReturnID; +extern char * _PREHASH_ObjectAttach; +extern char * _PREHASH_TargetPort; +extern char * _PREHASH_ObjectSpinStop; +extern char * _PREHASH_FullID; +extern char * _PREHASH_ActivateGroup; +extern char * _PREHASH_SysGPU; +extern char * _PREHASH_AvatarInterestsReply; +extern char * _PREHASH_StartLure; +extern char * _PREHASH_SysRAM; +extern char * _PREHASH_ObjectPosition; +extern char * _PREHASH_SitPosition; +extern char * _PREHASH_StartTime; +extern char * _PREHASH_BornOn; +extern char * _PREHASH_CameraCollidePlane; +extern char * _PREHASH_EconomyDataRequest; +extern char * _PREHASH_TeleportLureRequest; +extern char * _PREHASH_FolderID; +extern char * _PREHASH_RegionHandleRequest; +extern char * _PREHASH_GestureRequest; +extern char * _PREHASH_ScriptDataRequest; +extern char * _PREHASH_GroupRoleDataRequest; +extern char * _PREHASH_GroupTitlesRequest; +extern char * _PREHASH_AgentWearablesRequest; +extern char * _PREHASH_MapBlockRequest; +extern char * _PREHASH_LureID; +extern char * _PREHASH_CopyCenters; +extern char * _PREHASH_ParamList; +extern char * _PREHASH_InventorySerial; +extern char * _PREHASH_EdgeDataPacket; +extern char * _PREHASH_AvatarPickerReply; +extern char * _PREHASH_ParcelDwellReply; +extern char * _PREHASH_IsForSale; +extern char * _PREHASH_MuteID; +extern char * _PREHASH_MeanCollisionAlert; +extern char * _PREHASH_CanAcceptTasks; +extern char * _PREHASH_ItemData; +extern char * _PREHASH_AnimationList; +extern char * _PREHASH_PassObject; +extern char * _PREHASH_Reputation; +extern char * _PREHASH_IntValue; +extern char * _PREHASH_TargetType; +extern char * _PREHASH_Amount; +extern char * _PREHASH_HasAttachment; +extern char * _PREHASH_UpdateAttachment; +extern char * _PREHASH_RemoveAttachment; +extern char * _PREHASH_HeightWidthBlock; +extern char * _PREHASH_RequestObjectPropertiesFamily; +extern char * _PREHASH_ObjectPropertiesFamily; +extern char * _PREHASH_UserData; +extern char * _PREHASH_IsReadable; +extern char * _PREHASH_PathCurve; +extern char * _PREHASH_Status; +extern char * _PREHASH_FromGroup; +extern char * _PREHASH_AlreadyVoted; +extern char * _PREHASH_PlacesReply; +extern char * _PREHASH_DirPlacesReply; +extern char * _PREHASH_ParcelBuy; +extern char * _PREHASH_DirFindQueryBackend; +extern char * _PREHASH_DirPlacesQueryBackend; +extern char * _PREHASH_DirClassifiedQueryBackend; +extern char * _PREHASH_DirPicksQueryBackend; +extern char * _PREHASH_DirLandQueryBackend; +extern char * _PREHASH_DirPopularQueryBackend; +extern char * _PREHASH_LogoutDemand; +extern char * _PREHASH_HistoryData; +extern char * _PREHASH_SnapshotID; +extern char * _PREHASH_Aspect; +extern char * _PREHASH_ParamSize; +extern char * _PREHASH_VoteCast; +extern char * _PREHASH_CastsShadows; +extern char * _PREHASH_EveryoneMask; +extern char * _PREHASH_SetSunPhase; +extern char * _PREHASH_ObjectSpinUpdate; +extern char * _PREHASH_MaturePublish; +extern char * _PREHASH_UseExistingAsset; +extern char * _PREHASH_Powers; +extern char * _PREHASH_ParcelLocalID; +extern char * _PREHASH_TeleportCancel; +extern char * _PREHASH_UnixTime; +extern char * _PREHASH_QueryFlags; +extern char * _PREHASH_LastExecFroze; +extern char * _PREHASH_AlwaysRun; +extern char * _PREHASH_Bottom; +extern char * _PREHASH_ButtonData; +extern char * _PREHASH_SoundData; +extern char * _PREHASH_ViewerStats; +extern char * _PREHASH_RegionHandshake; +extern char * _PREHASH_ObjectDescription; +extern char * _PREHASH_Description; +extern char * _PREHASH_ParamType; +extern char * _PREHASH_UUIDNameReply; +extern char * _PREHASH_UUIDGroupNameReply; +extern char * _PREHASH_SaveAssetIntoInventory; +extern char * _PREHASH_UserInfo; +extern char * _PREHASH_AnimSequenceID; +extern char * _PREHASH_NVPairs; +extern char * _PREHASH_GroupNoticesListRequest; +extern char * _PREHASH_ParcelAccessListRequest; +extern char * _PREHASH_MuteListRequest; +extern char * _PREHASH_StartPeriod; +extern char * _PREHASH_RpcChannelRequest; +extern char * _PREHASH_LandStatRequest; +extern char * _PREHASH_PlacesQuery; +extern char * _PREHASH_DirPlacesQuery; +extern char * _PREHASH_SortOrder; +extern char * _PREHASH_Hunter; +extern char * _PREHASH_SunAngVelocity; +extern char * _PREHASH_BinaryBucket; +extern char * _PREHASH_ImagePacket; +extern char * _PREHASH_StartGroupProposal; +extern char * _PREHASH_EnergyLevel; +extern char * _PREHASH_PriceForListing; +extern char * _PREHASH_Scale; +extern char * _PREHASH_EstateCovenantReply; +extern char * _PREHASH_ParentEstateID; +extern char * _PREHASH_Extra2; +extern char * _PREHASH_Throttle; +extern char * _PREHASH_SimIP; +extern char * _PREHASH_GodID; +extern char * _PREHASH_TeleportMinPrice; +extern char * _PREHASH_VoteItem; +extern char * _PREHASH_ObjectRotation; +extern char * _PREHASH_SitRotation; +extern char * _PREHASH_SnapSelection; +extern char * _PREHASH_SoundTrigger; +extern char * _PREHASH_TerrainRaiseLimit; +extern char * _PREHASH_Quorum; +extern char * _PREHASH_TokenBlock; +extern char * _PREHASH_AgentBlock; +extern char * _PREHASH_CommandBlock; +extern char * _PREHASH_PricePublicObjectDecay; +extern char * _PREHASH_SpawnPointPos; +extern char * _PREHASH_AttachedSoundCutoffRadius; +extern char * _PREHASH_VolumeDetail; +extern char * _PREHASH_FromAgentName; +extern char * _PREHASH_Range; +extern char * _PREHASH_DirectoryVisibility; +extern char * _PREHASH_PublicIP; +extern char * _PREHASH_TeleportFailed; +extern char * _PREHASH_OnlineStatusReply; +extern char * _PREHASH_RequestAvatarInfo; +extern char * _PREHASH_PreloadSound; +extern char * _PREHASH_ScreenshotID; +extern char * _PREHASH_CovenantTimestamp; +extern char * _PREHASH_OldestUnacked; +extern char * _PREHASH_SimulatorIP; +extern char * _PREHASH_ObjectImport; +extern char * _PREHASH_Value; +extern char * _PREHASH_JointAxisOrAnchor; +extern char * _PREHASH_Test0; +extern char * _PREHASH_Test1; +extern char * _PREHASH_Test2; +extern char * _PREHASH_SunPhase; +extern char * _PREHASH_Place; +extern char * _PREHASH_Phase; +extern char * _PREHASH_ParcelDivide; +extern char * _PREHASH_PriceObjectClaim; +extern char * _PREHASH_Field; +extern char * _PREHASH_Ratio; +extern char * _PREHASH_JoinGroupReply; +extern char * _PREHASH_LiveHelpGroupReply; +extern char * _PREHASH_Score; +extern char * _PREHASH_ExpungeData; +extern char * _PREHASH_Image; +extern char * _PREHASH_ObjectClickAction; +extern char * _PREHASH_Delta; +extern char * _PREHASH_InitiateUpload; +extern char * _PREHASH_Parameter; +extern char * _PREHASH_Flags; +extern char * _PREHASH_Plane; +extern char * _PREHASH_Width; +extern char * _PREHASH_Right; +extern char * _PREHASH_DirFindQuery; +extern char * _PREHASH_Textures; +extern char * _PREHASH_EventData; +extern char * _PREHASH_Final; +extern char * _PREHASH_TelehubPos; +extern char * _PREHASH_ReportAutosaveCrash; +extern char * _PREHASH_Reset; +extern char * _PREHASH_CreateTrustedCircuit; +extern char * _PREHASH_DenyTrustedCircuit; +extern char * _PREHASH_RequestTrustedCircuit; +extern char * _PREHASH_Codec; +extern char * _PREHASH_Level; +extern char * _PREHASH_Modal; +extern char * _PREHASH_ChildAgentUnknown; +extern char * _PREHASH_LandingType; +extern char * _PREHASH_ScriptRunningReply; +extern char * _PREHASH_MoneyDetailsReply; +extern char * _PREHASH_Reply; +extern char * _PREHASH_TelehubRot; +extern char * _PREHASH_RequestFriendship; +extern char * _PREHASH_AcceptFriendship; +extern char * _PREHASH_GroupAccountDetailsReply; +extern char * _PREHASH_DwellInfo; +extern char * _PREHASH_AgentResume; +extern char * _PREHASH_ItemType; +extern char * _PREHASH_MailFilter; +extern char * _PREHASH_Disconnect; +extern char * _PREHASH_SimPosition; +extern char * _PREHASH_SimWideTotalPrims; +extern char * _PREHASH_Index; +extern char * _PREHASH_BaseFilename; +extern char * _PREHASH_SimFilename; +extern char * _PREHASH_LastOwnerID; +extern char * _PREHASH_GroupNoticeRequest; +extern char * _PREHASH_EmailMessageRequest; +extern char * _PREHASH_MapItemRequest; +extern char * _PREHASH_AgentCount; +extern char * _PREHASH_MessageBlock; +extern char * _PREHASH_FuseBlock; +extern char * _PREHASH_AgentGroupData; +extern char * _PREHASH_ClassifiedInfoUpdate; +extern char * _PREHASH_RegionPos; +extern char * _PREHASH_ParcelMediaUpdate; +extern char * _PREHASH_NoticeID; +extern char * _PREHASH_GridX; +extern char * _PREHASH_GridY; +extern char * _PREHASH_Title; +extern char * _PREHASH_AuctionID; +extern char * _PREHASH_VoteType; +extern char * _PREHASH_CategoryID; +extern char * _PREHASH_Token; +extern char * _PREHASH_AggregatePerms; +extern char * _PREHASH_StartParcelRemoveAck; +extern char * _PREHASH_ObjectSelect; +extern char * _PREHASH_ForceObjectSelect; +extern char * _PREHASH_Price; +extern char * _PREHASH_SunDirection; +extern char * _PREHASH_FromName; +extern char * _PREHASH_ChangeInventoryItemFlags; +extern char * _PREHASH_Force; +extern char * _PREHASH_TransactionBlock; +extern char * _PREHASH_PowersMask; +extern char * _PREHASH_Stamp; +extern char * _PREHASH_TotalCredits; +extern char * _PREHASH_State; +extern char * _PREHASH_TextureIndex; +extern char * _PREHASH_InviteeID; +extern char * _PREHASH_ParcelReclaim; +extern char * _PREHASH_Money; +extern char * _PREHASH_PathTwist; +extern char * _PREHASH_AuthBuyerID; +extern char * _PREHASH_Color; +extern char * _PREHASH_SourceType; +extern char * _PREHASH_World; +extern char * _PREHASH_QueryData; +extern char * _PREHASH_Users; +extern char * _PREHASH_SysOS; +extern char * _PREHASH_Notes; +extern char * _PREHASH_AvatarID; +extern char * _PREHASH_FounderID; +extern char * _PREHASH_EndPointID; +extern char * _PREHASH_StipendEstimate; +extern char * _PREHASH_LocationLookAt; +extern char * _PREHASH_Sound; +extern char * _PREHASH_Cover; +extern char * _PREHASH_TotalObjectCount; +extern char * _PREHASH_TextureEntry; +extern char * _PREHASH_SquareMetersCommitted; +extern char * _PREHASH_ChannelID; +extern char * _PREHASH_Dwell; +extern char * _PREHASH_North; +extern char * _PREHASH_AgentUpdate; +extern char * _PREHASH_PickGodDelete; +extern char * _PREHASH_HostName; +extern char * _PREHASH_PriceParcelClaim; +extern char * _PREHASH_ParcelClaim; +extern char * _PREHASH_AgentPowers; +extern char * _PREHASH_ProfileHollow; +extern char * _PREHASH_GroupRoleChanges; +extern char * _PREHASH_Count; +extern char * _PREHASH_South; +extern char * _PREHASH_Entry; +extern char * _PREHASH_ObjectUpdateCompressed; +extern char * _PREHASH_MuteFlags; +extern char * _PREHASH_Group; +extern char * _PREHASH_AgentPause; +extern char * _PREHASH_LanguagesText; +extern char * _PREHASH_InternalScriptMail; +extern char * _PREHASH_FindAgent; +extern char * _PREHASH_AgentData; +extern char * _PREHASH_FolderData; +extern char * _PREHASH_AssetBlock; +extern char * _PREHASH_AcceptNotices; +extern char * _PREHASH_SetGroupAcceptNotices; +extern char * _PREHASH_CloseCircuit; +extern char * _PREHASH_LogControl; +extern char * _PREHASH_TeleportFinish; +extern char * _PREHASH_PathRevolutions; +extern char * _PREHASH_ClassifiedInfoReply; +extern char * _PREHASH_ParcelInfoReply; +extern char * _PREHASH_AutosaveData; +extern char * _PREHASH_SetStartLocation; +extern char * _PREHASH_PassHours; +extern char * _PREHASH_AttachmentPt; +extern char * _PREHASH_ParcelFlags; +extern char * _PREHASH_NumVotes; +extern char * _PREHASH_AvatarPickerRequest; +extern char * _PREHASH_TeleportLocationRequest; +extern char * _PREHASH_DataHomeLocationRequest; +extern char * _PREHASH_EventNotificationAddRequest; +extern char * _PREHASH_ParcelDwellRequest; +extern char * _PREHASH_EventLocationRequest; +extern char * _PREHASH_EndPeriod; +extern char * _PREHASH_SetStartLocationRequest; +extern char * _PREHASH_QueryStart; +extern char * _PREHASH_EjectData; +extern char * _PREHASH_AvatarTextureUpdate; +extern char * _PREHASH_RPCServerPort; +extern char * _PREHASH_Bytes; +extern char * _PREHASH_Extra; +extern char * _PREHASH_ForceScriptControlRelease; +extern char * _PREHASH_ParcelRelease; +extern char * _PREHASH_VFileType; +extern char * _PREHASH_EjectGroupMemberReply; +extern char * _PREHASH_ImageData; +extern char * _PREHASH_SpaceServerSimulatorTimeMessage; +extern char * _PREHASH_SimulatorViewerTimeMessage; +extern char * _PREHASH_Rotation; +extern char * _PREHASH_Selection; +extern char * _PREHASH_TransactionData; +extern char * _PREHASH_OperationData; +extern char * _PREHASH_ExpirationDate; +extern char * _PREHASH_ParcelDeedToGroup; +extern char * _PREHASH_DirPicksReply; +extern char * _PREHASH_AvatarPicksReply; +extern char * _PREHASH_GroupTitlesReply; +extern char * _PREHASH_AgentInfo; +extern char * _PREHASH_MoneyTransferBackend; +extern char * _PREHASH_NextOwnerMask; +extern char * _PREHASH_MuteData; +extern char * _PREHASH_PassPrice; +extern char * _PREHASH_SourceID; +extern char * _PREHASH_ChangeUserRights; +extern char * _PREHASH_TeleportFlags; +extern char * _PREHASH_AssetData; +extern char * _PREHASH_SlaveParcelData; +extern char * _PREHASH_MultipleObjectUpdate; +extern char * _PREHASH_ObjectUpdate; +extern char * _PREHASH_ImprovedTerseObjectUpdate; +extern char * _PREHASH_ConfirmXferPacket; +extern char * _PREHASH_StartPingCheck; +extern char * _PREHASH_SimWideDeletes; +extern char * _PREHASH_LandStatReply; +extern char * _PREHASH_IsPhantom; +extern char * _PREHASH_AgentList; +extern char * _PREHASH_SimApproved; +extern char * _PREHASH_RezObject; +extern char * _PREHASH_TaskLocalID; +extern char * _PREHASH_ClaimDate; +extern char * _PREHASH_MergeParcel; +extern char * _PREHASH_Priority; +extern char * _PREHASH_Building; +extern char * _PREHASH_QueryText; +extern char * _PREHASH_GroupNoticeAdd; +extern char * _PREHASH_ReturnType; +extern char * _PREHASH_FetchFolders; +extern char * _PREHASH_SimulatorPublicHostBlock; +extern char * _PREHASH_HeaderData; +extern char * _PREHASH_RequestMultipleObjects; +extern char * _PREHASH_RetrieveInstantMessages; +extern char * _PREHASH_DequeueInstantMessages; +extern char * _PREHASH_OpenCircuit; +extern char * _PREHASH_SecureSessionID; +extern char * _PREHASH_CrossedRegion; +extern char * _PREHASH_DirGroupsReply; +extern char * _PREHASH_AvatarGroupsReply; +extern char * _PREHASH_EmailMessageReply; +extern char * _PREHASH_GroupVoteHistoryItemReply; +extern char * _PREHASH_ViewerPosition; +extern char * _PREHASH_Position; +extern char * _PREHASH_ParentEstate; +extern char * _PREHASH_EstateName; +extern char * _PREHASH_MuteName; +extern char * _PREHASH_StartParcelRename; +extern char * _PREHASH_BulkParcelRename; +extern char * _PREHASH_ParcelRename; +extern char * _PREHASH_ViewerFilename; +extern char * _PREHASH_Positive; +extern char * _PREHASH_UserReportInternal; +extern char * _PREHASH_AvatarPropertiesRequest; +extern char * _PREHASH_ParcelPropertiesRequest; +extern char * _PREHASH_GroupProfileRequest; +extern char * _PREHASH_AgentDataUpdateRequest; +extern char * _PREHASH_PriceObjectScaleFactor; +extern char * _PREHASH_DirPicksQuery; +extern char * _PREHASH_OpenEnrollment; +extern char * _PREHASH_GroupData; +extern char * _PREHASH_RequestGodlikePowers; +extern char * _PREHASH_GrantGodlikePowers; +extern char * _PREHASH_TransactionID; +extern char * _PREHASH_DestinationID; +extern char * _PREHASH_Controls; +extern char * _PREHASH_FirstDetachAll; +extern char * _PREHASH_EstateID; +extern char * _PREHASH_ImprovedInstantMessage; +extern char * _PREHASH_AgentQuit; +extern char * _PREHASH_CheckParcelSales; +extern char * _PREHASH_ParcelSales; +extern char * _PREHASH_CurrentInterval; +extern char * _PREHASH_PriceRentLight; +extern char * _PREHASH_MediaAutoScale; +extern char * _PREHASH_NeighborBlock; +extern char * _PREHASH_LayerData; +extern char * _PREHASH_NVPairData; +extern char * _PREHASH_TeleportLocal; +extern char * _PREHASH_EjecteeID; +extern char * _PREHASH_VoteInitiator; +extern char * _PREHASH_TypeData; +extern char * _PREHASH_OwnerIDs; +extern char * _PREHASH_SystemKickUser; +extern char * _PREHASH_TransactionTime; +extern char * _PREHASH_TimeToLive; +extern char * _PREHASH_StartParcelRemove; +extern char * _PREHASH_BulkParcelRemove; +extern char * _PREHASH_OldAgentID; +extern char * _PREHASH_BonusEstimate; +extern char * _PREHASH_MusicURL; +extern char * _PREHASH_CompleteLure; +extern char * _PREHASH_ParcelPrimBonus; +extern char * _PREHASH_EjectUser; +extern char * _PREHASH_CoarseLocationUpdate; +extern char * _PREHASH_ChildAgentPositionUpdate; +extern char * _PREHASH_StoreLocal; +extern char * _PREHASH_GroupName; +extern char * _PREHASH_PriceParcelRent; +extern char * _PREHASH_SimStatus; +extern char * _PREHASH_TransactionSuccess; +extern char * _PREHASH_LureType; +extern char * _PREHASH_GroupMask; +extern char * _PREHASH_SitObject; +extern char * _PREHASH_Override; +extern char * _PREHASH_LocomotionState; +extern char * _PREHASH_PriceUpload; +extern char * _PREHASH_RemoveParcel; +extern char * _PREHASH_ConfirmAuctionStart; +extern char * _PREHASH_RpcScriptRequestInbound; +extern char * _PREHASH_ActiveGroupID; +extern char * _PREHASH_ParcelReturnObjects; +extern char * _PREHASH_TotalObjects; +extern char * _PREHASH_ObjectExtraParams; +extern char * _PREHASH_Questions; +extern char * _PREHASH_TransferAbort; +extern char * _PREHASH_TransferInventory; +extern char * _PREHASH_RayTargetID; +extern char * _PREHASH_ClaimPrice; +extern char * _PREHASH_ObjectProperties; +extern char * _PREHASH_ParcelProperties; +extern char * _PREHASH_EstateOwnerID; +extern char * _PREHASH_LogoutRequest; +extern char * _PREHASH_AssetUploadRequest; +extern char * _PREHASH_ReputationIndividualRequest; +extern char * _PREHASH_MajorVersion; +extern char * _PREHASH_MinorVersion; +extern char * _PREHASH_SimulatorAssign; +extern char * _PREHASH_TransactionType; +extern char * _PREHASH_AvatarPropertiesUpdate; +extern char * _PREHASH_ParcelPropertiesUpdate; +extern char * _PREHASH_FetchItems; +extern char * _PREHASH_AbortXfer; +extern char * _PREHASH_DeRezAck; +extern char * _PREHASH_TakeControls; +extern char * _PREHASH_DirLandReply; +extern char * _PREHASH_SpaceLocationTeleportReply; +extern char * _PREHASH_MuteType; +extern char * _PREHASH_IMViaEMail; +extern char * _PREHASH_StartExpungeProcessAck; +extern char * _PREHASH_RentPrice; +extern char * _PREHASH_GenericMessage; +extern char * _PREHASH_ChildAgentAlive; +extern char * _PREHASH_AssetType; +extern char * _PREHASH_SpawnPointBlock; +extern char * _PREHASH_AttachmentBlock; +extern char * _PREHASH_ObjectMaterial; +extern char * _PREHASH_OwnerName; +extern char * _PREHASH_AvatarNotesReply; +extern char * _PREHASH_CacheID; +extern char * _PREHASH_OwnerMask; +extern char * _PREHASH_TransferInventoryAck; + + +void init_prehash_data(); + + + + + +#endif diff --git a/indra/llmessage/message_string_table.cpp b/indra/llmessage/message_string_table.cpp new file mode 100644 index 0000000000..687b47a112 --- /dev/null +++ b/indra/llmessage/message_string_table.cpp @@ -0,0 +1,75 @@ +/** + * @file message_string_table.cpp + * @brief static string table for message template + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llerror.h" +#include "message.h" + +inline U32 message_hash_my_string(const char *str) +{ + U32 retval = 0; + while (*str++) + { + retval += *str; + retval <<= 1; + } + return (retval % MESSAGE_NUMBER_OF_HASH_BUCKETS); +} + + +LLMessageStringTable gMessageStringTable; + + +LLMessageStringTable::LLMessageStringTable() +: mUsed(0) +{ + for (U32 i = 0; i < MESSAGE_NUMBER_OF_HASH_BUCKETS; i++) + { + mEmpty[i] = TRUE; + mString[i][0] = 0; + } +} + + +LLMessageStringTable::~LLMessageStringTable() +{ } + + +char* LLMessageStringTable::getString(const char *str) +{ + U32 hash_value = message_hash_my_string(str); + while (!mEmpty[hash_value]) + { + if (!strncmp(str, mString[hash_value], MESSAGE_MAX_STRINGS_LENGTH)) + { + return mString[hash_value]; + } + else + { + hash_value++; + hash_value %= MESSAGE_NUMBER_OF_HASH_BUCKETS; + } + } + // not found, so add! + strncpy(mString[hash_value], str, MESSAGE_MAX_STRINGS_LENGTH); + mString[hash_value][MESSAGE_MAX_STRINGS_LENGTH - 1] = 0; + mEmpty[hash_value] = FALSE; + mUsed++; + if (mUsed >= MESSAGE_NUMBER_OF_HASH_BUCKETS - 1) + { + U32 i; + llinfos << "Dumping string table before crashing on HashTable full!" << llendl; + for (i = 0; i < MESSAGE_NUMBER_OF_HASH_BUCKETS; i++) + { + llinfos << "Entry #" << i << ": " << mString[i] << llendl; + } + } + return mString[hash_value]; +} + diff --git a/indra/llmessage/net.cpp b/indra/llmessage/net.cpp new file mode 100644 index 0000000000..2b712840d8 --- /dev/null +++ b/indra/llmessage/net.cpp @@ -0,0 +1,516 @@ +/** + * @file net.cpp + * @brief Cross-platform routines for sending and receiving packets. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "net.h" + +// system library includes +#include +#include + +#if !LL_WINDOWS // Windows Versions +#include +#include +#include +#include +#include +#include +#endif + +// linden library includes +#include "network.h" +#include "llerror.h" +#include "llhost.h" +#include "lltimer.h" +#include "indra_constants.h" + + +// Globals +#if LL_WINDOWS + +SOCKADDR_IN stDstAddr; +SOCKADDR_IN stSrcAddr; +SOCKADDR_IN stLclAddr; +static WSADATA stWSAData; + +#else + +struct sockaddr_in stDstAddr; +struct sockaddr_in stSrcAddr; +struct sockaddr_in stLclAddr; + +#if LL_DARWIN +#ifndef _SOCKLEN_T +#define _SOCKLEN_T +typedef int socklen_t; +#endif +#endif + +#endif + + +const char* LOOPBACK_ADDRESS_STRING = "127.0.0.1"; + +#if LL_DARWIN + // Mac OS X returns an error when trying to set these to 400000. Smaller values succeed. + const int SEND_BUFFER_SIZE = 200000; + const int RECEIVE_BUFFER_SIZE = 200000; +#else // LL_DARWIN + const int SEND_BUFFER_SIZE = 400000; + const int RECEIVE_BUFFER_SIZE = 400000; +#endif // LL_DARWIN + +// universal functions (cross-platform) + +LLHost get_sender() +{ + return LLHost(stSrcAddr.sin_addr.s_addr, ntohs(stSrcAddr.sin_port)); +} + +U32 get_sender_ip(void) +{ + return stSrcAddr.sin_addr.s_addr; +} + +U32 get_sender_port() +{ + return ntohs(stSrcAddr.sin_port); +} + +const char* u32_to_ip_string(U32 ip) +{ + static char buffer[MAXADDRSTR]; /* Flawfinder: ignore */ + + // Convert the IP address into a string + in_addr in; + in.s_addr = ip; + char* result = inet_ntoa(in); + + // NULL indicates error in conversion + if (result != NULL) + { + strncpy( buffer, result, MAXADDRSTR ); /* Flawfinder: ignore */ + buffer[MAXADDRSTR-1] = '\0'; + return buffer; + } + else + { + return "(bad IP addr)"; + } +} + + +// Returns ip_string if successful, NULL if not. Copies into ip_string +char *u32_to_ip_string(U32 ip, char *ip_string) +{ + char *result; + in_addr in; + + // Convert the IP address into a string + in.s_addr = ip; + result = inet_ntoa(in); + + // NULL indicates error in conversion + if (result != NULL) + { + //the function signature needs to change to pass in the lengfth of first and last. + strcpy(ip_string, result); + return ip_string; + } + else + { + return NULL; + } +} + + +// Wrapper for inet_addr() +U32 ip_string_to_u32(const char* ip_string) +{ + return inet_addr(ip_string); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// Windows Versions +////////////////////////////////////////////////////////////////////////////////////////// + +#if LL_WINDOWS + +S32 start_net(S32& socket_out, int& nPort) +{ + // Create socket, make non-blocking + // Init WinSock + int nRet; + int hSocket; + + int snd_size = SEND_BUFFER_SIZE; + int rec_size = RECEIVE_BUFFER_SIZE; + int buff_size = 4; + + // Initialize windows specific stuff + if(WSAStartup(0x0202, &stWSAData)) + { + S32 err = WSAGetLastError(); + WSACleanup(); + llwarns << "Windows Sockets initialization failed, err " << err << llendl; + return 1; + } + + // Get a datagram socket + hSocket = (int)socket(AF_INET, SOCK_DGRAM, 0); + if (hSocket == INVALID_SOCKET) + { + S32 err = WSAGetLastError(); + WSACleanup(); + llwarns << "socket() failed, err " << err << llendl; + return 2; + } + + // Name the socket (assign the local port number to receive on) + stLclAddr.sin_family = AF_INET; + stLclAddr.sin_addr.s_addr = htonl(INADDR_ANY); + stLclAddr.sin_port = htons(nPort); + + S32 attempt_port = nPort; + llinfos << "attempting to connect on port " << attempt_port << llendl; + nRet = bind(hSocket, (struct sockaddr*) &stLclAddr, sizeof(stLclAddr)); + + if (nRet == SOCKET_ERROR) + { + // If we got an address in use error... + if (WSAGetLastError() == WSAEADDRINUSE) + { + // Try all ports from PORT_DISCOVERY_RANGE_MIN to PORT_DISCOVERY_RANGE_MAX + for(attempt_port = PORT_DISCOVERY_RANGE_MIN; + attempt_port <= PORT_DISCOVERY_RANGE_MAX; + attempt_port++) + { + stLclAddr.sin_port = htons(attempt_port); + llinfos << "trying port " << attempt_port << llendl; + nRet = bind(hSocket, (struct sockaddr*) &stLclAddr, sizeof(stLclAddr)); + + if (!(nRet == SOCKET_ERROR && + WSAGetLastError() == WSAEADDRINUSE)) + { + break; + } + } + + if (nRet == SOCKET_ERROR) + { + llwarns << "startNet() : Couldn't find available network port." << llendl; + // Fail gracefully here in release + return 3; + } + } + else + // Some other socket error + { + llwarns << llformat("bind() port: %d failed, Err: %d\n", nPort, WSAGetLastError()) << llendl; + // Fail gracefully in release. + return 4; + } + } + llinfos << "connected on port " << attempt_port << llendl; + nPort = attempt_port; + + // Set socket to be non-blocking + unsigned long argp = 1; + nRet = ioctlsocket (hSocket, FIONBIO, &argp); + if (nRet == SOCKET_ERROR) + { + printf("Failed to set socket non-blocking, Err: %d\n", + WSAGetLastError()); + } + + // set a large receive buffer + nRet = setsockopt(hSocket, SOL_SOCKET, SO_RCVBUF, (char *)&rec_size, buff_size); + if (nRet) + { + llinfos << "Can't set receive buffer size!" << llendl; + } + + nRet = setsockopt(hSocket, SOL_SOCKET, SO_SNDBUF, (char *)&snd_size, buff_size); + if (nRet) + { + llinfos << "Can't set send buffer size!" << llendl; + } + + getsockopt(hSocket, SOL_SOCKET, SO_RCVBUF, (char *)&rec_size, &buff_size); + getsockopt(hSocket, SOL_SOCKET, SO_SNDBUF, (char *)&snd_size, &buff_size); + + llinfos << "startNet - receive buffer size : " << rec_size << llendl; + llinfos << "startNet - send buffer size : " << snd_size << llendl; + + // Setup a destination address + char achMCAddr[MAXADDRSTR] = " "; /* Flawfinder: ignore */ + stDstAddr.sin_family = AF_INET; + stDstAddr.sin_addr.s_addr = inet_addr(achMCAddr); + stDstAddr.sin_port = htons(nPort); + + socket_out = hSocket; + return 0; +} + +void end_net() +{ + WSACleanup(); +} + +S32 receive_packet(int hSocket, char * receiveBuffer) +{ + // Receives data asynchronously from the socket set by initNet(). + // Returns the number of bytes received into dataReceived, or zero + // if there is no data received. + int nRet; + int addr_size = sizeof(struct sockaddr_in); + + nRet = recvfrom(hSocket, receiveBuffer, NET_BUFFER_SIZE, 0, (struct sockaddr*)&stSrcAddr, &addr_size); + if (nRet == SOCKET_ERROR ) + { + if (WSAEWOULDBLOCK == WSAGetLastError()) + return 0; + if (WSAECONNRESET == WSAGetLastError()) + return 0; + llinfos << "receivePacket() failed, Error: " << WSAGetLastError() << llendl; + } + + return nRet; +} + +// Returns TRUE on success. +BOOL send_packet(int hSocket, const char *sendBuffer, int size, U32 recipient, int nPort) +{ + // Sends a packet to the address set in initNet + // + int nRet = 0; + U32 last_error = 0; + + stDstAddr.sin_addr.s_addr = recipient; + stDstAddr.sin_port = htons(nPort); + do + { + nRet = sendto(hSocket, sendBuffer, size, 0, (struct sockaddr*)&stDstAddr, sizeof(stDstAddr)); + + if (nRet == SOCKET_ERROR ) + { + last_error = WSAGetLastError(); + if (last_error != WSAEWOULDBLOCK) + { + // WSAECONNRESET - I think this is caused by an ICMP "connection refused" + // message being sent back from a Linux box... I'm not finding helpful + // documentation or web pages on this. The question is whether the packet + // actually got sent or not. Based on the structure of this code, I would + // assume it is. JNC 2002.01.18 + if (WSAECONNRESET == WSAGetLastError()) + { + return TRUE; + } + llinfos << "sendto() failed to " << u32_to_ip_string(recipient) << ":" << nPort + << ", Error " << last_error << llendl; + } + } + } while ( (nRet == SOCKET_ERROR) + &&(last_error == WSAEWOULDBLOCK)); + + return (nRet != SOCKET_ERROR); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// Linux Versions +////////////////////////////////////////////////////////////////////////////////////////// + +#else + +// Create socket, make non-blocking +S32 start_net(S32& socket_out, int& nPort) +{ + int hSocket, nRet; + int snd_size = SEND_BUFFER_SIZE; + int rec_size = RECEIVE_BUFFER_SIZE; + + socklen_t buff_size = 4; + + // Create socket + hSocket = socket(AF_INET, SOCK_DGRAM, 0); + if (hSocket < 0) + { + llwarns << "socket() failed" << llendl; + return 1; + } + + // Don't bind() if we want the operating system to assign our ports for + // us. + if (NET_USE_OS_ASSIGNED_PORT == nPort) + { + // Do nothing; the operating system will do it for us. + } + else + { + // Name the socket (assign the local port number to receive on) + stLclAddr.sin_family = AF_INET; + stLclAddr.sin_addr.s_addr = htonl(INADDR_ANY); + stLclAddr.sin_port = htons(nPort); + U32 attempt_port = nPort; + llinfos << "attempting to connect on port " << attempt_port << llendl; + + nRet = bind(hSocket, (struct sockaddr*) &stLclAddr, sizeof(stLclAddr)); + if (nRet < 0) + { + // If we got an address in use error... + if (errno == EADDRINUSE) + { + // Try all ports from PORT_DISCOVERY_RANGE_MIN to PORT_DISCOVERY_RANGE_MAX + for(attempt_port = PORT_DISCOVERY_RANGE_MIN; + attempt_port <= PORT_DISCOVERY_RANGE_MAX; + attempt_port++) + { + stLclAddr.sin_port = htons(attempt_port); + llinfos << "trying port " << attempt_port << llendl; + nRet = bind(hSocket, (struct sockaddr*) &stLclAddr, sizeof(stLclAddr)); + if (!((nRet < 0) && (errno == EADDRINUSE))) + { + break; + } + } + if (nRet < 0) + { + llwarns << "startNet() : Couldn't find available network port." << llendl; + // Fail gracefully in release. + return 3; + } + } + // Some other socket error + else + { + llwarns << llformat ("bind() port: %d failed, Err: %s\n", nPort, strerror(errno)) << llendl; + // Fail gracefully in release. + return 4; + } + } + llinfos << "connected on port " << attempt_port << llendl; + nPort = attempt_port; + } + // Set socket to be non-blocking + fcntl(hSocket, F_SETFL, O_NONBLOCK); + // set a large receive buffer + nRet = setsockopt(hSocket, SOL_SOCKET, SO_RCVBUF, (char *)&rec_size, buff_size); + if (nRet) + { + llinfos << "Can't set receive size!" << llendl; + } + nRet = setsockopt(hSocket, SOL_SOCKET, SO_SNDBUF, (char *)&snd_size, buff_size); + if (nRet) + { + llinfos << "Can't set send size!" << llendl; + } + getsockopt(hSocket, SOL_SOCKET, SO_RCVBUF, (char *)&rec_size, &buff_size); + getsockopt(hSocket, SOL_SOCKET, SO_SNDBUF, (char *)&snd_size, &buff_size); + + llinfos << "startNet - receive buffer size : " << rec_size << llendl; + llinfos << "startNet - send buffer size : " << snd_size << llendl; + + // Setup a destination address + char achMCAddr[MAXADDRSTR] = "127.0.0.1"; /* Flawfinder: ignore */ + stDstAddr.sin_family = AF_INET; + stDstAddr.sin_addr.s_addr = inet_addr(achMCAddr); + stDstAddr.sin_port = htons(nPort); + + socket_out = hSocket; + return 0; +} + +void end_net() +{ +} + +int receive_packet(int hSocket, char * receiveBuffer) +{ + // Receives data asynchronously from the socket set by initNet(). + // Returns the number of bytes received into dataReceived, or zero + // if there is no data received. + // or -1 if an error occured! + int nRet; + socklen_t addr_size = sizeof(struct sockaddr_in); + + nRet = recvfrom(hSocket, receiveBuffer, NET_BUFFER_SIZE, 0, (struct sockaddr*)&stSrcAddr, &addr_size); + + if (nRet == -1) + { + // To maintain consistency with the Windows implementation, return a zero for size on error. + return 0; + } + + return nRet; +} + +BOOL send_packet(int hSocket, const char * sendBuffer, int size, U32 recipient, int nPort) +{ + int ret; + BOOL success; + BOOL resend; + S32 send_attempts = 0; + + stDstAddr.sin_addr.s_addr = recipient; + stDstAddr.sin_port = htons(nPort); + + do + { + ret = sendto(hSocket, sendBuffer, size, 0, (struct sockaddr*)&stDstAddr, sizeof(stDstAddr)); + send_attempts++; + + if (ret >= 0) + { + // successful send + success = TRUE; + resend = FALSE; + } + else + { + // send failed, check to see if we should resend + success = FALSE; + + if (errno == EAGAIN) + { + // say nothing, just repeat send + llinfos << "sendto() reported buffer full, resending (attempt " << send_attempts << ")" << llendl; + llinfos << inet_ntoa(stDstAddr.sin_addr) << ":" << nPort << llendl; + resend = TRUE; + } + else if (errno == ECONNREFUSED) + { + // response to ICMP connection refused message on earlier send + llinfos << "sendto() reported connection refused, resending (attempt " << send_attempts << ")" << llendl; + llinfos << inet_ntoa(stDstAddr.sin_addr) << ":" << nPort << llendl; + resend = TRUE; + } + else + { + // some other error + llinfos << "sendto() failed: " << errno << ", " << strerror(errno) << llendl; + llinfos << inet_ntoa(stDstAddr.sin_addr) << ":" << nPort << llendl; + resend = FALSE; + } + } + } + while ( resend && send_attempts < 3); + + if (send_attempts >= 3) + { + llinfos << "sendPacket() bailed out of send!" << llendl; + return FALSE; + } + + return success; +} + +#endif + +//EOF diff --git a/indra/llmessage/net.h b/indra/llmessage/net.h new file mode 100644 index 0000000000..1044807ba6 --- /dev/null +++ b/indra/llmessage/net.h @@ -0,0 +1,50 @@ +/** + * @file net.h + * @brief Cross platform UDP network code. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_NET_H +#define LL_NET_H + +class LLTimer; +class LLHost; + +#define NET_BUFFER_SIZE (0x2000) + +// Request a free local port from the operating system +#define NET_USE_OS_ASSIGNED_PORT 0 + +// Returns 0 on success, non-zero on error. +// Sets socket handler/descriptor, changes nPort if port requested is unavailable. +S32 start_net(S32& socket_out, int& nPort); +void end_net(); + +// returns size of packet or -1 in case of error +S32 receive_packet(int hSocket, char * receiveBuffer); + +BOOL send_packet(int hSocket, const char *sendBuffer, int size, U32 recipient, int nPort); // Returns TRUE on success. + +//void get_sender(char * tmp); +LLHost get_sender(); +U32 get_sender_port(); +U32 get_sender_ip(void); + +const char* u32_to_ip_string(U32 ip); // Returns pointer to internal string buffer, "(bad IP addr)" on failure, cannot nest calls +char* u32_to_ip_string(U32 ip, char *ip_string); // NULL on failure, ip_string on success, you must allocate at least MAXADDRSTR chars +U32 ip_string_to_u32(const char* ip_string); // Wrapper for inet_addr() + +extern const char* LOOPBACK_ADDRESS_STRING; + + +// useful MTU consts + +const S32 MTUBYTES = 1200; // 1500 = standard Ethernet MTU +const S32 ETHERNET_MTU_BYTES = 1500; +const S32 MTUBITS = MTUBYTES*8; +const S32 MTUU32S = MTUBITS/32; + + +#endif diff --git a/indra/llmessage/partsyspacket.cpp b/indra/llmessage/partsyspacket.cpp new file mode 100644 index 0000000000..4030cd815b --- /dev/null +++ b/indra/llmessage/partsyspacket.cpp @@ -0,0 +1,1277 @@ +/** + * @file partsyspacket.cpp + * @brief Object for packing particle system initialization parameters + * before sending them over the network. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "partsyspacket.h" +#include "imageids.h" + +// this function is global +void gSetInitDataDefaults(LLPartInitData *setMe) +{ + U32 i; + + //for(i = 0; i < 18; i++) + //{ + // setMe->k[i] = 0.0f; + //} + + //setMe->kill_p[0] = setMe->kill_p[1] = setMe->kill_p[2] = 0.0f; + //setMe->kill_p[3] = -0.2f; // time parameter, die when t= 5.0f + //setMe->kill_p[4] = 1.0f; + //setMe->kill_p[5] = -0.5f; // or radius == 2 (contracting) + + //setMe->bounce_p[0] = setMe->bounce_p[1] = + // setMe->bounce_p[2] = setMe->bounce_p[3] = 0.0f; + //setMe->bounce_p[4] = 1.0f; + + setMe->bounce_b = 1.0f; + // i just changed the meaning of bounce_b + // its now the attenuation from revlecting your velocity across the normal + // set by bounce_p + + //setMe->pos_ranges[0] = setMe->pos_ranges[2] = setMe->pos_ranges[4] = -1.0f; + //setMe->pos_ranges[1] = setMe->pos_ranges[3] = setMe->pos_ranges[5] = 1.0f; + + //setMe->vel_ranges[0] = setMe->vel_ranges[2] = setMe->vel_ranges[4] = -1.0f; + //setMe->vel_ranges[1] = setMe->vel_ranges[3] = setMe->vel_ranges[5] = 1.0f; + + for(i = 0; i < 3; i++) + { + setMe->diffEqAlpha[i] = 0.0f; + setMe->diffEqScale[i] = 0.0f; + } + + setMe->scale_range[0] = 1.00f; + setMe->scale_range[1] = 5.00f; + setMe->scale_range[2] = setMe->scale_range[3] = 0.0f; + + setMe->alpha_range[0] = setMe->alpha_range[1] = 1.0f; + setMe->alpha_range[2] = setMe->alpha_range[3] = 0.0f; + + setMe->vel_offset[0] = 0.0f; + setMe->vel_offset[1] = 0.0f; + setMe->vel_offset[2] = 0.0f; + + // start dropping particles when I'm more then one sim away + setMe->mDistBeginFadeout = 256.0f; + setMe->mDistEndFadeout = 1.414f * 512.0f; + // stop displaying particles when I'm more then two sim diagonals away + + setMe->mImageUuid = IMG_SHOT; + + for(i = 0; i < 8; i++) + { + setMe->mFlags[i] = 0x00; + } + + setMe->createMe = TRUE; + + setMe->maxParticles = 25; + setMe->initialParticles = 25; + + //These defaults are for an explosion - a short lived set of debris affected by gravity. + //Action flags default to PART_SYS_AFFECTED_BY_WIND + PART_SYS_AFFECTED_BY_GRAVITY + PART_SYS_DISTANCE_DEATH + setMe->mFlags[PART_SYS_ACTION_BYTE] = PART_SYS_AFFECTED_BY_WIND | PART_SYS_AFFECTED_BY_GRAVITY | PART_SYS_DISTANCE_DEATH; + setMe->mFlags[PART_SYS_KILL_BYTE] = PART_SYS_DISTANCE_DEATH + PART_SYS_TIME_DEATH; + + setMe->killPlaneNormal[0] = 0.0f;setMe->killPlaneNormal[1] = 0.0f;setMe->killPlaneNormal[2] = 1.0f; //Straight up + setMe->killPlaneZ = 0.0f; //get local ground z as an approximation if turn on PART_SYS_KILL_PLANE + setMe->bouncePlaneNormal[0] = 0.0f;setMe->bouncePlaneNormal[1] = 0.0f;setMe->bouncePlaneNormal[2] = 1.0f; //Straight up + setMe->bouncePlaneZ = 0.0f; //get local ground z as an approximation if turn on PART_SYS_BOUNCE + setMe->spawnRange = 1.0f; + setMe->spawnFrequency = 0.0f; //Create the instant one dies + setMe->spawnFreqencyRange = 0.0f; + setMe->spawnDirection[0] = 0.0f;setMe->spawnDirection[1] = 0.0f;setMe->spawnDirection[2] = 1.0f; //Straight up + setMe->spawnDirectionRange = 1.0f; //global scattering + setMe->spawnVelocity = 0.75f; + setMe->spawnVelocityRange = 0.25f; //velocity +/- 0.25 + setMe->speedLimit = 1.0f; + + setMe->windWeight = 0.5f; //0.0f means looks like a heavy object (if gravity is on), 1.0f means light and fluffy + setMe->currentGravity[0] = 0.0f;setMe->currentGravity[1] = 0.0f;setMe->currentGravity[2] = -9.81f; + //This has to be constant to allow for compression + + setMe->gravityWeight = 0.5f; //0.0f means boyed by air, 1.0f means it's a lead weight + setMe->globalLifetime = 0.0f; //Arbitrary, but default is no global die, so doesn't matter + setMe->individualLifetime = 5.0f; + setMe->individualLifetimeRange = 1.0f; //Particles last 5 secs +/- 1 + setMe->alphaDecay = 1.0f; //normal alpha fadeout + setMe->scaleDecay = 0.0f; //no scale decay + setMe->distanceDeath = 10.0f; //die if hit unit radius + setMe->dampMotionFactor = 0.0f; + + setMe->windDiffusionFactor[0] = 0.0f; + setMe->windDiffusionFactor[1] = 0.0f; + setMe->windDiffusionFactor[2] = 0.0f; +} + +LLPartSysCompressedPacket::LLPartSysCompressedPacket() +{ + // default constructor for mDefaults called implicitly/automatically here + for(int i = 0; i < MAX_PART_SYS_PACKET_SIZE; i++) + { + mData[i] = '\0'; + } + + gSetInitDataDefaults(&mDefaults); +} + +LLPartSysCompressedPacket::~LLPartSysCompressedPacket() +{ + // no dynamic data is stored by this class, do nothing. +} + +void LLPartSysCompressedPacket::writeFlagByte(LLPartInitData *in) +{ + mData[0] = mData[1] = mData[2] = '\0'; + + U32 i; + //for(i = 1; i < 18; i++) { + // if(in->k[i] != mDefaults.k[i]) + // { + // mData[0] |= PART_SYS_K_MASK; + // break; + // } + //} + + if(in->killPlaneZ != mDefaults.killPlaneZ || + in->killPlaneNormal[0] != mDefaults.killPlaneNormal[0] || + in->killPlaneNormal[1] != mDefaults.killPlaneNormal[1] || + in->killPlaneNormal[2] != mDefaults.killPlaneNormal[2] || + in->distanceDeath != mDefaults.distanceDeath) + { + mData[0] |= PART_SYS_KILL_P_MASK; + } + + + + if(in->bouncePlaneZ != mDefaults.bouncePlaneZ || + in->bouncePlaneNormal[0] != mDefaults.bouncePlaneNormal[0] || + in->bouncePlaneNormal[1] != mDefaults.bouncePlaneNormal[1] || + in->bouncePlaneNormal[2] != mDefaults.bouncePlaneNormal[2]) + { + mData[0] |= PART_SYS_BOUNCE_P_MASK; + } + + if(in->bounce_b != mDefaults.bounce_b) + { + mData[0] |= PART_SYS_BOUNCE_B_MASK; + } + + + //if(in->pos_ranges[0] != mDefaults.pos_ranges[0] || in->pos_ranges[1] != mDefaults.pos_ranges[1] || + // in->pos_ranges[2] != mDefaults.pos_ranges[2] || in->pos_ranges[3] != mDefaults.pos_ranges[3] || + // in->pos_ranges[4] != mDefaults.pos_ranges[4] || in->pos_ranges[5] != mDefaults.pos_ranges[5]) + //{ + // mData[0] |= PART_SYS_POS_RANGES_MASK; + //} + + //if(in->vel_ranges[0] != mDefaults.vel_ranges[0] || in->vel_ranges[1] != mDefaults.vel_ranges[1] || + // in->vel_ranges[2] != mDefaults.vel_ranges[2] || in->vel_ranges[3] != mDefaults.vel_ranges[3] || + // in->vel_ranges[4] != mDefaults.vel_ranges[4] || in->vel_ranges[5] != mDefaults.vel_ranges[5]) + //{ +// mData[0] |= PART_SYS_VEL_RANGES_MASK; + //} + + + if(in->diffEqAlpha[0] != mDefaults.diffEqAlpha[0] || + in->diffEqAlpha[1] != mDefaults.diffEqAlpha[1] || + in->diffEqAlpha[2] != mDefaults.diffEqAlpha[2] || + in->diffEqScale[0] != mDefaults.diffEqScale[0] || + in->diffEqScale[1] != mDefaults.diffEqScale[1] || + in->diffEqScale[2] != mDefaults.diffEqScale[2]) + { + mData[0] |= PART_SYS_ALPHA_SCALE_DIFF_MASK; + } + + + if(in->scale_range[0] != mDefaults.scale_range[0] || + in->scale_range[1] != mDefaults.scale_range[1] || + in->scale_range[2] != mDefaults.scale_range[2] || + in->scale_range[3] != mDefaults.scale_range[3]) + { + mData[0] |= PART_SYS_SCALE_RANGE_MASK; + } + + + if(in->alpha_range[0] != mDefaults.alpha_range[0] || + in->alpha_range[1] != mDefaults.alpha_range[1] || + in->alpha_range[2] != mDefaults.alpha_range[2] || + in->alpha_range[3] != mDefaults.alpha_range[3]) + { + mData[2] |= PART_SYS_BYTE_3_ALPHA_MASK; + } + + if(in->vel_offset[0] != mDefaults.vel_offset[0] || + in->vel_offset[1] != mDefaults.vel_offset[1] || + in->vel_offset[2] != mDefaults.vel_offset[2]) + { + mData[0] |= PART_SYS_VEL_OFFSET_MASK; + } + + + if(in->mImageUuid != mDefaults.mImageUuid) + { + mData[0] |= PART_SYS_M_IMAGE_UUID_MASK; + } + + for( i = 0; i < 8; i++) + { + if(in->mFlags[i]) + { + mData[1] |= 1<mFlags[i]); + } + } + + + if(in->spawnRange != mDefaults.spawnRange || + in->spawnFrequency != mDefaults.spawnFrequency || + in->spawnFreqencyRange != mDefaults.spawnFreqencyRange || + in->spawnDirection[0] != mDefaults.spawnDirection[0] || + in->spawnDirection[1] != mDefaults.spawnDirection[1] || + in->spawnDirection[2] != mDefaults.spawnDirection[2] || + in->spawnDirectionRange != mDefaults.spawnDirectionRange || + in->spawnVelocity != mDefaults.spawnVelocity || + in->spawnVelocityRange != mDefaults.spawnVelocityRange) + { + mData[3] |= PART_SYS_BYTE_SPAWN_MASK; + } + + + if(in->windWeight != mDefaults.windWeight || + in->currentGravity[0] != mDefaults.currentGravity[0] || + in->currentGravity[1] != mDefaults.currentGravity[1] || + in->currentGravity[2] != mDefaults.currentGravity[2] || + in->gravityWeight != mDefaults.gravityWeight) + { + mData[3] |= PART_SYS_BYTE_ENVIRONMENT_MASK; + } + + + if(in->globalLifetime != mDefaults.globalLifetime || + in->individualLifetime != mDefaults.individualLifetime || + in->individualLifetimeRange != mDefaults.individualLifetimeRange) + { + mData[3] |= PART_SYS_BYTE_LIFESPAN_MASK; + } + + + if(in->speedLimit != mDefaults.speedLimit || + in->alphaDecay != mDefaults.alphaDecay || + in->scaleDecay != mDefaults.scaleDecay || + in->dampMotionFactor != mDefaults.dampMotionFactor) + { + mData[3] |= PART_SYS_BYTE_DECAY_DAMP_MASK; + } + + if(in->windDiffusionFactor[0] != mDefaults.windDiffusionFactor[0] || + in->windDiffusionFactor[1] != mDefaults.windDiffusionFactor[1] || + in->windDiffusionFactor[2] != mDefaults.windDiffusionFactor[2]) + { + mData[3] |= PART_SYS_BYTE_WIND_DIFF_MASK; + } +} + +F32 floatFromTwoBytes(S8 bMant, S8 bExp) +{ + F32 result = bMant; + while(bExp > 0) + { + result *= 2.0f; + bExp--; + } + while(bExp < 0) + { + result *= 0.5f; + bExp++; + } + return result; +} + +void twoBytesFromFloat(F32 fIn, S8 &bMant, S8 &bExp) +{ + bExp = 0; + if(fIn > 127.0f) + { + fIn = 127.0f; + } + if(fIn < -127.0f) + { + fIn = -127.0f; + } + while(fIn < 64 && fIn > -64 && bExp > -127) + { + fIn *= 2.0f; + bExp--; + } + while((fIn > 128 || fIn < -128) && bExp < 127) + { + fIn *= 0.5f; + bExp++; + } + bMant = (S8)fIn; +} + + + +/* +U32 LLPartSysCompressedPacket::writeK(LLPartInitData *in, U32 startByte) +{ + U32 i, kFlag, i_mod_eight; + S8 bMant, bExp; + + kFlag = startByte; + + startByte += 3; // 3 bytes contain enough room for 18 flag bits + mData[kFlag] = 0x00; +// llprintline("In the writeK\n"); + + i_mod_eight = 0; + for(i = 0; i < 18; i++) + { + if(in->k[i] != mDefaults.k[i]) + { + + mData[kFlag] |= 1<k[i], bMant, bExp); + + mData[startByte++] = bMant; + mData[startByte++] = bExp; + } + i_mod_eight++; + while(i_mod_eight >= 8) + { + kFlag++; + i_mod_eight -= 8; + } + } + + return startByte; +}*/ + +U32 LLPartSysCompressedPacket::writeKill_p(LLPartInitData *in, U32 startByte) +{ + S8 bMant, bExp; + + twoBytesFromFloat(in->killPlaneNormal[0], bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + twoBytesFromFloat(in->killPlaneNormal[1], bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + twoBytesFromFloat(in->killPlaneNormal[2], bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + twoBytesFromFloat(in->killPlaneZ, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + twoBytesFromFloat(in->distanceDeath, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + return startByte; +} + +U32 LLPartSysCompressedPacket::writeBounce_p(LLPartInitData *in, U32 startByte) +{ + S8 bMant, bExp; + + twoBytesFromFloat(in->bouncePlaneNormal[0], bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + twoBytesFromFloat(in->bouncePlaneNormal[1], bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + twoBytesFromFloat(in->bouncePlaneNormal[2], bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + + twoBytesFromFloat(in->bouncePlaneZ, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + return startByte; +} + +U32 LLPartSysCompressedPacket::writeBounce_b(LLPartInitData *in, U32 startByte) +{ + S8 bMant, bExp; + twoBytesFromFloat(in->bounce_b, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + return startByte; +} + +//U32 LLPartSysCompressedPacket::writePos_ranges(LLPartInitData *in, U32 startByte) +//{ +// S8 tmp; +// int i; +// for(i = 0; i < 6; i++) +// { +// tmp = (S8) in->pos_ranges[i]; // float to int conversion (keep the sign) +// mData[startByte++] = (U8)tmp; // signed to unsigned typecast +// } +// return startByte; +//} + +//U32 LLPartSysCompressedPacket::writeVel_ranges(LLPartInitData *in, U32 startByte) +//{ +// S8 tmp; +// int i; +// for(i = 0; i < 6; i++) +// { +// tmp = (S8) in->vel_ranges[i]; // float to int conversion (keep the sign) +// mData[startByte++] = (U8)tmp; // signed to unsigned typecast +// } +// return startByte; +//} + +U32 LLPartSysCompressedPacket::writeAlphaScaleDiffEqn_range(LLPartInitData *in, U32 startByte) +{ + S8 bExp, bMant; + int i; + for(i = 0; i < 3; i++) + { + twoBytesFromFloat(in->diffEqAlpha[i], bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + } + for(i = 0; i < 3; i++) + { + twoBytesFromFloat(in->diffEqScale[i], bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + } + return startByte; +} + +U32 LLPartSysCompressedPacket::writeScale_range(LLPartInitData *in, U32 startByte) +{ + S8 bExp, bMant; + int i; + for(i = 0; i < 4; i++) + { + twoBytesFromFloat(in->scale_range[i], bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + } + return startByte; +} + + +U32 LLPartSysCompressedPacket::writeAlpha_range(LLPartInitData *in, U32 startByte) +{ + S8 bExp, bMant; + int i; + for(i = 0; i < 4; i++) + { + twoBytesFromFloat(in->alpha_range[i], bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + } + return startByte; +} + +U32 LLPartSysCompressedPacket::writeVelocityOffset(LLPartInitData *in, U32 startByte) +{ + S8 bExp, bMant; + int i; + for(i = 0; i < 3; i++) + { + twoBytesFromFloat(in->vel_offset[i], bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + } + return startByte; +} + +U32 LLPartSysCompressedPacket::writeUUID(LLPartInitData *in, U32 startByte) +{ + U8 * bufPtr = mData + startByte; + if(in->mImageUuid == IMG_SHOT) { + mData[startByte++] = 0x01; + return startByte; + } + + if(in->mImageUuid == IMG_SPARK) { + mData[startByte++] = 0x02; + return startByte; + } + + + if(in->mImageUuid == IMG_BIG_EXPLOSION_1) { + mData[startByte++] = 0x03; + return startByte; + } + + if(in->mImageUuid == IMG_BIG_EXPLOSION_2) { + mData[startByte++] = 0x04; + return startByte; + } + + + if(in->mImageUuid == IMG_SMOKE_POOF) { + mData[startByte++] = 0x05; + return startByte; + } + + if(in->mImageUuid == IMG_FIRE) { + mData[startByte++] = 0x06; + return startByte; + } + + + if(in->mImageUuid == IMG_EXPLOSION) { + mData[startByte++] = 0x07; + return startByte; + } + + if(in->mImageUuid == IMG_EXPLOSION_2) { + mData[startByte++] = 0x08; + return startByte; + } + + + if(in->mImageUuid == IMG_EXPLOSION_3) { + mData[startByte++] = 0x09; + return startByte; + } + + if(in->mImageUuid == IMG_EXPLOSION_4) { + mData[startByte++] = 0x0A; + return startByte; + } + + mData[startByte++] = 0x00; // flag for "read whole UUID" + + memcpy(bufPtr, in->mImageUuid.mData, 16); /* Flawfinder: ignore */ + return (startByte+16); +} + +U32 LLPartSysCompressedPacket::writeSpawn(LLPartInitData *in, U32 startByte) +{ + S8 bExp, bMant; + int i; + + twoBytesFromFloat(in->spawnRange, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + twoBytesFromFloat(in->spawnFrequency, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + twoBytesFromFloat(in->spawnFreqencyRange, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + + + for(i = 0; i < 3; i++) + { + twoBytesFromFloat(in->spawnDirection[i], bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + } + + twoBytesFromFloat(in->spawnDirectionRange, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + twoBytesFromFloat(in->spawnVelocity, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + twoBytesFromFloat(in->spawnVelocityRange, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + return startByte; +} + +U32 LLPartSysCompressedPacket::writeEnvironment(LLPartInitData *in, U32 startByte) +{ + S8 bExp, bMant; + int i; + + twoBytesFromFloat(in->windWeight, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + for(i = 0; i < 3; i++) + { + twoBytesFromFloat(in->currentGravity[i], bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + } + + twoBytesFromFloat(in->gravityWeight, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + return startByte; +} + +U32 LLPartSysCompressedPacket::writeLifespan(LLPartInitData *in, U32 startByte) +{ + S8 bExp, bMant; + + twoBytesFromFloat(in->globalLifetime, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + twoBytesFromFloat(in->individualLifetime, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + twoBytesFromFloat(in->individualLifetimeRange, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + return startByte; +} + + +U32 LLPartSysCompressedPacket::writeDecayDamp(LLPartInitData *in, U32 startByte) +{ + S8 bExp, bMant; + + twoBytesFromFloat(in->speedLimit, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + twoBytesFromFloat(in->alphaDecay, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + twoBytesFromFloat(in->scaleDecay, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + twoBytesFromFloat(in->dampMotionFactor, bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + return startByte; +} + +U32 LLPartSysCompressedPacket::writeWindDiffusionFactor(LLPartInitData *in, U32 startByte) +{ + S8 bExp, bMant; + + twoBytesFromFloat(in->windDiffusionFactor[0], bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + twoBytesFromFloat(in->windDiffusionFactor[1], bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + twoBytesFromFloat(in->windDiffusionFactor[2], bMant, bExp); + mData[startByte++] = bMant; + mData[startByte++] = bExp; + + return startByte; +} + + + + + + +/* +U32 LLPartSysCompressedPacket::readK(LLPartInitData *in, U32 startByte) +{ + U32 i, i_mod_eight, kFlag; + S8 bMant, bExp; // 1 bytes mantissa and exponent for a float + kFlag = startByte; + startByte += 3; // 3 bytes has enough room for 18 bits + + i_mod_eight = 0; + for(i = 0; i < 18; i++) + { + if(mData[kFlag]&(1<k[i] = floatFromTwoBytes(bMant, bExp); // much tighter platform-independent + // way to ship floats + + } + i_mod_eight++; + if(i_mod_eight >= 8) + { + i_mod_eight -= 8; + kFlag++; + } + } + + return startByte; +} +*/ + +U32 LLPartSysCompressedPacket::readKill_p(LLPartInitData *in, U32 startByte) +{ + S8 bMant, bExp; + + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->killPlaneNormal[0] = floatFromTwoBytes(bMant, bExp); + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->killPlaneNormal[1] = floatFromTwoBytes(bMant, bExp); + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->killPlaneNormal[2] = floatFromTwoBytes(bMant, bExp); + + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->killPlaneZ = floatFromTwoBytes(bMant, bExp); + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->distanceDeath = floatFromTwoBytes(bMant, bExp); + + return startByte; +} + +U32 LLPartSysCompressedPacket::readBounce_p(LLPartInitData *in, U32 startByte) +{ + + S8 bMant, bExp; + + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->bouncePlaneNormal[0] = floatFromTwoBytes(bMant, bExp); + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->bouncePlaneNormal[1] = floatFromTwoBytes(bMant, bExp); + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->bouncePlaneNormal[2] = floatFromTwoBytes(bMant, bExp); + + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->bouncePlaneZ = floatFromTwoBytes(bMant, bExp); + + return startByte; +} + +U32 LLPartSysCompressedPacket::readBounce_b(LLPartInitData *in, U32 startByte) +{ + S8 bMant, bExp; + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->bounce_b = floatFromTwoBytes(bMant, bExp); + return startByte; +} + + +//U32 LLPartSysCompressedPacket::readPos_ranges(LLPartInitData *in, U32 startByte) +//{ +// S8 tmp; +// int i; +// for(i = 0; i < 6; i++) +// { +// tmp = (S8)mData[startByte++]; +// in->pos_ranges[i] = tmp; +// } +// return startByte; +//} + +//U32 LLPartSysCompressedPacket::readVel_ranges(LLPartInitData *in, U32 startByte) +//{ +// S8 tmp; +// int i; +// for(i = 0; i < 6; i++) +// { +// tmp = (S8)mData[startByte++]; +// in->vel_ranges[i] = tmp; +// } +// return startByte; +//} + + + +U32 LLPartSysCompressedPacket::readAlphaScaleDiffEqn_range(LLPartInitData *in, U32 startByte) +{ + int i; + S8 bMant, bExp; + for(i = 0; i < 3; i++) + { + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->diffEqAlpha[i] = floatFromTwoBytes(bMant, bExp); + } + for(i = 0; i < 3; i++) + { + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->diffEqScale[i] = floatFromTwoBytes(bMant, bExp); + } + return startByte; +} + +U32 LLPartSysCompressedPacket::readAlpha_range(LLPartInitData *in, U32 startByte) +{ + int i; + S8 bMant, bExp; + for(i = 0; i < 4; i++) + { + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->alpha_range[i] = floatFromTwoBytes(bMant, bExp); + } + return startByte; +} + +U32 LLPartSysCompressedPacket::readScale_range(LLPartInitData *in, U32 startByte) +{ + int i; + S8 bMant, bExp; + for(i = 0; i < 4; i++) + { + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->scale_range[i] = floatFromTwoBytes(bMant, bExp); + } + return startByte; +} + +U32 LLPartSysCompressedPacket::readVelocityOffset(LLPartInitData *in, U32 startByte) +{ + int i; + S8 bMant, bExp; + for(i = 0; i < 3; i++) + { + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->vel_offset[i] = floatFromTwoBytes(bMant, bExp); + } + return startByte; +} + +U32 LLPartSysCompressedPacket::readUUID(LLPartInitData *in, U32 startByte) +{ + U8 * bufPtr = mData + startByte; + + if(mData[startByte] == 0x01) + { + in->mImageUuid = IMG_SHOT; + return startByte+1; + } + if(mData[startByte] == 0x02) + { + in->mImageUuid = IMG_SPARK; + return startByte+1; + } + if(mData[startByte] == 0x03) + { + in->mImageUuid = IMG_BIG_EXPLOSION_1; + return startByte+1; + } + if(mData[startByte] == 0x04) + { + in->mImageUuid = IMG_BIG_EXPLOSION_2; + return startByte+1; + } + if(mData[startByte] == 0x05) + { + in->mImageUuid = IMG_SMOKE_POOF; + return startByte+1; + } + if(mData[startByte] == 0x06) + { + in->mImageUuid = IMG_FIRE; + return startByte+1; + } + if(mData[startByte] == 0x07) + { + in->mImageUuid = IMG_EXPLOSION; + return startByte+1; + } + if(mData[startByte] == 0x08) + { + in->mImageUuid = IMG_EXPLOSION_2; + return startByte+1; + } + if(mData[startByte] == 0x09) + { + in->mImageUuid = IMG_EXPLOSION_3; + return startByte+1; + } + if(mData[startByte] == 0x0A) + { + in->mImageUuid = IMG_EXPLOSION_4; + return startByte+1; + } + + startByte++; // cause we actually have to read the UUID now. + memcpy(in->mImageUuid.mData, bufPtr, 16); /* Flawfinder: ignore */ + return (startByte+16); +} + + + + +U32 LLPartSysCompressedPacket::readSpawn(LLPartInitData *in, U32 startByte) +{ + S8 bMant, bExp; + U32 i; + + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->spawnRange = floatFromTwoBytes(bMant, bExp); + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->spawnFrequency = floatFromTwoBytes(bMant, bExp); + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->spawnFreqencyRange = floatFromTwoBytes(bMant, bExp); + + for(i = 0; i < 3; i++) + { + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->spawnDirection[i] = floatFromTwoBytes(bMant, bExp); + } + + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->spawnDirectionRange = floatFromTwoBytes(bMant, bExp); + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->spawnVelocity = floatFromTwoBytes(bMant, bExp); + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->spawnVelocityRange = floatFromTwoBytes(bMant, bExp); + + return startByte; +} + +U32 LLPartSysCompressedPacket::readEnvironment(LLPartInitData *in, U32 startByte) +{ + S8 bMant, bExp; + U32 i; + + + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->windWeight = floatFromTwoBytes(bMant, bExp); + + for(i = 0; i < 3; i++) + { + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->currentGravity[i] = floatFromTwoBytes(bMant, bExp); + } + + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->gravityWeight = floatFromTwoBytes(bMant, bExp); + + return startByte; +} + +U32 LLPartSysCompressedPacket::readLifespan(LLPartInitData *in, U32 startByte) +{ + S8 bMant, bExp; + + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->globalLifetime = floatFromTwoBytes(bMant, bExp); + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->individualLifetime = floatFromTwoBytes(bMant, bExp); + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->individualLifetimeRange = floatFromTwoBytes(bMant, bExp); + + return startByte; +} + +U32 LLPartSysCompressedPacket::readDecayDamp(LLPartInitData *in, U32 startByte) +{ + S8 bMant, bExp; + + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->speedLimit = floatFromTwoBytes(bMant, bExp); + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->alphaDecay = floatFromTwoBytes(bMant, bExp); + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->scaleDecay = floatFromTwoBytes(bMant, bExp); + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->dampMotionFactor = floatFromTwoBytes(bMant, bExp); + + return startByte; +} + +U32 LLPartSysCompressedPacket::readWindDiffusionFactor(LLPartInitData *in, U32 startByte) +{ + int i; + S8 bMant, bExp; + for(i = 0; i < 3; i++) + { + bMant = mData[startByte++]; + bExp = mData[startByte++]; + in->windDiffusionFactor[i] = floatFromTwoBytes(bMant, bExp); + } + return startByte; +} + +BOOL LLPartSysCompressedPacket::fromLLPartInitData(LLPartInitData *in, U32 &bytesUsed) +{ + + writeFlagByte(in); + U32 currByte = 4; + +// llprintline("calling \"fromLLPartInitData\"\n"); + + //if(mData[0] & PART_SYS_K_MASK) + //{ + // currByte = writeK(in, 3); // first 3 bytes are reserved for header data + //} + + + + if(mData[0] & PART_SYS_KILL_P_MASK) + { + currByte = writeKill_p(in, currByte); + } + + if(mData[0] & PART_SYS_BOUNCE_P_MASK) + { + currByte = writeBounce_p(in, currByte); + } + + if(mData[0] & PART_SYS_BOUNCE_B_MASK) + { + currByte = writeBounce_b(in, currByte); + } + + //if(mData[0] & PART_SYS_POS_RANGES_MASK) + //{ + // currByte = writePos_ranges(in, currByte); + //} + + //if(mData[0] & PART_SYS_VEL_RANGES_MASK) + //{ + // currByte = writeVel_ranges(in, currByte); + //} + + if(mData[0] & PART_SYS_ALPHA_SCALE_DIFF_MASK) + { + currByte = writeAlphaScaleDiffEqn_range(in, currByte); + } + + if(mData[0] & PART_SYS_SCALE_RANGE_MASK) + { + currByte = writeScale_range(in, currByte); + } + + if(mData[0] & PART_SYS_VEL_OFFSET_MASK) + { + currByte = writeVelocityOffset(in, currByte); + } + + if(mData[0] & PART_SYS_M_IMAGE_UUID_MASK) + { + currByte = writeUUID(in, currByte); + } + + + if(mData[3] & PART_SYS_BYTE_SPAWN_MASK) + { + currByte = writeSpawn(in, currByte); + } + + if(mData[3] & PART_SYS_BYTE_ENVIRONMENT_MASK) + { + currByte = writeEnvironment(in, currByte); + } + + if(mData[3] & PART_SYS_BYTE_LIFESPAN_MASK) + { + currByte = writeLifespan(in, currByte); + } + + if(mData[3] & PART_SYS_BYTE_DECAY_DAMP_MASK) + { + currByte = writeDecayDamp(in, currByte); + } + + if(mData[3] & PART_SYS_BYTE_WIND_DIFF_MASK) + { + currByte = writeWindDiffusionFactor(in, currByte); + } + + + if(mData[2] & PART_SYS_BYTE_3_ALPHA_MASK) + { + currByte = writeAlpha_range(in, currByte); + } + + mData[currByte++] = (U8)in->maxParticles; + mData[currByte++] = (U8)in->initialParticles; + + + U32 flagFlag = 1; // flag indicating which flag bytes are non-zero + // yeah, I know, the name sounds funny + for(U32 i = 0; i < 8; i++) + { + +// llprintline("Flag \"%x\" gets byte \"%x\"\n", flagFlag, in->mFlags[i]); + if(mData[1] & flagFlag) + { + mData[currByte++] = in->mFlags[i]; +// llprintline("and is valid...\n"); + } + flagFlag <<= 1; + } + + bytesUsed = mNumBytes = currByte; + + + +// llprintline("returning from \"fromLLPartInitData\" with %d bytes\n", bytesUsed); + + return TRUE; +} + +BOOL LLPartSysCompressedPacket::toLLPartInitData(LLPartInitData *out, U32 *bytesUsed) +{ + U32 currByte = 4; + + gSetInitDataDefaults(out); + + if(mData[0] & PART_SYS_KILL_P_MASK) + { + currByte = readKill_p(out, currByte); + } + + if(mData[0] & PART_SYS_BOUNCE_P_MASK) + { + currByte = readBounce_p(out, currByte); + } + + if(mData[0] & PART_SYS_BOUNCE_B_MASK) + { + currByte = readBounce_b(out, currByte); + } + + if(mData[0] & PART_SYS_ALPHA_SCALE_DIFF_MASK) + { + currByte = readAlphaScaleDiffEqn_range(out, currByte); + } + + if(mData[0] & PART_SYS_SCALE_RANGE_MASK) + { + currByte = readScale_range(out, currByte); + } + + if(mData[0] & PART_SYS_VEL_OFFSET_MASK) + { + currByte = readVelocityOffset(out, currByte); + } + + if(mData[0] & PART_SYS_M_IMAGE_UUID_MASK) + { + currByte = readUUID(out, currByte); + } + + + if(mData[3] & PART_SYS_BYTE_SPAWN_MASK) + { + currByte = readSpawn(out, currByte); + } + + if(mData[3] & PART_SYS_BYTE_ENVIRONMENT_MASK) + { + currByte = readEnvironment(out, currByte); + } + + if(mData[3] & PART_SYS_BYTE_LIFESPAN_MASK) + { + currByte = readLifespan(out, currByte); + } + + if(mData[3] & PART_SYS_BYTE_DECAY_DAMP_MASK) + { + currByte = readDecayDamp(out, currByte); + } + + if(mData[3] & PART_SYS_BYTE_WIND_DIFF_MASK) + { + currByte = readWindDiffusionFactor(out, currByte); + } + + if(mData[2] & PART_SYS_BYTE_3_ALPHA_MASK) + { + currByte = readAlpha_range(out, currByte); + } + + out->maxParticles = mData[currByte++]; + out->initialParticles = mData[currByte++]; + + U32 flagFlag = 1; // flag indicating which flag bytes are non-zero + // yeah, I know, the name sounds funny + for(U32 i = 0; i < 8; i++) + { + flagFlag = 1<mFlags[i] = mData[currByte++]; + } + } + + *bytesUsed = currByte; + return TRUE; +} + +BOOL LLPartSysCompressedPacket::fromUnsignedBytes(U8 *in, U32 bytesUsed) +{ + if ((in != NULL) && (bytesUsed <= sizeof(mData))) + { + memcpy(mData, in, bytesUsed); + mNumBytes = bytesUsed; + return TRUE; + } + else + { + llerrs << "NULL input data or number of bytes exceed mData size" << llendl; + return FALSE; + } +} + + +U32 LLPartSysCompressedPacket::bufferSize() +{ + return mNumBytes; +} + +BOOL LLPartSysCompressedPacket::toUnsignedBytes(U8 *out) +{ + memcpy(out, mData, mNumBytes); /* Flawfinder: ignore */ + return TRUE; +} + +U8 * LLPartSysCompressedPacket::getBytePtr() +{ + return mData; +} + + diff --git a/indra/llmessage/partsyspacket.h b/indra/llmessage/partsyspacket.h new file mode 100644 index 0000000000..c98eb42bfb --- /dev/null +++ b/indra/llmessage/partsyspacket.h @@ -0,0 +1,243 @@ +/** + * @file partsyspacket.h + * @brief Object for packing particle system initialization parameters + * before sending them over the network + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_PARTSYSPACKET_H +#define LL_PARTSYSPACKET_H + +#include "lluuid.h" + +// Particle system stuff + +const U64 PART_SYS_MAX_TIME_IN_USEC = 1000000; // 1 second, die not quite near instantaneously + + +// this struct is for particle system initialization parameters +// I'm breaking some rules here, but I need a storage structure to hold initialization data +// for these things. Sorry guys, they're not simple enough (yet) to avoid this cleanly +struct LLPartInitData { + // please do not add functions to this class -- data only! + //F32 k[18]; // first 9 --> x,y,z last 9 --> scale, alpha, rot + //F32 kill_p[6]; // last one is for particles that die when they reach a spherical bounding radius + //F32 kill_plane[3]; + //F32 bounce_p[5]; + F32 bounce_b; // recently changed + // no need to store orientation and position here, as they're sent over seperately + //F32 pos_ranges[6]; + //F32 vel_ranges[6]; + F32 scale_range[4]; + F32 alpha_range[4]; + F32 vel_offset[3]; //new - more understandable! + + F32 mDistBeginFadeout; // for fadeout LOD optimization + F32 mDistEndFadeout; + + LLUUID mImageUuid; + //U8 n; // number of particles + U8 mFlags[8]; // for miscellaneous data --> its interpretation can change at my whim! + U8 createMe; // do I need to be created? or has the work allready been done? + //ActionFlag is now mFlags[PART_SYS_ACTION_BYTE] + //Spawn point is initially object creation center + + F32 diffEqAlpha[3]; + F32 diffEqScale[3]; + + U8 maxParticles; + //How many particles exist at any time within the system? + U8 initialParticles; + //How many particles exist when the system is created? + F32 killPlaneZ; + //For simplicity assume the XY plane, so this sets an altitude at which to die + F32 killPlaneNormal[3]; + //Normal if not planar XY + F32 bouncePlaneZ; + //For simplicity assume the XY plane, so this sets an altitude at which to bounce + F32 bouncePlaneNormal[3]; + //Normal if not planar XY + F32 spawnRange; + //Range of emission points about the mSpawnPoint + F32 spawnFrequency; + //Required if the system is to spawn new particles. + //This variable determines the time after a particle dies when it is respawned. + F32 spawnFreqencyRange; + //Determines the random range of time until a new particle is spawned. + F32 spawnDirection[3]; + //Direction vector giving the mean direction in which particles are spawned + F32 spawnDirectionRange; + //Direction limiting the angular range of emissions about the mean direction. 1.0f means everywhere, 0.0f means uni-directional + F32 spawnVelocity; + //The mean speed at which particles are emitted + F32 spawnVelocityRange; + //The range of speeds about the mean at which particles are emitted. + F32 speedLimit; + //Used to constrain particle maximum velocity + F32 windWeight; + //How much of an effect does wind have + F32 currentGravity[3]; + //Gravity direction used in update calculations + F32 gravityWeight; + //How much of an effect does gravity have + F32 globalLifetime; + //If particles re-spawn, a system can exist forever. + //If (ActionFlags & PART_SYS_GLOBAL_DIE) is TRUE this variable is used to determine how long the system lasts. + F32 individualLifetime; + //How long does each particle last if nothing else happens to it + F32 individualLifetimeRange; + //Range of variation in individual lifetimes + F32 alphaDecay; + //By what factor does alpha decrease as the lifetime of a particle is approached. + F32 scaleDecay; + //By what factor does scale decrease as the lifetime of a particle is approached. + F32 distanceDeath; + //With the increased functionality, particle systems can expand to indefinite size + //(e.g. wind can chaotically move particles into a wide spread). + //To avoid particles exceeding normal object size constraints, + //set the PART_SYS_DISTANCE_DEATH flag, and set a distance value here, representing a radius around the spawn point. + F32 dampMotionFactor; + //How much to damp motion + F32 windDiffusionFactor[3]; + //Change the size and alpha of particles as wind speed increases (scale gets bigger, alpha smaller) +}; + +// constants for setting flag values +// BYTES are in range 0-8, bits are in range 2^0 - 2^8 and can only be powers of two +const int PART_SYS_NO_Z_BUFFER_BYTE = 0; // option to turn off z-buffer when rendering +const int PART_SYS_NO_Z_BUFFER_BIT = 2; // particle systems -- +// I advise against using this, as it looks bad in every case I've tried + +const int PART_SYS_SLOW_ANIM_BYTE = 0; // slow animation down by a factor of 10 +const int PART_SYS_SLOW_ANIM_BIT = 1; // useful for tweaking anims during debugging + +const int PART_SYS_FOLLOW_VEL_BYTE = 0; // indicates whether to orient sprites towards +const int PART_SYS_FOLLOW_VEL_BIT = 4; // their velocity vector -- default is FALSE + +const int PART_SYS_IS_LIGHT_BYTE = 0; // indicates whether a particular particle system +const int PART_SYS_IS_LIGHT_BIT = 8; // is also a light object -- for andrew +// should deprecate this once there is a general method for setting light properties of objects + +const int PART_SYS_SPAWN_COPY_BYTE = 0; // indicates whether to spawn baby particle systems on +const int PART_SYS_SPAWN_COPY_BIT = 0x10; // particle death -- intended for smoke trails + +const int PART_SYS_COPY_VEL_BYTE = 0; // indicates whether baby particle systems inherit parents vel +const int PART_SYS_COPY_VEL_BIT = 0x20; // (by default they don't) + +const int PART_SYS_INVISIBLE_BYTE = 0; // optional -- turn off display, just simulate +const int PART_SYS_INVISIBLE_BIT = 0x40; // useful for smoke trails + +const int PART_SYS_ADAPT_TO_FRAMERATE_BYTE = 0; // drop sprites from render call proportionally +const int PART_SYS_ADAPT_TO_FRAMERATE_BIT = 0x80; // to how far we are below 60 fps + + +// 26 September 2001 - not even big enough to hold all changes, so should enlarge anyway +//const U16 MAX_PART_SYS_PACKET_SIZE = 180; +const U16 MAX_PART_SYS_PACKET_SIZE = 256; + +//const U8 PART_SYS_K_MASK = 0x01; +const U8 PART_SYS_KILL_P_MASK = 0x02; +const U8 PART_SYS_BOUNCE_P_MASK = 0x04; +const U8 PART_SYS_BOUNCE_B_MASK = 0x08; +//const U8 PART_SYS_POS_RANGES_MASK = 0x10; +//const U8 PART_SYS_VEL_RANGES_MASK = 0x20; +const U8 PART_SYS_VEL_OFFSET_MASK = 0x10; //re-use one of the original slots now commented out +const U8 PART_SYS_ALPHA_SCALE_DIFF_MASK = 0x20; //re-use one of the original slots now commented out +const U8 PART_SYS_SCALE_RANGE_MASK = 0x40; +const U8 PART_SYS_M_IMAGE_UUID_MASK = 0x80; +const U8 PART_SYS_BYTE_3_ALPHA_MASK = 0x01; // wrapped around, didn't we? + +const U8 PART_SYS_BYTE_SPAWN_MASK = 0x01; +const U8 PART_SYS_BYTE_ENVIRONMENT_MASK = 0x02; +const U8 PART_SYS_BYTE_LIFESPAN_MASK = 0x04; +const U8 PART_SYS_BYTE_DECAY_DAMP_MASK = 0x08; +const U8 PART_SYS_BYTE_WIND_DIFF_MASK = 0x10; + + +// 26 September 2001 - new constants for mActionFlags +const int PART_SYS_ACTION_BYTE = 1; +const U8 PART_SYS_SPAWN = 0x01; +const U8 PART_SYS_BOUNCE = 0x02; +const U8 PART_SYS_AFFECTED_BY_WIND = 0x04; +const U8 PART_SYS_AFFECTED_BY_GRAVITY = 0x08; +const U8 PART_SYS_EVALUATE_WIND_PER_PARTICLE = 0x10; +const U8 PART_SYS_DAMP_MOTION = 0x20; +const U8 PART_SYS_WIND_DIFFUSION = 0x40; + +// 26 September 2001 - new constants for mKillFlags +const int PART_SYS_KILL_BYTE = 2; +const U8 PART_SYS_KILL_PLANE = 0x01; +const U8 PART_SYS_GLOBAL_DIE = 0x02; +const U8 PART_SYS_DISTANCE_DEATH = 0x04; +const U8 PART_SYS_TIME_DEATH = 0x08; + + +// global, because the sim-side also calls it in the LLPartInitDataFactory + + +void gSetInitDataDefaults(LLPartInitData *setMe); + +class LLPartSysCompressedPacket +{ +public: + LLPartSysCompressedPacket(); + ~LLPartSysCompressedPacket(); + BOOL fromLLPartInitData(LLPartInitData *in, U32 &bytesUsed); + BOOL toLLPartInitData(LLPartInitData *out, U32 *bytesUsed); + BOOL fromUnsignedBytes(U8 *in, U32 bytesUsed); + BOOL toUnsignedBytes(U8 *out); + U32 bufferSize(); + U8 *getBytePtr(); + +protected: + U8 mData[MAX_PART_SYS_PACKET_SIZE]; + U32 mNumBytes; + LLPartInitData mDefaults; // this is intended to hold default LLPartInitData values + // please do not modify it + LLPartInitData mWorkingCopy; // uncompressed data I'm working with + +protected: + // private functions (used only to break up code) + void writeFlagByte(LLPartInitData *in); + //U32 writeK(LLPartInitData *in, U32 startByte); + U32 writeKill_p(LLPartInitData *in, U32 startByte); + U32 writeBounce_p(LLPartInitData *in, U32 startByte); + U32 writeBounce_b(LLPartInitData *in, U32 startByte); + //U32 writePos_ranges(LLPartInitData *in, U32 startByte); + //U32 writeVel_ranges(LLPartInitData *in, U32 startByte); + U32 writeAlphaScaleDiffEqn_range(LLPartInitData *in, U32 startByte); + U32 writeScale_range(LLPartInitData *in, U32 startByte); + U32 writeAlpha_range(LLPartInitData *in, U32 startByte); + U32 writeUUID(LLPartInitData *in, U32 startByte); + + U32 writeVelocityOffset(LLPartInitData *in, U32 startByte); + U32 writeSpawn(LLPartInitData *in, U32 startByte); //all spawn data + U32 writeEnvironment(LLPartInitData *in, U32 startByte); //wind and gravity + U32 writeLifespan(LLPartInitData *in, U32 startByte); //lifespan data - individual and global + U32 writeDecayDamp(LLPartInitData *in, U32 startByte); //alpha and scale, and motion damp + U32 writeWindDiffusionFactor(LLPartInitData *in, U32 startByte); + + + //U32 readK(LLPartInitData *in, U32 startByte); + U32 readKill_p(LLPartInitData *in, U32 startByte); + U32 readBounce_p(LLPartInitData *in, U32 startByte); + U32 readBounce_b(LLPartInitData *in, U32 startByte); + //U32 readPos_ranges(LLPartInitData *in, U32 startByte); + //U32 readVel_ranges(LLPartInitData *in, U32 startByte); + U32 readAlphaScaleDiffEqn_range(LLPartInitData *in, U32 startByte); + U32 readScale_range(LLPartInitData *in, U32 startByte); + U32 readAlpha_range(LLPartInitData *in, U32 startByte); + U32 readUUID(LLPartInitData *in, U32 startByte); + + U32 readVelocityOffset(LLPartInitData *in, U32 startByte); + U32 readSpawn(LLPartInitData *in, U32 startByte); //all spawn data + U32 readEnvironment(LLPartInitData *in, U32 startByte); //wind and gravity + U32 readLifespan(LLPartInitData *in, U32 startByte); //lifespan data - individual and global + U32 readDecayDamp(LLPartInitData *in, U32 startByte); //alpha and scale, and motion damp + U32 readWindDiffusionFactor(LLPartInitData *in, U32 startByte); +}; + +#endif + diff --git a/indra/llmessage/patch_code.cpp b/indra/llmessage/patch_code.cpp new file mode 100644 index 0000000000..c8ebac53e7 --- /dev/null +++ b/indra/llmessage/patch_code.cpp @@ -0,0 +1,390 @@ +/** + * @file patch_code.cpp + * @brief Encode patch DCT data into bitcode. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llmath.h" +//#include "vmath.h" +#include "v3math.h" +#include "patch_dct.h" +#include "patch_code.h" +#include "bitpack.h" + +U32 gPatchSize, gWordBits; + +void init_patch_coding(LLBitPack &bitpack) +{ + bitpack.resetBitPacking(); +} + +void code_patch_group_header(LLBitPack &bitpack, LLGroupHeader *gopp) +{ +#ifdef LL_BIG_ENDIAN + U8 *stride = (U8 *)&gopp->stride; + bitpack.bitPack(&(stride[1]), 8); + bitpack.bitPack(&(stride[0]), 8); +#else + bitpack.bitPack((U8 *)&gopp->stride, 16); +#endif + bitpack.bitPack((U8 *)&gopp->patch_size, 8); + bitpack.bitPack((U8 *)&gopp->layer_type, 8); + + gPatchSize = gopp->patch_size; +} + +void code_patch_header(LLBitPack &bitpack, LLPatchHeader *ph, S32 *patch) +{ + S32 i, j, temp, patch_size = gPatchSize, wbits = (ph->quant_wbits & 0xf) + 2; + U32 max_wbits = wbits + 5, min_wbits = wbits>>1; + + wbits = min_wbits; + + for (i = 0; i < (int) patch_size*patch_size; i++) + { + temp = patch[i]; + if (temp) + { + if (temp < 0) + temp *= -1; + for (j = max_wbits; j > (int) min_wbits; j--) + { + if (temp & (1< wbits) + wbits = j; + break; + } + } + } + } + + wbits += 1; + + ph->quant_wbits &= 0xf0; + + if ( (wbits > 17) + ||(wbits < 2)) + { + llerrs << "Bits needed per word in code_patch_header out of legal range. Adjust compression quatization." << llendl; + } + + ph->quant_wbits |= (wbits - 2); + + bitpack.bitPack((U8 *)&ph->quant_wbits, 8); +#ifdef LL_BIG_ENDIAN + U8 *offset = (U8 *)&ph->dc_offset; + bitpack.bitPack(&(offset[3]), 8); + bitpack.bitPack(&(offset[2]), 8); + bitpack.bitPack(&(offset[1]), 8); + bitpack.bitPack(&(offset[0]), 8); +#else + bitpack.bitPack((U8 *)&ph->dc_offset, 32); +#endif +#ifdef LL_BIG_ENDIAN + U8 *range = (U8 *)&ph->range; + bitpack.bitPack(&(range[1]), 8); + bitpack.bitPack(&(range[0]), 8); +#else + bitpack.bitPack((U8 *)&ph->range, 16); +#endif +#ifdef LL_BIG_ENDIAN + U8 *ids = (U8 *)&ph->patchids; + bitpack.bitPack(&(ids[1]), 8); + bitpack.bitPack(&(ids[0]), 2); +#else + bitpack.bitPack((U8 *)&ph->patchids, 10); +#endif + + gWordBits = wbits; +} + +void code_end_of_data(LLBitPack &bitpack) +{ + bitpack.bitPack((U8 *)&END_OF_PATCHES, 8); +} + +void code_patch(LLBitPack &bitpack, S32 *patch, S32 postquant) +{ + S32 i, j, patch_size = gPatchSize, wbits = gWordBits; + S32 temp; + BOOL b_eob; + + if ( (postquant > patch_size*patch_size) + ||(postquant < 0)) + { + llerrs << "Bad postquant in code_patch!" << llendl; + } + + if (postquant) + patch[patch_size*patch_size - postquant] = 0; + + for (i = 0; i < patch_size*patch_size; i++) + { + b_eob = FALSE; + temp = patch[i]; + if (!temp) + { + b_eob = TRUE; + for (j = i; j < patch_size*patch_size - postquant; j++) + { + if (patch[j]) + { + b_eob = FALSE; + break; + } + } + if (b_eob) + { + bitpack.bitPack((U8 *)&ZERO_EOB, 2); + return; + } + else + { + bitpack.bitPack((U8 *)&ZERO_CODE, 1); + } + } + else + { + if (temp < 0) + { + temp *= -1; + if (temp > (1< (1<stride = retvalu16; + + U8 retvalu8 = 0; + bitpack.bitUnpack(&retvalu8, 8); + gopp->patch_size = retvalu8; + + retvalu8 = 0; + bitpack.bitUnpack(&retvalu8, 8); + gopp->layer_type = retvalu8; + + gPatchSize = gopp->patch_size; +} + +void decode_patch_header(LLBitPack &bitpack, LLPatchHeader *ph) +{ + U8 retvalu8; + + retvalu8 = 0; + bitpack.bitUnpack(&retvalu8, 8); + ph->quant_wbits = retvalu8; + + if (END_OF_PATCHES == ph->quant_wbits) + { + // End of data, blitz the rest. + ph->dc_offset = 0; + ph->range = 0; + ph->patchids = 0; + return; + } + + U32 retvalu32 = 0; +#ifdef LL_BIG_ENDIAN + U8 *ret = (U8 *)&retvalu32; + bitpack.bitUnpack(&(ret[3]), 8); + bitpack.bitUnpack(&(ret[2]), 8); + bitpack.bitUnpack(&(ret[1]), 8); + bitpack.bitUnpack(&(ret[0]), 8); +#else + bitpack.bitUnpack((U8 *)&retvalu32, 32); +#endif + ph->dc_offset = *(F32 *)&retvalu32; + + U16 retvalu16 = 0; +#ifdef LL_BIG_ENDIAN + ret = (U8 *)&retvalu16; + bitpack.bitUnpack(&(ret[1]), 8); + bitpack.bitUnpack(&(ret[0]), 8); +#else + bitpack.bitUnpack((U8 *)&retvalu16, 16); +#endif + ph->range = retvalu16; + + retvalu16 = 0; +#ifdef LL_BIG_ENDIAN + ret = (U8 *)&retvalu16; + bitpack.bitUnpack(&(ret[1]), 8); + bitpack.bitUnpack(&(ret[0]), 2); +#else + bitpack.bitUnpack((U8 *)&retvalu16, 10); +#endif + ph->patchids = retvalu16; + + gWordBits = (ph->quant_wbits & 0xf) + 2; +} + +void decode_patch(LLBitPack &bitpack, S32 *patches) +{ +#ifdef LL_BIG_ENDIAN + S32 i, j, patch_size = gPatchSize, wbits = gWordBits; + U8 tempu8; + U16 tempu16; + U32 tempu32; + for (i = 0; i < patch_size*patch_size; i++) + { + bitpack.bitUnpack((U8 *)&tempu8, 1); + if (tempu8) + { + // either 0 EOB or Value + bitpack.bitUnpack((U8 *)&tempu8, 1); + if (tempu8) + { + // value + bitpack.bitUnpack((U8 *)&tempu8, 1); + if (tempu8) + { + // negative + patches[i] = -1; + } + else + { + // positive + patches[i] = 1; + } + if (wbits <= 8) + { + bitpack.bitUnpack((U8 *)&tempu8, wbits); + patches[i] *= tempu8; + } + else if (wbits <= 16) + { + tempu16 = 0; + U8 *ret = (U8 *)&tempu16; + bitpack.bitUnpack(&(ret[1]), 8); + bitpack.bitUnpack(&(ret[0]), wbits - 8); + patches[i] *= tempu16; + } + else if (wbits <= 24) + { + tempu32 = 0; + U8 *ret = (U8 *)&tempu32; + bitpack.bitUnpack(&(ret[2]), 8); + bitpack.bitUnpack(&(ret[1]), 8); + bitpack.bitUnpack(&(ret[0]), wbits - 16); + patches[i] *= tempu32; + } + else if (wbits <= 32) + { + tempu32 = 0; + U8 *ret = (U8 *)&tempu32; + bitpack.bitUnpack(&(ret[3]), 8); + bitpack.bitUnpack(&(ret[2]), 8); + bitpack.bitUnpack(&(ret[1]), 8); + bitpack.bitUnpack(&(ret[0]), wbits - 24); + patches[i] *= tempu32; + } + } + else + { + for (j = i; j < patch_size*patch_size; j++) + { + patches[j] = 0; + } + return; + } + } + else + { + patches[i] = 0; + } + } +#else + S32 i, j, patch_size = gPatchSize, wbits = gWordBits; + U32 temp; + for (i = 0; i < patch_size*patch_size; i++) + { + temp = 0; + bitpack.bitUnpack((U8 *)&temp, 1); + if (temp) + { + // either 0 EOB or Value + temp = 0; + bitpack.bitUnpack((U8 *)&temp, 1); + if (temp) + { + // value + temp = 0; + bitpack.bitUnpack((U8 *)&temp, 1); + if (temp) + { + // negative + temp = 0; + bitpack.bitUnpack((U8 *)&temp, wbits); + patches[i] = temp; + patches[i] *= -1; + } + else + { + // positive + temp = 0; + bitpack.bitUnpack((U8 *)&temp, wbits); + patches[i] = temp; + } + } + else + { + for (j = i; j < patch_size*patch_size; j++) + { + patches[j] = 0; + } + return; + } + } + else + { + patches[i] = 0; + } + } +#endif +} + diff --git a/indra/llmessage/patch_code.h b/indra/llmessage/patch_code.h new file mode 100644 index 0000000000..a18736df11 --- /dev/null +++ b/indra/llmessage/patch_code.h @@ -0,0 +1,28 @@ +/** + * @file patch_code.h + * @brief Function declarations for encoding and decoding patches. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_PATCH_CODE_H +#define LL_PATCH_CODE_H + +class LLBitPack; +class LLGroupHeader; +class LLPatchHeader; + +void init_patch_coding(LLBitPack &bitpack); +void code_patch_group_header(LLBitPack &bitpack, LLGroupHeader *gopp); +void code_patch_header(LLBitPack &bitpack, LLPatchHeader *ph, S32 *patch); +void code_end_of_data(LLBitPack &bitpack); +void code_patch(LLBitPack &bitpack, S32 *patch, S32 postquant); +void end_patch_coding(LLBitPack &bitpack); + +void init_patch_decoding(LLBitPack &bitpack); +void decode_patch_group_header(LLBitPack &bitpack, LLGroupHeader *gopp); +void decode_patch_header(LLBitPack &bitpack, LLPatchHeader *ph); +void decode_patch(LLBitPack &bitpack, S32 *patches); + +#endif diff --git a/indra/llmessage/patch_dct.cpp b/indra/llmessage/patch_dct.cpp new file mode 100644 index 0000000000..8d6969a2cd --- /dev/null +++ b/indra/llmessage/patch_dct.cpp @@ -0,0 +1,751 @@ +/** + * @file patch_dct.cpp + * @brief DCT patch. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llmath.h" +//#include "vmath.h" +#include "v3math.h" +#include "patch_dct.h" + +typedef struct s_patch_compress_global_data +{ + S32 patch_size; + S32 patch_stride; + U32 charptr; + S32 layer_type; +} PCGD; + +PCGD gPatchCompressGlobalData; + +void reset_patch_compressor(void) +{ + PCGD *pcp = &gPatchCompressGlobalData; + + pcp->charptr = 0; +} + +S32 gCurrentSize = 0; + +F32 gPatchQuantizeTable[LARGE_PATCH_SIZE*LARGE_PATCH_SIZE]; + +void build_patch_quantize_table(S32 size) +{ + S32 i, j; + for (j = 0; j < size; j++) + { + for (i = 0; i < size; i++) + { + gPatchQuantizeTable[j*size + i] = 1.f/(1.f + 2.f*(i+j)); + } + } +} + +F32 gPatchCosines[LARGE_PATCH_SIZE*LARGE_PATCH_SIZE]; + +void setup_patch_cosines(S32 size) +{ + S32 n, u; + F32 oosob = F_PI*0.5f/size; + + for (u = 0; u < size; u++) + { + for (n = 0; n < size; n++) + { + gPatchCosines[u*size+n] = cosf((2.f*n+1.f)*u*oosob); + } + } +} + +S32 gCopyMatrix[LARGE_PATCH_SIZE*LARGE_PATCH_SIZE]; + +void build_copy_matrix(S32 size) +{ + S32 i, j, count; + BOOL b_diag = FALSE; + BOOL b_right = TRUE; + + i = 0; + j = 0; + count = 0; + + while ( (i < size) + &&(j < size)) + { + gCopyMatrix[j*size + i] = count; + + count++; + + if (!b_diag) + { + if (b_right) + { + if (i < size - 1) + i++; + else + j++; + b_right = FALSE; + b_diag = TRUE; + } + else + { + if (j < size - 1) + j++; + else + i++; + b_right = TRUE; + b_diag = TRUE; + } + } + else + { + if (b_right) + { + i++; + j--; + if ( (i == size - 1) + ||(j == 0)) + { + b_diag = FALSE; + } + } + else + { + i--; + j++; + if ( (i == 0) + ||(j == size - 1)) + { + b_diag = FALSE; + } + } + } + } +} + + +void init_patch_compressor(S32 patch_size, S32 patch_stride, S32 layer_type) +{ + PCGD *pcp = &gPatchCompressGlobalData; + + pcp->charptr = 0; + + pcp->patch_size = patch_size; + pcp->patch_stride = patch_stride; + pcp->layer_type = layer_type; + + if (patch_size != gCurrentSize) + { + gCurrentSize = patch_size; + build_patch_quantize_table(patch_size); + setup_patch_cosines(patch_size); + build_copy_matrix(patch_size); + } +} + +void prescan_patch(F32 *patch, LLPatchHeader *php, F32 &zmax, F32 &zmin) +{ + S32 i, j; + PCGD *pcp = &gPatchCompressGlobalData; + S32 stride = pcp->patch_stride; + S32 size = pcp->patch_size; + S32 jstride; + + zmax = -99999999.f; + zmin = 99999999.f; + + for (j = 0; j < size; j++) + { + jstride = j*stride; + for (i = 0; i < size; i++) + { + if (*(patch + jstride + i) > zmax) + { + zmax = *(patch + jstride + i); + } + if (*(patch + jstride + i) < zmin) + { + zmin = *(patch + jstride + i); + } + } + } + + php->dc_offset = zmin; + php->range = (U16) ((zmax - zmin) + 1.f); +} + +void dct_line(F32 *linein, F32 *lineout, S32 line) +{ + S32 u; + F32 total; + F32 *pcp = gPatchCosines; + S32 line_size = line*NORMAL_PATCH_SIZE; + +#ifdef _PATCH_SIZE_16_AND_32_ONLY + F32 *tlinein, *tpcp; + + tlinein = linein + line_size; + + total = *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein); + + *(lineout + line_size) = OO_SQRT2*total; + + for (u = 1; u < NORMAL_PATCH_SIZE; u++) + { + tlinein = linein + line_size; + tpcp = pcp + (u<<4); + + total = *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein)*(*tpcp); + + *(lineout + line_size + u) = total; + } +#else + S32 n; + S32 size = gPatchCompressGlobalData.patch_size; + total = 0.f; + for (n = 0; n < size; n++) + { + total += linein[line_size + n]; + } + lineout[line_size] = OO_SQRT2*total; + + for (u = 1; u < size; u++) + { + total = 0.f; + for (n = 0; n < size; n++) + { + total += linein[line_size + n]*pcp[u*size+n]; + } + lineout[line_size + u] = total; + } +#endif +} + +void dct_line_large(F32 *linein, F32 *lineout, S32 line) +{ + S32 u; + F32 total; + F32 *pcp = gPatchCosines; + S32 line_size = line*LARGE_PATCH_SIZE; + + F32 *tlinein, *tpcp; + + tlinein = linein + line_size; + + total = *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein++); + total += *(tlinein); + + *(lineout + line_size) = OO_SQRT2*total; + + for (u = 1; u < LARGE_PATCH_SIZE; u++) + { + tlinein = linein + line_size; + tpcp = pcp + (u<<5); + + total = *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein++)*(*(tpcp++)); + total += *(tlinein)*(*tpcp); + + *(lineout + line_size + u) = total; + } +} + +inline void dct_column(F32 *linein, S32 *lineout, S32 column) +{ + S32 u; + F32 total; + F32 oosob = 2.f/16.f; + F32 *pcp = gPatchCosines; + S32 *copy_matrix = gCopyMatrix; + F32 *qt = gPatchQuantizeTable; + +#ifdef _PATCH_SIZE_16_AND_32_ONLY + F32 *tlinein, *tpcp; + S32 sizeu; + + tlinein = linein + column; + + total = *(tlinein); + total += *(tlinein += NORMAL_PATCH_SIZE); + total += *(tlinein += NORMAL_PATCH_SIZE); + total += *(tlinein += NORMAL_PATCH_SIZE); + + total += *(tlinein += NORMAL_PATCH_SIZE); + total += *(tlinein += NORMAL_PATCH_SIZE); + total += *(tlinein += NORMAL_PATCH_SIZE); + total += *(tlinein += NORMAL_PATCH_SIZE); + + total += *(tlinein += NORMAL_PATCH_SIZE); + total += *(tlinein += NORMAL_PATCH_SIZE); + total += *(tlinein += NORMAL_PATCH_SIZE); + total += *(tlinein += NORMAL_PATCH_SIZE); + + total += *(tlinein += NORMAL_PATCH_SIZE); + total += *(tlinein += NORMAL_PATCH_SIZE); + total += *(tlinein += NORMAL_PATCH_SIZE); + total += *(tlinein += NORMAL_PATCH_SIZE); + + *(lineout + *(copy_matrix + column)) = (S32)(OO_SQRT2*total*oosob*(*(qt + column))); + + for (u = 1; u < NORMAL_PATCH_SIZE; u++) + { + tlinein = linein + column; + tpcp = pcp + (u<<4); + + total = *(tlinein)*(*(tpcp++)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp++)); + + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp++)); + + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp++)); + + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp)); + + sizeu = NORMAL_PATCH_SIZE*u + column; + + *(lineout + *(copy_matrix + sizeu)) = (S32)(total*oosob*(*(qt+sizeu))); + } +#else + S32 size = gPatchCompressGlobalData.patch_size; + F32 oosob = 2.f/size; + S32 n; + total = 0.f; + for (n = 0; n < size; n++) + { + total += linein[size*n + column]; + } + lineout[copy_matrix[column]] = OO_SQRT2*total*oosob*qt[column]; + + for (u = 1; u < size; u++) + { + total = 0.f; + for (n = 0; n < size; n++) + { + total += linein[size*n + column]*pcp[u*size+n]; + } + lineout[copy_matrix[size*u + column]] = total*oosob*qt[size*u + column]; + } +#endif +} + +inline void dct_column_large(F32 *linein, S32 *lineout, S32 column) +{ + S32 u; + F32 total; + F32 oosob = 2.f/32.f; + F32 *pcp = gPatchCosines; + S32 *copy_matrix = gCopyMatrix; + F32 *qt = gPatchQuantizeTable; + + F32 *tlinein, *tpcp; + S32 sizeu; + + tlinein = linein + column; + + total = *(tlinein); + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + total += *(tlinein += LARGE_PATCH_SIZE); + + *(lineout + *(copy_matrix + column)) = (S32)(OO_SQRT2*total*oosob*(*(qt + column))); + + for (u = 1; u < LARGE_PATCH_SIZE; u++) + { + tlinein = linein + column; + tpcp = pcp + (u<<5); + + total = *(tlinein)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp++)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp)); + + sizeu = LARGE_PATCH_SIZE*u + column; + + *(lineout + *(copy_matrix + sizeu)) = (S32)(total*oosob*(*(qt+sizeu))); + } +} + +inline void dct_patch(F32 *block, S32 *cpatch) +{ + F32 temp[NORMAL_PATCH_SIZE*NORMAL_PATCH_SIZE]; + +#ifdef _PATCH_SIZE_16_AND_32_ONLY + dct_line(block, temp, 0); + dct_line(block, temp, 1); + dct_line(block, temp, 2); + dct_line(block, temp, 3); + + dct_line(block, temp, 4); + dct_line(block, temp, 5); + dct_line(block, temp, 6); + dct_line(block, temp, 7); + + dct_line(block, temp, 8); + dct_line(block, temp, 9); + dct_line(block, temp, 10); + dct_line(block, temp, 11); + + dct_line(block, temp, 12); + dct_line(block, temp, 13); + dct_line(block, temp, 14); + dct_line(block, temp, 15); + + dct_column(temp, cpatch, 0); + dct_column(temp, cpatch, 1); + dct_column(temp, cpatch, 2); + dct_column(temp, cpatch, 3); + + dct_column(temp, cpatch, 4); + dct_column(temp, cpatch, 5); + dct_column(temp, cpatch, 6); + dct_column(temp, cpatch, 7); + + dct_column(temp, cpatch, 8); + dct_column(temp, cpatch, 9); + dct_column(temp, cpatch, 10); + dct_column(temp, cpatch, 11); + + dct_column(temp, cpatch, 12); + dct_column(temp, cpatch, 13); + dct_column(temp, cpatch, 14); + dct_column(temp, cpatch, 15); +#else + S32 i; + S32 size = gPatchCompressGlobalData.patch_size; + for (i = 0; i < size; i++) + { + dct_line(block, temp, i); + } + for (i = 0; i < size; i++) + { + dct_column(temp, cpatch, i); + } +#endif +} + +inline void dct_patch_large(F32 *block, S32 *cpatch) +{ + F32 temp[LARGE_PATCH_SIZE*LARGE_PATCH_SIZE]; + + dct_line_large(block, temp, 0); + dct_line_large(block, temp, 1); + dct_line_large(block, temp, 2); + dct_line_large(block, temp, 3); + + dct_line_large(block, temp, 4); + dct_line_large(block, temp, 5); + dct_line_large(block, temp, 6); + dct_line_large(block, temp, 7); + + dct_line_large(block, temp, 8); + dct_line_large(block, temp, 9); + dct_line_large(block, temp, 10); + dct_line_large(block, temp, 11); + + dct_line_large(block, temp, 12); + dct_line_large(block, temp, 13); + dct_line_large(block, temp, 14); + dct_line_large(block, temp, 15); + + dct_line_large(block, temp, 16); + dct_line_large(block, temp, 17); + dct_line_large(block, temp, 18); + dct_line_large(block, temp, 19); + + dct_line_large(block, temp, 20); + dct_line_large(block, temp, 21); + dct_line_large(block, temp, 22); + dct_line_large(block, temp, 23); + + dct_line_large(block, temp, 24); + dct_line_large(block, temp, 25); + dct_line_large(block, temp, 26); + dct_line_large(block, temp, 27); + + dct_line_large(block, temp, 28); + dct_line_large(block, temp, 29); + dct_line_large(block, temp, 30); + dct_line_large(block, temp, 31); + + dct_column_large(temp, cpatch, 0); + dct_column_large(temp, cpatch, 1); + dct_column_large(temp, cpatch, 2); + dct_column_large(temp, cpatch, 3); + + dct_column_large(temp, cpatch, 4); + dct_column_large(temp, cpatch, 5); + dct_column_large(temp, cpatch, 6); + dct_column_large(temp, cpatch, 7); + + dct_column_large(temp, cpatch, 8); + dct_column_large(temp, cpatch, 9); + dct_column_large(temp, cpatch, 10); + dct_column_large(temp, cpatch, 11); + + dct_column_large(temp, cpatch, 12); + dct_column_large(temp, cpatch, 13); + dct_column_large(temp, cpatch, 14); + dct_column_large(temp, cpatch, 15); + + dct_column_large(temp, cpatch, 16); + dct_column_large(temp, cpatch, 17); + dct_column_large(temp, cpatch, 18); + dct_column_large(temp, cpatch, 19); + + dct_column_large(temp, cpatch, 20); + dct_column_large(temp, cpatch, 21); + dct_column_large(temp, cpatch, 22); + dct_column_large(temp, cpatch, 23); + + dct_column_large(temp, cpatch, 24); + dct_column_large(temp, cpatch, 25); + dct_column_large(temp, cpatch, 26); + dct_column_large(temp, cpatch, 27); + + dct_column_large(temp, cpatch, 28); + dct_column_large(temp, cpatch, 29); + dct_column_large(temp, cpatch, 30); + dct_column_large(temp, cpatch, 31); +} + +void compress_patch(F32 *patch, S32 *cpatch, LLPatchHeader *php, S32 prequant) +{ + S32 i, j; + PCGD *pcp = &gPatchCompressGlobalData; + S32 stride = pcp->patch_stride; + S32 size = pcp->patch_size; + F32 block[LARGE_PATCH_SIZE*LARGE_PATCH_SIZE], *tblock; + F32 *tpatch; + + S32 wordsize = prequant; + F32 oozrange = 1.f/php->range; + + F32 dc = php->dc_offset; + + S32 range = (1<quant_wbits = wordsize - 2; + php->quant_wbits |= (prequant - 2)<<4; + + for (j = 0; j < size; j++) + { + tblock = block + j*size; + tpatch = patch + j*stride; + for (i = 0; i < size; i++) + { +// block[j*size + i] = (patch[j*stride + i] - dc)*premult - sub; + *(tblock++) = *(tpatch++)*premult - sub; + } + } + + if (size == 16) + dct_patch(block, cpatch); + else + dct_patch_large(block, cpatch); +} + +void get_patch_group_header(LLGroupHeader *gopp) +{ + PCGD *pcp = &gPatchCompressGlobalData; + gopp->stride = pcp->patch_stride; + gopp->patch_size = pcp->patch_size; + gopp->layer_type = pcp->layer_type; +} diff --git a/indra/llmessage/patch_dct.h b/indra/llmessage/patch_dct.h new file mode 100644 index 0000000000..d5c0d067fc --- /dev/null +++ b/indra/llmessage/patch_dct.h @@ -0,0 +1,73 @@ +/** + * @file patch_dct.h + * @brief Function declarations for DCT and IDCT routines + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_PATCH_DCT_H +#define LL_PATCH_DCT_H + +class LLVector3; + +// Code Values +const U8 ZERO_CODE = 0x0; +const U8 ZERO_EOB = 0x2; +const U8 POSITIVE_VALUE = 0x6; +const U8 NEGATIVE_VALUE = 0x7; + +const S8 NORMAL_PATCH_SIZE = 16; +const S8 LARGE_PATCH_SIZE = 32; + +const U8 END_OF_PATCHES = 97; + +#define _PATCH_SIZE_16_AND_32_ONLY + +// Top level header for group of headers +//typedef struct LL_Group_Header +//{ +// U16 stride; // 2 = 2 +// U8 patch_size; // 1 = 3 +// U8 layer_type; // 1 = 4 +//} LLGroupHeader; + +class LLGroupHeader +{ +public: + U16 stride; // 2 = 2 + U8 patch_size; // 1 = 3 + U8 layer_type; // 1 = 4 +}; + +// Individual patch header + +//typedef struct LL_Patch_Header +//{ +// F32 dc_offset; // 4 bytes +// U16 range; // 2 = 7 ((S16) FP range (breaks if we need > 32K meters in 1 patch) +// U8 quant_wbits; // 1 = 8 (upper 4 bits is quant - 2, lower 4 bits is word bits - 2) +// U16 patchids; // 2 = 10 (actually only uses 10 bits, 5 for each) +//} LLPatchHeader; +class LLPatchHeader +{ +public: + F32 dc_offset; // 4 bytes + U16 range; // 2 = 7 ((S16) FP range (breaks if we need > 32K meters in 1 patch) + U8 quant_wbits; // 1 = 8 (upper 4 bits is quant - 2, lower 4 bits is word bits - 2) + U16 patchids; // 2 = 10 (actually only uses 10 bits, 5 for each) +}; + +// Compression routines +void init_patch_compressor(S32 patch_size, S32 patch_stride, S32 layer_type); +void prescan_patch(F32 *patch, LLPatchHeader *php, F32 &zmax, F32 &zmin); +void compress_patch(F32 *patch, S32 *cpatch, LLPatchHeader *php, S32 prequant); +void get_patch_group_header(LLGroupHeader *gopp); + +// Decompression routines +void set_group_of_patch_header(LLGroupHeader *gopp); +void init_patch_decompressor(S32 size); +void decompress_patch(F32 *patch, S32 *cpatch, LLPatchHeader *ph); +void decompress_patchv(LLVector3 *v, S32 *cpatch, LLPatchHeader *ph); + +#endif diff --git a/indra/llmessage/patch_idct.cpp b/indra/llmessage/patch_idct.cpp new file mode 100644 index 0000000000..cfc52c551d --- /dev/null +++ b/indra/llmessage/patch_idct.cpp @@ -0,0 +1,666 @@ +/** + * @file patch_idct.cpp + * @brief IDCT patch. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llmath.h" +//#include "vmath.h" +#include "v3math.h" +#include "patch_dct.h" + +LLGroupHeader *gGOPP; + +void set_group_of_patch_header(LLGroupHeader *gopp) +{ + gGOPP = gopp; +} + +F32 gPatchDequantizeTable[LARGE_PATCH_SIZE*LARGE_PATCH_SIZE]; +void build_patch_dequantize_table(S32 size) +{ + S32 i, j; + for (j = 0; j < size; j++) + { + for (i = 0; i < size; i++) + { + gPatchDequantizeTable[j*size + i] = (1.f + 2.f*(i+j)); + } + } +} + +S32 gCurrentDeSize = 0; + +F32 gPatchICosines[LARGE_PATCH_SIZE*LARGE_PATCH_SIZE]; + +void setup_patch_icosines(S32 size) +{ + S32 n, u; + F32 oosob = F_PI*0.5f/size; + + for (u = 0; u < size; u++) + { + for (n = 0; n < size; n++) + { + gPatchICosines[u*size+n] = cosf((2.f*n+1.f)*u*oosob); + } + } +} + +S32 gDeCopyMatrix[LARGE_PATCH_SIZE*LARGE_PATCH_SIZE]; + +void build_decopy_matrix(S32 size) +{ + S32 i, j, count; + BOOL b_diag = FALSE; + BOOL b_right = TRUE; + + i = 0; + j = 0; + count = 0; + + while ( (i < size) + &&(j < size)) + { + gDeCopyMatrix[j*size + i] = count; + + count++; + + if (!b_diag) + { + if (b_right) + { + if (i < size - 1) + i++; + else + j++; + b_right = FALSE; + b_diag = TRUE; + } + else + { + if (j < size - 1) + j++; + else + i++; + b_right = TRUE; + b_diag = TRUE; + } + } + else + { + if (b_right) + { + i++; + j--; + if ( (i == size - 1) + ||(j == 0)) + { + b_diag = FALSE; + } + } + else + { + i--; + j++; + if ( (i == 0) + ||(j == size - 1)) + { + b_diag = FALSE; + } + } + } + } +} + +void init_patch_decompressor(S32 size) +{ + if (size != gCurrentDeSize) + { + gCurrentDeSize = size; + build_patch_dequantize_table(size); + setup_patch_icosines(size); + build_decopy_matrix(size); + } +} + +inline void idct_line(F32 *linein, F32 *lineout, S32 line) +{ + S32 n; + F32 total; + F32 *pcp = gPatchICosines; + +#ifdef _PATCH_SIZE_16_AND_32_ONLY + F32 oosob = 2.f/16.f; + S32 line_size = line*NORMAL_PATCH_SIZE; + F32 *tlinein, *tpcp; + + + for (n = 0; n < NORMAL_PATCH_SIZE; n++) + { + tpcp = pcp + n; + tlinein = linein + line_size; + + total = OO_SQRT2*(*(tlinein++)); + total += *(tlinein++)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += NORMAL_PATCH_SIZE)); + + total += *(tlinein++)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += NORMAL_PATCH_SIZE)); + + total += *(tlinein++)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += NORMAL_PATCH_SIZE)); + + total += *(tlinein++)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein)*(*(tpcp += NORMAL_PATCH_SIZE)); + + *(lineout + line_size + n) = total*oosob; + } +#else + F32 oosob = 2.f/size; + S32 size = gGOPP->patch_size; + S32 line_size = line*size; + S32 u; + for (n = 0; n < size; n++) + { + total = OO_SQRT2*linein[line_size]; + for (u = 1; u < size; u++) + { + total += linein[line_size + u]*pcp[u*size+n]; + } + lineout[line_size + n] = total*oosob; + } +#endif +} + +inline void idct_line_large_slow(F32 *linein, F32 *lineout, S32 line) +{ + S32 n; + F32 total; + F32 *pcp = gPatchICosines; + + F32 oosob = 2.f/32.f; + S32 line_size = line*LARGE_PATCH_SIZE; + F32 *tlinein, *tpcp; + + + for (n = 0; n < LARGE_PATCH_SIZE; n++) + { + tpcp = pcp + n; + tlinein = linein + line_size; + + total = OO_SQRT2*(*(tlinein++)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein)*(*(tpcp += LARGE_PATCH_SIZE)); + + *(lineout + line_size + n) = total*oosob; + } +} + +// Nota Bene: assumes that coefficients beyond 128 are 0! + +void idct_line_large(F32 *linein, F32 *lineout, S32 line) +{ + S32 n; + F32 total; + F32 *pcp = gPatchICosines; + + F32 oosob = 2.f/32.f; + S32 line_size = line*LARGE_PATCH_SIZE; + F32 *tlinein, *tpcp; + F32 *baselinein = linein + line_size; + F32 *baselineout = lineout + line_size; + + + for (n = 0; n < LARGE_PATCH_SIZE; n++) + { + tpcp = pcp++; + tlinein = baselinein; + + total = OO_SQRT2*(*(tlinein++)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein++)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein)*(*(tpcp)); + + *baselineout++ = total*oosob; + } +} + +inline void idct_column(F32 *linein, F32 *lineout, S32 column) +{ + S32 n; + F32 total; + F32 *pcp = gPatchICosines; + +#ifdef _PATCH_SIZE_16_AND_32_ONLY + F32 *tlinein, *tpcp; + + for (n = 0; n < NORMAL_PATCH_SIZE; n++) + { + tpcp = pcp + n; + tlinein = linein + column; + + total = OO_SQRT2*(*tlinein); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp += NORMAL_PATCH_SIZE)); + + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp += NORMAL_PATCH_SIZE)); + + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp += NORMAL_PATCH_SIZE)); + + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp += NORMAL_PATCH_SIZE)); + total += *(tlinein += NORMAL_PATCH_SIZE)*(*(tpcp += NORMAL_PATCH_SIZE)); + + *(lineout + (n<<4) + column) = total; + } + +#else + S32 size = gGOPP->patch_size; + S32 u; + S32 u_size; + + for (n = 0; n < size; n++) + { + total = OO_SQRT2*linein[column]; + for (u = 1; u < size; u++) + { + u_size = u*size; + total += linein[u_size + column]*pcp[u_size+n]; + } + lineout[size*n + column] = total; + } +#endif +} + +inline void idct_column_large_slow(F32 *linein, F32 *lineout, S32 column) +{ + S32 n; + F32 total; + F32 *pcp = gPatchICosines; + + F32 *tlinein, *tpcp; + + for (n = 0; n < LARGE_PATCH_SIZE; n++) + { + tpcp = pcp + n; + tlinein = linein + column; + + total = OO_SQRT2*(*tlinein); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + + *(lineout + (n<<5) + column) = total; + } +} + +// Nota Bene: assumes that coefficients beyond 128 are 0! + +void idct_column_large(F32 *linein, F32 *lineout, S32 column) +{ + S32 n, m; + F32 total; + F32 *pcp = gPatchICosines; + + F32 *tlinein, *tpcp; + F32 *baselinein = linein + column; + F32 *baselineout = lineout + column; + + for (n = 0; n < LARGE_PATCH_SIZE; n++) + { + tpcp = pcp++; + tlinein = baselinein; + + total = OO_SQRT2*(*tlinein); + for (m = 1; m < NORMAL_PATCH_SIZE; m++) + total += *(tlinein += LARGE_PATCH_SIZE)*(*(tpcp += LARGE_PATCH_SIZE)); + + *(baselineout + (n<<5)) = total; + } +} + +inline void idct_patch(F32 *block) +{ + F32 temp[LARGE_PATCH_SIZE*LARGE_PATCH_SIZE]; + +#ifdef _PATCH_SIZE_16_AND_32_ONLY + idct_column(block, temp, 0); + idct_column(block, temp, 1); + idct_column(block, temp, 2); + idct_column(block, temp, 3); + + idct_column(block, temp, 4); + idct_column(block, temp, 5); + idct_column(block, temp, 6); + idct_column(block, temp, 7); + + idct_column(block, temp, 8); + idct_column(block, temp, 9); + idct_column(block, temp, 10); + idct_column(block, temp, 11); + + idct_column(block, temp, 12); + idct_column(block, temp, 13); + idct_column(block, temp, 14); + idct_column(block, temp, 15); + + idct_line(temp, block, 0); + idct_line(temp, block, 1); + idct_line(temp, block, 2); + idct_line(temp, block, 3); + + idct_line(temp, block, 4); + idct_line(temp, block, 5); + idct_line(temp, block, 6); + idct_line(temp, block, 7); + + idct_line(temp, block, 8); + idct_line(temp, block, 9); + idct_line(temp, block, 10); + idct_line(temp, block, 11); + + idct_line(temp, block, 12); + idct_line(temp, block, 13); + idct_line(temp, block, 14); + idct_line(temp, block, 15); +#else + S32 i; + S32 size = gGOPP->patch_size; + for (i = 0; i < size; i++) + { + idct_column(block, temp, i); + } + for (i = 0; i < size; i++) + { + idct_line(temp, block, i); + } +#endif +} + +inline void idct_patch_large(F32 *block) +{ + F32 temp[LARGE_PATCH_SIZE*LARGE_PATCH_SIZE]; + + idct_column_large_slow(block, temp, 0); + idct_column_large_slow(block, temp, 1); + idct_column_large_slow(block, temp, 2); + idct_column_large_slow(block, temp, 3); + + idct_column_large_slow(block, temp, 4); + idct_column_large_slow(block, temp, 5); + idct_column_large_slow(block, temp, 6); + idct_column_large_slow(block, temp, 7); + + idct_column_large_slow(block, temp, 8); + idct_column_large_slow(block, temp, 9); + idct_column_large_slow(block, temp, 10); + idct_column_large_slow(block, temp, 11); + + idct_column_large_slow(block, temp, 12); + idct_column_large_slow(block, temp, 13); + idct_column_large_slow(block, temp, 14); + idct_column_large_slow(block, temp, 15); + + idct_column_large_slow(block, temp, 16); + idct_column_large_slow(block, temp, 17); + idct_column_large_slow(block, temp, 18); + idct_column_large_slow(block, temp, 19); + + idct_column_large_slow(block, temp, 20); + idct_column_large_slow(block, temp, 21); + idct_column_large_slow(block, temp, 22); + idct_column_large_slow(block, temp, 23); + + idct_column_large_slow(block, temp, 24); + idct_column_large_slow(block, temp, 25); + idct_column_large_slow(block, temp, 26); + idct_column_large_slow(block, temp, 27); + + idct_column_large_slow(block, temp, 28); + idct_column_large_slow(block, temp, 29); + idct_column_large_slow(block, temp, 30); + idct_column_large_slow(block, temp, 31); + + idct_line_large_slow(temp, block, 0); + idct_line_large_slow(temp, block, 1); + idct_line_large_slow(temp, block, 2); + idct_line_large_slow(temp, block, 3); + + idct_line_large_slow(temp, block, 4); + idct_line_large_slow(temp, block, 5); + idct_line_large_slow(temp, block, 6); + idct_line_large_slow(temp, block, 7); + + idct_line_large_slow(temp, block, 8); + idct_line_large_slow(temp, block, 9); + idct_line_large_slow(temp, block, 10); + idct_line_large_slow(temp, block, 11); + + idct_line_large_slow(temp, block, 12); + idct_line_large_slow(temp, block, 13); + idct_line_large_slow(temp, block, 14); + idct_line_large_slow(temp, block, 15); + + idct_line_large_slow(temp, block, 16); + idct_line_large_slow(temp, block, 17); + idct_line_large_slow(temp, block, 18); + idct_line_large_slow(temp, block, 19); + + idct_line_large_slow(temp, block, 20); + idct_line_large_slow(temp, block, 21); + idct_line_large_slow(temp, block, 22); + idct_line_large_slow(temp, block, 23); + + idct_line_large_slow(temp, block, 24); + idct_line_large_slow(temp, block, 25); + idct_line_large_slow(temp, block, 26); + idct_line_large_slow(temp, block, 27); + + idct_line_large_slow(temp, block, 28); + idct_line_large_slow(temp, block, 29); + idct_line_large_slow(temp, block, 30); + idct_line_large_slow(temp, block, 31); +} + +S32 gDitherNoise = 128; + +void decompress_patch(F32 *patch, S32 *cpatch, LLPatchHeader *ph) +{ + S32 i, j; + + F32 block[LARGE_PATCH_SIZE*LARGE_PATCH_SIZE], *tblock = block; + F32 *tpatch; + + LLGroupHeader *gopp = gGOPP; + S32 size = gopp->patch_size; + F32 range = ph->range; + S32 prequant = (ph->quant_wbits >> 4) + 2; + S32 quantize = 1<dc_offset; + S32 stride = gopp->stride; + + F32 ooq = 1.f/(F32)quantize; + F32 *dq = gPatchDequantizeTable; + S32 *decopy_matrix = gDeCopyMatrix; + + F32 mult = ooq*range; + F32 addval = mult*(F32)(1<<(prequant - 1))+hmin; + + for (i = 0; i < size*size; i++) + { + *(tblock++) = *(cpatch + *(decopy_matrix++))*(*dq++); + } + + if (size == 16) + { + idct_patch(block); + } + else + { + idct_patch_large(block); + } + + for (j = 0; j < size; j++) + { + tpatch = patch + j*stride; + tblock = block + j*size; + for (i = 0; i < size; i++) + { + *(tpatch++) = *(tblock++)*mult+addval; + } + } +} + + +void decompress_patchv(LLVector3 *v, S32 *cpatch, LLPatchHeader *ph) +{ + S32 i, j; + + F32 block[LARGE_PATCH_SIZE*LARGE_PATCH_SIZE], *tblock = block; + LLVector3 *tvec; + + LLGroupHeader *gopp = gGOPP; + S32 size = gopp->patch_size; + F32 range = ph->range; + S32 prequant = (ph->quant_wbits >> 4) + 2; + S32 quantize = 1<dc_offset; + S32 stride = gopp->stride; + + F32 ooq = 1.f/(F32)quantize; + F32 *dq = gPatchDequantizeTable; + S32 *decopy_matrix = gDeCopyMatrix; + + F32 mult = ooq*range; + F32 addval = mult*(F32)(1<<(prequant - 1))+hmin; + +// BOOL b_diag = FALSE; +// BOOL b_right = TRUE; + + for (i = 0; i < size*size; i++) + { + *(tblock++) = *(cpatch + *(decopy_matrix++))*(*dq++); + } + + if (size == 16) + idct_patch(block); + else + idct_patch_large(block); + + for (j = 0; j < size; j++) + { + tvec = v + j*stride; + tblock = block + j*size; + for (i = 0; i < size; i++) + { + (*tvec++).mV[VZ] = *(tblock++)*mult+addval; + } + } +} + diff --git a/indra/llmessage/sound_ids.h b/indra/llmessage/sound_ids.h new file mode 100644 index 0000000000..35c7f6e438 --- /dev/null +++ b/indra/llmessage/sound_ids.h @@ -0,0 +1,293 @@ +/** + * @file sound_ids.h + * @brief Temporary holder for sound IDs. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_SOUND_IDS_H +#define LL_SOUND_IDS_H + +#include "lluuid.h" + +const LLUUID SND_NULL = LLUUID::null; +const LLUUID SND_RIDE ("00000000-0000-0000-0000-000000000100"); +const LLUUID SND_SHOT ("00000000-0000-0000-0000-000000000101"); +const LLUUID SND_MORTAR ("00000000-0000-0000-0000-000000000102"); +const LLUUID SND_HIT ("00000000-0000-0000-0000-000000000103"); +const LLUUID SND_EXPLOSION ("00000000-0000-0000-0000-000000000104"); +const LLUUID SND_BOING ("00000000-0000-0000-0000-000000000105"); +const LLUUID SND_OBJECT_CREATE ("9f1bc096-3592-411e-9b0b-c447a9ff054c"); + +// +// Different bird sounds for different states +// + +const LLUUID SND_CHIRP ("00000000-0000-0000-0000-000000000106"); // Flying random chirp +const LLUUID SND_CHIRP2 ("828a9526-175b-455d-8af0-0e3c0fb602b2"); // Spooked by user +const LLUUID SND_CHIRP3 ("f99772d6-1ce6-4a39-a28b-06d26c94c9e3"); // Spooked by object +const LLUUID SND_CHIRP4 ("54472ca4-7fc9-42cb-b7d5-99ad5b12bd50"); // Chasing other bird +const LLUUID SND_CHIRP5 ("2929964f-fac5-40d7-9179-2864a8fa9ace"); // Hopping random chirp +const LLUUID SND_CHIRPDEAD ("9abff1d3-863a-4e04-bd83-3834fd7fcff4"); // Hit by grenade - dead! + + +const LLUUID SND_MUNCH ("00000000-0000-0000-0000-000000000107"); +const LLUUID SND_PUNCH ("00000000-0000-0000-0000-000000000108"); +const LLUUID SND_SPLASH ("00000000-0000-0000-0000-000000000109"); +const LLUUID SND_CLICK ("00000000-0000-0000-0000-000000000110"); +const LLUUID SND_WHISTLE ("ab858f9a-1f44-4d39-9b33-351543d03ccb"); +const LLUUID SND_TYPING ("5e191c7b-8996-9ced-a177-b2ac32bfea06"); + +const LLUUID SND_ARROW_SHOT ("00000000-0000-0000-0000-000000000111"); +const LLUUID SND_ARROW_THUD ("00000000-0000-0000-0000-000000000112"); +const LLUUID SND_LASER_SHOT ("00000000-0000-0000-0000-000000000113"); +const LLUUID SND_JET_THRUST ("67f5e4f0-0534-4d97-bc01-f297648d20e0"); + +const LLUUID SND_SILENCE ("00000000-0000-0000-0000-000000000114"); +const LLUUID SND_BUBBLES ("00000000-0000-0000-0000-000000000115"); +const LLUUID SND_WELCOME ("00000000-0000-0000-0000-000000000116"); +const LLUUID SND_SQUISH ("00000000-0000-0000-0000-000000000117"); +const LLUUID SND_SUBPOD ("00000000-0000-0000-0000-000000000118"); +const LLUUID SND_FOOTSTEPS ("00000000-0000-0000-0000-000000000119"); +const LLUUID SND_STEP_LEFT ("00000000-0000-0000-0000-000000000124"); +const LLUUID SND_STEP_RIGHT ("00000000-0000-0000-0000-000000000125"); + +const LLUUID SND_BALL_COLLISION ("00000000-0000-0000-0000-000000000120"); + +const LLUUID SND_OOOH_SCARE_ME ("00000000-0000-0000-0000-000000000121"); +const LLUUID SND_PAYBACK_TIME ("00000000-0000-0000-0000-000000000122"); +const LLUUID SND_READY_FOR_BATTLE ("00000000-0000-0000-0000-000000000123"); + +const LLUUID SND_FLESH_FLESH ("dce5fdd4-afe4-4ea1-822f-dd52cac46b08"); +const LLUUID SND_FLESH_PLASTIC ("51011582-fbca-4580-ae9e-1a5593f094ec"); +const LLUUID SND_FLESH_RUBBER ("68d62208-e257-4d0c-bbe2-20c9ea9760bb"); +const LLUUID SND_GLASS_FLESH ("75872e8c-bc39-451b-9b0b-042d7ba36cba"); +const LLUUID SND_GLASS_GLASS ("6a45ba0b-5775-4ea8-8513-26008a17f873"); +const LLUUID SND_GLASS_PLASTIC ("992a6d1b-8c77-40e0-9495-4098ce539694"); +const LLUUID SND_GLASS_RUBBER ("2de4da5a-faf8-46be-bac6-c4d74f1e5767"); +const LLUUID SND_GLASS_WOOD ("6e3fb0f7-6d9c-42ca-b86b-1122ff562d7d"); +const LLUUID SND_METAL_FLESH ("14209133-4961-4acc-9649-53fc38ee1667"); +const LLUUID SND_METAL_GLASS ("bc4a4348-cfcc-4e5e-908e-8a52a8915fe6"); +const LLUUID SND_METAL_METAL ("9e5c1297-6eed-40c0-825a-d9bcd86e3193"); +const LLUUID SND_METAL_PLASTIC ("e534761c-1894-4b61-b20c-658a6fb68157"); +const LLUUID SND_METAL_RUBBER ("8761f73f-6cf9-4186-8aaa-0948ed002db1"); +const LLUUID SND_METAL_WOOD ("874a26fd-142f-4173-8c5b-890cd846c74d"); +const LLUUID SND_PLASTIC_PLASTIC ("0e24a717-b97e-4b77-9c94-b59a5a88b2da"); +const LLUUID SND_RUBBER_PLASTIC ("75cf3ade-9a5b-4c4d-bb35-f9799bda7fb2"); +const LLUUID SND_RUBBER_RUBBER ("153c8bf7-fb89-4d89-b263-47e58b1b4774"); +const LLUUID SND_STONE_FLESH ("55c3e0ce-275a-46fa-82ff-e0465f5e8703"); +const LLUUID SND_STONE_GLASS ("24babf58-7156-4841-9a3f-761bdbb8e237"); +const LLUUID SND_STONE_METAL ("aca261d8-e145-4610-9e20-9eff990f2c12"); +const LLUUID SND_STONE_PLASTIC ("0642fba6-5dcf-4d62-8e7b-94dbb529d117"); +const LLUUID SND_STONE_RUBBER ("25a863e8-dc42-4e8a-a357-e76422ace9b5"); +const LLUUID SND_STONE_STONE ("9538f37c-456e-4047-81be-6435045608d4"); +const LLUUID SND_STONE_WOOD ("8c0f84c3-9afd-4396-b5f5-9bca2c911c20"); +const LLUUID SND_WOOD_FLESH ("be582e5d-b123-41a2-a150-454c39e961c8"); +const LLUUID SND_WOOD_PLASTIC ("c70141d4-ba06-41ea-bcbc-35ea81cb8335"); +const LLUUID SND_WOOD_RUBBER ("7d1826f4-24c4-4aac-8c2e-eff45df37783"); +const LLUUID SND_WOOD_WOOD ("063c97d3-033a-4e9b-98d8-05c8074922cb"); + + +const LLUUID SND_SLIDE_FLESH_FLESH ("614eec22-f73d-4fdc-8691-a37dc5c58333"); +const LLUUID SND_SLIDE_FLESH_PLASTIC (SND_NULL); +const LLUUID SND_SLIDE_FLESH_RUBBER (SND_NULL); +const LLUUID SND_SLIDE_FLESH_FABRIC ("3678b9b9-2a0c-42b5-9c83-80b64ad6e898"); +const LLUUID SND_SLIDE_FLESH_GRAVEL ("02eaa42a-ce1a-4b6b-9c38-cd7ad0e8f4a6"); +const LLUUID SND_SLIDE_FLESH_GRAVEL_02 ("e7d3b501-79f8-4419-b842-ab6843e0f840"); +const LLUUID SND_SLIDE_FLESH_GRAVEL_03 ("4c3e8b52-6244-4e44-85a6-f4ab994418ed"); +const LLUUID SND_SLIDE_GLASS_GRAVEL ("ca491e77-5c47-4ea1-8021-b3ebbf636cab"); +const LLUUID SND_SLIDE_GLASS_GRAVEL_02 ("30794d49-91ce-48e3-a527-c06f67bd6cbe"); +const LLUUID SND_SLIDE_GLASS_GRAVEL_03 ("04c78e54-fd8d-46b6-8ab9-7678b5d6e5cb"); +const LLUUID SND_SLIDE_GLASS_FLESH (SND_NULL); +const LLUUID SND_SLIDE_GLASS_GLASS (SND_NULL); +const LLUUID SND_SLIDE_GLASS_PLASTIC (SND_NULL); +const LLUUID SND_SLIDE_GLASS_RUBBER (SND_NULL); +const LLUUID SND_SLIDE_GLASS_WOOD (SND_NULL); +const LLUUID SND_SLIDE_METAL_FABRIC ("18b66e81-2958-42d4-a373-7a5054919adc"); +const LLUUID SND_SLIDE_METAL_FLESH ("dde65837-633c-4841-af2f-62ec471bf61e"); +const LLUUID SND_SLIDE_METAL_FLESH_02 ("f3cc2cbe-1a1a-4db7-a8d2-e9c8f8fa1f4f"); +const LLUUID SND_SLIDE_METAL_GLASS ("4188be39-7b1f-4495-bf2b-83ddd82eea05"); +const LLUUID SND_SLIDE_METAL_GLASS_02 ("336faa2b-9d96-4e14-93ad-b63b60074379"); +const LLUUID SND_SLIDE_METAL_GLASS_03 ("34d912aa-cf73-4462-b7d0-dcba2c66caba"); +const LLUUID SND_SLIDE_METAL_GLASS_04 ("97ffc063-e872-4469-8e95-1450ac6bad2b"); +const LLUUID SND_SLIDE_METAL_GRAVEL ("2bbff37d-009a-4cfc-9a0d-817652c08fbe"); +const LLUUID SND_SLIDE_METAL_GRAVEL_02 ("a906a228-783b-49e7-9f0a-e20a41d0e39f"); +const LLUUID SND_SLIDE_METAL_METAL ("09461277-c691-45de-b2c5-89dfd3712f79"); +const LLUUID SND_SLIDE_METAL_METAL_02 ("e00a5d97-8fdc-46c1-bd53-7e312727466c"); +const LLUUID SND_SLIDE_METAL_METAL_03 ("8ebfa780-c440-4b52-ab65-5edf3bc15bf1"); +const LLUUID SND_SLIDE_METAL_METAL_04 ("d6d03cb2-5b16-4e31-b7d4-2a81d2a0909b"); +const LLUUID SND_SLIDE_METAL_METAL_05 ("3a46f447-916e-47de-a1e5-95d1af46bd0f"); +const LLUUID SND_SLIDE_METAL_METAL_06 ("cd423231-e70d-4fd2-ad26-f1c6cf5f0610"); +const LLUUID SND_SLIDE_METAL_PLASTIC (SND_NULL); +const LLUUID SND_SLIDE_METAL_RUBBER ("12d97bc0-3c15-4744-b6bd-77d1316eb4f0"); +const LLUUID SND_SLIDE_METAL_WOOD ("4afb6926-a73f-4cb7-85d5-0f9a40107434"); +const LLUUID SND_SLIDE_METAL_WOOD_02 ("349970bf-187d-4bcb-b2cf-e7bb6581590f"); +const LLUUID SND_SLIDE_METAL_WOOD_03 ("64bf6e87-73d4-4cb4-84f7-55cecfd97cd3"); +const LLUUID SND_SLIDE_METAL_WOOD_04 ("0dc670a9-dbe8-41bc-b8ee-4d96d99219d5"); +const LLUUID SND_SLIDE_METAL_WOOD_05 ("6e3cc57b-c9aa-4829-86a1-8e82aeaccb47"); +const LLUUID SND_SLIDE_METAL_WOOD_06 ("c1237f4c-8c88-4da1-bfbc-2af26a8d9e5a"); +const LLUUID SND_SLIDE_METAL_WOOD_07 ("0e1ec243-063b-4dcb-a903-52b8dffed3d2"); +const LLUUID SND_SLIDE_METAL_WOOD_08 ("66736d0f-533d-4007-a8ee-0f27c2034126"); +const LLUUID SND_SLIDE_PLASTIC_GRAVEL ("35092c21-5c48-4b4d-a818-3cf240af2348"); +const LLUUID SND_SLIDE_PLASTIC_GRAVEL_02("c37f5776-0020-47e8-89a0-c74cc6f5742d"); +const LLUUID SND_SLIDE_PLASTIC_GRAVEL_03("d2fc8db6-2e66-464a-8ccb-f99b61ee4987"); +const LLUUID SND_SLIDE_PLASTIC_GRAVEL_04("93cbdb10-6e82-4c0b-a547-7b3b79ac25f6"); +const LLUUID SND_SLIDE_PLASTIC_GRAVEL_05("2f6d0542-fcd1-4264-a17b-f57bf5ebf402"); +const LLUUID SND_SLIDE_PLASTIC_GRAVEL_06("5b8887d4-3be2-45a0-b25d-85af3b1e6392"); +const LLUUID SND_SLIDE_PLASTIC_PLASTIC (SND_NULL); +const LLUUID SND_SLIDE_PLASTIC_PLASTIC_02 (SND_NULL); +const LLUUID SND_SLIDE_PLASTIC_PLASTIC_03 (SND_NULL); +const LLUUID SND_SLIDE_PLASTIC_FABRIC ("7294d9ad-3e41-4373-992c-a9f21d5d66ad"); +const LLUUID SND_SLIDE_PLASTIC_FABRIC_02("58608ce1-f524-472f-b447-bbe6ce4a46e0"); +const LLUUID SND_SLIDE_PLASTIC_FABRIC_03("06ae285e-0b34-4ea6-84ab-9c6c31b414fc"); +const LLUUID SND_SLIDE_PLASTIC_FABRIC_04("211613db-0461-49bd-9554-5c14ad8b31f6"); +const LLUUID SND_SLIDE_RUBBER_PLASTIC ("a98ffa5a-e48e-4f9d-9242-b9a3210ad84a"); +const LLUUID SND_SLIDE_RUBBER_PLASTIC_02 ("d4136c40-eeaa-49c6-a982-8e5a16f5d93a"); +const LLUUID SND_SLIDE_RUBBER_PLASTIC_03 ("29ec0fb2-0b23-47b2-835b-c83cc7cf9fb0"); +const LLUUID SND_SLIDE_RUBBER_RUBBER (SND_NULL); +const LLUUID SND_SLIDE_STONE_FLESH (SND_NULL); +const LLUUID SND_SLIDE_STONE_GLASS (SND_NULL); +const LLUUID SND_SLIDE_STONE_METAL (SND_NULL); +const LLUUID SND_SLIDE_STONE_PLASTIC ("afd0bcc3-d41a-4572-9e7f-08a29eeb0b8a"); +const LLUUID SND_SLIDE_STONE_PLASTIC_02 ("881b720a-96cf-4128-bb98-5d87e03e93c7"); +const LLUUID SND_SLIDE_STONE_PLASTIC_03 ("293dac42-658a-4c5a-a7a2-6d4c5e5658b0"); +const LLUUID SND_SLIDE_STONE_RUBBER ("0724b946-6a3f-4eeb-bb50-0a3b33120974"); +const LLUUID SND_SLIDE_STONE_RUBBER_02 ("ada93d00-76e2-4bf1-9ad9-493727630717"); +const LLUUID SND_SLIDE_STONE_STONE ("ade766dc-2e75-4699-9b41-7c8e53d2b3f2"); +const LLUUID SND_SLIDE_STONE_STONE_02 ("66698375-6594-47b0-8046-c3973de1291d"); +const LLUUID SND_SLIDE_STONE_WOOD ("174ef324-ed50-4f65-9479-b4da580aeb3c"); +const LLUUID SND_SLIDE_STONE_WOOD_02 ("33d517fd-ff11-4d01-a7b5-0e3abf818dcf"); +const LLUUID SND_SLIDE_STONE_WOOD_03 ("1bac4b63-e6fd-4659-9761-991284cf4582"); +const LLUUID SND_SLIDE_STONE_WOOD_04 ("a7d28564-6821-4c01-a378-cde98fba7ba9"); +const LLUUID SND_SLIDE_WOOD_FABRIC ("22c58e74-22cd-4960-9ab7-5bf08ab824e5"); +const LLUUID SND_SLIDE_WOOD_FABRIC_02 ("0b0ed22e-4a0f-4617-a4cf-20d0f2b78ccc"); +const LLUUID SND_SLIDE_WOOD_FABRIC_03 ("42b80abb-9823-4b74-a210-326ccf23636a"); +const LLUUID SND_SLIDE_WOOD_FABRIC_04 ("8538298a-1e6b-4b69-a9ee-5e01e4a02b35"); +const LLUUID SND_SLIDE_WOOD_FLESH ("84b026f3-a11c-4366-aa7c-07edcd89b2bb"); +const LLUUID SND_SLIDE_WOOD_FLESH_02 ("2644191f-4848-47ba-8ba7-bddc0bfcb3da"); +const LLUUID SND_SLIDE_WOOD_FLESH_03 ("edb978e4-9be9-456f-b2fc-e8502bfe25be"); +const LLUUID SND_SLIDE_WOOD_FLESH_04 ("bf2b972e-f42a-46d7-b53e-5fca38f5bc61"); +const LLUUID SND_SLIDE_WOOD_GRAVEL ("d063bb4d-0eff-4403-a6cc-c6c6c073e624"); +const LLUUID SND_SLIDE_WOOD_GRAVEL_02 ("511eb679-6d93-47fa-9141-c3ef9261c919"); +const LLUUID SND_SLIDE_WOOD_GRAVEL_03 ("4ed1fd43-4707-4e5c-b7b7-21ec4e72c1ac"); +const LLUUID SND_SLIDE_WOOD_GRAVEL_04 ("99ea89b3-aa76-4b87-99c8-670365c6d8c3"); +const LLUUID SND_SLIDE_WOOD_PLASTIC ("505ca3c4-94a0-4e28-8fc1-ea72a428396b"); +const LLUUID SND_SLIDE_WOOD_PLASTIC_02 ("fc404011-df71-4ed0-8f22-b72bdd18f63c"); +const LLUUID SND_SLIDE_WOOD_PLASTIC_03 ("67dbe225-26df-4efa-8c8b-f1ef669fec45"); +const LLUUID SND_SLIDE_WOOD_RUBBER (SND_NULL); +const LLUUID SND_SLIDE_WOOD_WOOD ("3079d569-b3e8-4df4-9e09-f0d4611213ef"); +const LLUUID SND_SLIDE_WOOD_WOOD_02 ("276b093d-dbcb-4279-a89e-a54b0b416af6"); +const LLUUID SND_SLIDE_WOOD_WOOD_03 ("c3f3ca5e-2768-4081-847f-247139310fdb"); +const LLUUID SND_SLIDE_WOOD_WOOD_04 ("f08d44b8-ff87-4a98-9561-c72f1f2fec81"); +const LLUUID SND_SLIDE_WOOD_WOOD_05 ("2d8a58cf-f139-4238-8503-27d334d05c85"); +const LLUUID SND_SLIDE_WOOD_WOOD_06 ("e157ebbd-b12d-4225-aa7c-d47b026a7687"); +const LLUUID SND_SLIDE_WOOD_WOOD_07 ("35e17956-e7b4-478c-b274-e37db8a166b2"); +const LLUUID SND_SLIDE_WOOD_WOOD_08 ("e606fc65-0643-4964-9979-ff964fa6a62c"); + + +const LLUUID SND_ROLL_FLESH_FLESH (SND_NULL); +const LLUUID SND_ROLL_FLESH_PLASTIC ("89a0be4c-848d-4a6e-8886-298f56c2cff4"); +const LLUUID SND_ROLL_FLESH_PLASTIC_02 ("beb06343-1aa1-4af2-b320-5d2ec31c53b1"); +const LLUUID SND_ROLL_FLESH_RUBBER (SND_NULL); +const LLUUID SND_ROLL_GLASS_GRAVEL ("ba795c74-7e09-4572-b495-e09886a46b86"); +const LLUUID SND_ROLL_GLASS_GRAVEL_02 ("4c93c3b7-14cb-4d9b-a7df-628ad935f1f2"); +const LLUUID SND_ROLL_GLASS_FLESH (SND_NULL); +const LLUUID SND_ROLL_GLASS_GLASS (SND_NULL); +const LLUUID SND_ROLL_GLASS_PLASTIC (SND_NULL); +const LLUUID SND_ROLL_GLASS_RUBBER (SND_NULL); +const LLUUID SND_ROLL_GLASS_WOOD ("d40b1f48-a061-4f6e-b18f-4326a3dd5c29"); +const LLUUID SND_ROLL_GLASS_WOOD_02 ("78cd407a-bb36-4163-ba09-20f2e6d9d44b"); +const LLUUID SND_ROLL_GRAVEL_GRAVEL ("c7354cc3-6df5-4738-8dbb-b28a6ac46a05"); +const LLUUID SND_ROLL_GRAVEL_GRAVEL_02 ("01d194c4-72a6-47df-81a5-8db430faff87"); +const LLUUID SND_ROLL_METAL_FABRIC ("ce6e6564-20fd-48e4-81e2-cd3f81c00a3e"); +const LLUUID SND_ROLL_METAL_FABRIC_02 ("fc4d0065-32f6-4bb0-9f3f-f4737eb27163"); +const LLUUID SND_ROLL_METAL_FLESH (SND_NULL); +const LLUUID SND_ROLL_METAL_GLASS ("63d530bb-a41f-402b-aa1f-be6b11959809"); +const LLUUID SND_ROLL_METAL_GLASS_02 ("f62642c2-6db5-4faa-8b77-939067d837c3"); +const LLUUID SND_ROLL_METAL_GLASS_03 ("db5b5a15-2817-4cd7-9f0b-9ad49b5e52c8"); +const LLUUID SND_ROLL_METAL_GRAVEL ("447164e3-9646-4c1a-a16d-606892891466"); +const LLUUID SND_ROLL_METAL_METAL ("c3c22cf3-5d1f-4cc3-b4b5-708b9f65979c"); +const LLUUID SND_ROLL_METAL_METAL_02 ("d8386277-a1ea-460e-b6fd-bb285c323bf1"); +const LLUUID SND_ROLL_METAL_METAL_03 ("69ee1f02-f9cd-4c8b-aedd-39a2d6705680"); +const LLUUID SND_ROLL_METAL_METAL_04 ("5cc6b5fd-26ce-47ad-b21d-3a7c190dd375"); +const LLUUID SND_ROLL_METAL_PLASTIC ("c6a9bbf6-df15-4713-9f84-7237fce4051e"); +const LLUUID SND_ROLL_METAL_PLASTIC_01 ("0fedb59b-2dbb-4cec-b6cc-8559ec027749"); +const LLUUID SND_ROLL_METAL_RUBBER (SND_NULL); +const LLUUID SND_ROLL_METAL_WOOD ("1d76af57-01b1-4c73-9a1d-69523bfa50ea"); +const LLUUID SND_ROLL_METAL_WOOD_02 ("78aa4e71-8e7c-4b90-a561-3ebdc639f99b"); +const LLUUID SND_ROLL_METAL_WOOD_03 ("777d95bf-962f-48fa-93bf-8c1806557d72"); +const LLUUID SND_ROLL_METAL_WOOD_04 ("1833da76-45e2-4a8b-97da-d17413e056c9"); +const LLUUID SND_ROLL_METAL_WOOD_05 ("b13e1232-3d8d-42e9-92ec-b30f9f823962"); +const LLUUID SND_ROLL_PLASTIC_FABRIC ("616a1f03-209f-4c55-b264-83a000b6ef0a"); +const LLUUID SND_ROLL_PLASTIC_PLASTIC ("873f3d82-00b2-4082-9c69-7aef3461dba1"); +const LLUUID SND_ROLL_PLASTIC_PLASTIC_02 ("cc39879f-ebc8-4405-a4fc-8342f5bed31e"); +const LLUUID SND_ROLL_RUBBER_PLASTIC (SND_NULL); +const LLUUID SND_ROLL_RUBBER_RUBBER (SND_NULL); +const LLUUID SND_ROLL_STONE_FLESH (SND_NULL); +const LLUUID SND_ROLL_STONE_GLASS (SND_NULL); +const LLUUID SND_ROLL_STONE_METAL (SND_NULL); +const LLUUID SND_ROLL_STONE_PLASTIC ("155f65a8-cae7-476e-a58b-fd362be7fd0e"); +const LLUUID SND_ROLL_STONE_RUBBER (SND_NULL); +const LLUUID SND_ROLL_STONE_STONE ("67d56e3f-6ed5-4658-9418-14f020c38b11"); +const LLUUID SND_ROLL_STONE_STONE_02 ("43d99d10-d75b-4246-accf-4ceb2c909aa7"); +const LLUUID SND_ROLL_STONE_STONE_03 ("f04e83ff-eed7-4e99-8f45-eb97e4e1d3b7"); +const LLUUID SND_ROLL_STONE_STONE_04 ("10fcc5ad-fa89-48d6-b774-986b580c1efc"); +const LLUUID SND_ROLL_STONE_STONE_05 ("3d86f5a3-1a91-49d9-b99f-8521a7422497"); +const LLUUID SND_ROLL_STONE_WOOD ("53e46fb7-6c21-4fe1-bffe-0567475d48fa"); +const LLUUID SND_ROLL_STONE_WOOD_02 ("5eba8c9a-a014-4299-87f1-315c45ec795b"); +const LLUUID SND_ROLL_STONE_WOOD_03 ("ea6c05fc-6e9c-4526-8a20-bc47810bb549"); +const LLUUID SND_ROLL_STONE_WOOD_04 ("64618cbf-3f42-4728-8094-e77807545efb"); +const LLUUID SND_ROLL_WOOD_FLESH ("26ee185d-6fc3-49f8-89ba-51cab04cfc42"); +const LLUUID SND_ROLL_WOOD_FLESH_02 ("334faa25-1e80-4c99-b29f-4c9c2a3d079d"); +const LLUUID SND_ROLL_WOOD_FLESH_03 ("2f876626-4dce-4f71-a91e-a25302edfab7"); +const LLUUID SND_ROLL_WOOD_FLESH_04 ("d6877aac-07fc-4931-bcde-585f223802ad"); +const LLUUID SND_ROLL_WOOD_GRAVEL ("2a23ebb5-a4a2-4f1f-8d75-7384239354aa"); +const LLUUID SND_ROLL_WOOD_GRAVEL_02 ("208bf26d-f097-450c-95c4-9d26317c613c"); +const LLUUID SND_ROLL_WOOD_GRAVEL_03 ("a26ecaf4-92c6-4e32-9864-56b7c70cab8e"); +const LLUUID SND_ROLL_WOOD_PLASTIC ("71c1000a-9f16-4cc3-8ede-ec4aa3bf5723"); +const LLUUID SND_ROLL_WOOD_PLASTIC_02 ("7bc20ba6-1e6d-4eea-83ad-c5cc3ae0e409"); +const LLUUID SND_ROLL_WOOD_RUBBER (SND_NULL); +const LLUUID SND_ROLL_WOOD_WOOD ("2cc8eec4-bb4a-4ba8-b783-71526ec708e8"); +const LLUUID SND_ROLL_WOOD_WOOD_02 ("0a1f8070-a11a-4b4c-b260-5ffb6acb0a5d"); +const LLUUID SND_ROLL_WOOD_WOOD_03 ("160bef64-da9c-4be8-b07b-a5060b501700"); +const LLUUID SND_ROLL_WOOD_WOOD_04 ("1c62ea16-cc60-48ed-829a-68b8f4cf0c1c"); +const LLUUID SND_ROLL_WOOD_WOOD_05 ("be9cc8fe-b920-4bf5-8924-453088cbc03f"); +const LLUUID SND_ROLL_WOOD_WOOD_06 ("a76cfe60-56b0-43b1-8f31-93e56947d78b"); +const LLUUID SND_ROLL_WOOD_WOOD_07 ("0c6aa481-b5bc-4573-ae83-8e16ff27e750"); +const LLUUID SND_ROLL_WOOD_WOOD_08 ("214ab2c7-871a-451b-b0db-4c5677199011"); +const LLUUID SND_ROLL_WOOD_WOOD_09 ("0086e4db-3ac6-4545-b414-6f359bedd9a5"); + +const LLUUID SND_SLIDE_STONE_STONE_01 ("2a7dcbd1-d3e6-4767-8432-8322648e7b9d"); + +const LLUUID SND_STONE_DIRT_01 ("97727335-392c-4338-ac4b-23a7883279c2"); +const LLUUID SND_STONE_DIRT_02 ("cbe75eb2-3375-41d8-9e3f-2ae46b4164ed"); +const LLUUID SND_STONE_DIRT_03 ("31e236ee-001b-4c8e-ad6c-c2074cb64357"); +const LLUUID SND_STONE_DIRT_04 ("c8091652-e04b-4a11-84ba-15dba06e7a1b"); + +const LLUUID SND_STONE_STONE_02 ("ba4ef5ac-7435-4240-b826-c24ba8fa5a78"); +const LLUUID SND_STONE_STONE_04 ("ea296329-0f09-4993-af1b-e6784bab1dc9"); + + + +// extra guids +#if 0 +const LLUUID SND_ ("a839b8ac-b0af-4ba9-9fde-188754744e02"); +const LLUUID SND_ ("20165fa8-836f-4993-85dc-1529172dcd14"); +const LLUUID SND_ ("fba8e17b-a4b3-4693-9fce-c14800f8a349"); +const LLUUID SND_ ("2d48db8b-7260-4b02-ad2a-b2c6bee60e94"); +const LLUUID SND_ ("956d344b-1808-4d8b-88b1-cbc82b7a96a1"); +const LLUUID SND_ ("b8303cc6-f0b4-4c6f-a199-81f87aba342e"); +const LLUUID SND_ ("fbf7cd0c-bc8f-4cba-9c19-11f4dd03a06b"); +const LLUUID SND_ ("85047f7d-933a-4ce5-a7b5-34670243e1ab"); +const LLUUID SND_ ("0f81acf7-6a2e-4490-957f-c7b0eda00559"); +const LLUUID SND_ ("5631a6a1-79b4-4de8-bccf-1880b6882da1"); +const LLUUID SND_ ("43c87a6b-ffb2-437b-89a0-9deba890a4fc"); +const LLUUID SND_ ("58878d1d-3156-4d01-ac3c-0c4fb99f4d53"); +const LLUUID SND_ ("9a83f321-44bf-40f6-b006-46c085515345"); +const LLUUID SND_ ("ff144533-33ab-40f2-bac8-39c34699ecc4"); +const LLUUID SND_ ("09018e87-d52c-4cd5-9805-015f413319e7"); +const LLUUID SND_ ("17d4c057-7edd-401e-9589-d5b9fe981bf2"); +#endif + +#endif diff --git a/indra/llprimitive/legacy_object_types.h b/indra/llprimitive/legacy_object_types.h new file mode 100644 index 0000000000..57ace87e89 --- /dev/null +++ b/indra/llprimitive/legacy_object_types.h @@ -0,0 +1,59 @@ +/** + * @file legacy_object_types.h + * @brief Byte codes for basic object and primitive types + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LEGACY_OBJECT_TYPES_H +#define LL_LEGACY_OBJECT_TYPES_H + +const S8 PLAYER = 'c'; +//const S8 BASIC_SHOT = 's'; +//const S8 BIG_SHOT = 'S'; +//const S8 TREE_SHOT = 'g'; +//const S8 PHYSICAL_BALL = 'b'; + +const S8 TREE = 'T'; +const S8 TREE_NEW = 'R'; +//const S8 SPARK = 'p'; +//const S8 SMOKE = 'q'; +//const S8 BOX = 'x'; +//const S8 CYLINDER = 'y'; +//const S8 CONE = 'o'; +//const S8 SPHERE = 'h'; +//const S8 BIRD = 'r'; // ascii 114 +//const S8 ATOR = 'a'; +//const S8 ROCK = 'k'; + +const S8 GRASS = 'd'; +const S8 PART_SYS = 'P'; + +//const S8 ORACLE = 'O'; +//const S8 TEXTBUBBLE = 't'; // Text bubble to show communication +//const S8 DEMON = 'M'; // Maxwell's demon for scarfing legacy_object_types.h +//const S8 CUBE = 'f'; +//const S8 LSL_TEST = 'L'; +//const S8 PRISM = '1'; +//const S8 PYRAMID = '2'; +//const S8 TETRAHEDRON = '3'; +//const S8 HALF_CYLINDER = '4'; +//const S8 HALF_CONE = '5'; +//const S8 HALF_SPHERE = '6'; + +const S8 PRIMITIVE_VOLUME = 'v'; + +// Misc constants + +//const F32 AVATAR_RADIUS = 0.5f; +//const F32 SHOT_RADIUS = 0.05f; +//const F32 BIG_SHOT_RADIUS = 0.05f; +//const F32 TREE_SIZE = 5.f; +//const F32 BALL_SIZE = 4.f; + +//const F32 SHOT_VELOCITY = 100.f; +//const F32 GRENADE_BLAST_RADIUS = 5.f; + +#endif + diff --git a/indra/llprimitive/llmaterialtable.cpp b/indra/llprimitive/llmaterialtable.cpp new file mode 100644 index 0000000000..ebd6306284 --- /dev/null +++ b/indra/llprimitive/llmaterialtable.cpp @@ -0,0 +1,657 @@ +/** + * @file llmaterialtable.cpp + * @brief Table of material names and IDs for viewer + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llmaterialtable.h" +#include "material_codes.h" +#include "sound_ids.h" +#include "imageids.h" + +LLMaterialTable LLMaterialTable::basic(1); + +F32 const LLMaterialTable::DEFAULT_FRICTION = 0.5f; +F32 const LLMaterialTable::DEFAULT_RESTITUTION = 0.4f; + +LLMaterialTable::LLMaterialTable() +{ +} + +LLMaterialTable::LLMaterialTable(U8 isBasic) +{ + initBasicTable(); +} + +LLMaterialTable::~LLMaterialTable() +{ + if (mCollisionSoundMatrix) + { + delete [] mCollisionSoundMatrix; + mCollisionSoundMatrix = NULL; + } + + if (mSlidingSoundMatrix) + { + delete [] mSlidingSoundMatrix; + mSlidingSoundMatrix = NULL; + } + + if (mRollingSoundMatrix) + { + delete [] mRollingSoundMatrix; + mRollingSoundMatrix = NULL; + } + + mMaterialInfoList.deleteAllData(); +} + +void LLMaterialTable::initBasicTable() +{ + add(LL_MCODE_STONE,"Stone", LL_DEFAULT_STONE_UUID); + add(LL_MCODE_METAL,"Metal", LL_DEFAULT_METAL_UUID); + add(LL_MCODE_GLASS,"Glass", LL_DEFAULT_GLASS_UUID); + add(LL_MCODE_WOOD,"Wood", LL_DEFAULT_WOOD_UUID); + add(LL_MCODE_FLESH,"Flesh", LL_DEFAULT_FLESH_UUID); + add(LL_MCODE_PLASTIC,"Plastic", LL_DEFAULT_PLASTIC_UUID); + add(LL_MCODE_RUBBER,"Rubber", LL_DEFAULT_RUBBER_UUID); + add(LL_MCODE_LIGHT,"Light", LL_DEFAULT_LIGHT_UUID); + + // specify densities for these materials. . . + // these were taken from http://www.mcelwee.net/html/densities_of_various_materials.html + + addDensity(LL_MCODE_STONE,30.f); + addDensity(LL_MCODE_METAL,50.f); + addDensity(LL_MCODE_GLASS,20.f); + addDensity(LL_MCODE_WOOD,10.f); + addDensity(LL_MCODE_FLESH,10.f); + addDensity(LL_MCODE_PLASTIC,5.f); + addDensity(LL_MCODE_RUBBER,0.5f); // + addDensity(LL_MCODE_LIGHT,20.f); // + + // add damage and energy values + addDamageAndEnergy(LL_MCODE_STONE, 1.f, 1.f, 1.f); // concrete + addDamageAndEnergy(LL_MCODE_METAL, 1.f, 1.f, 1.f); // steel + addDamageAndEnergy(LL_MCODE_GLASS, 1.f, 1.f, 1.f); // borosilicate glass + addDamageAndEnergy(LL_MCODE_WOOD, 1.f, 1.f, 1.f); // southern pine + addDamageAndEnergy(LL_MCODE_FLESH, 1.f, 1.f, 1.f); // saltwater + addDamageAndEnergy(LL_MCODE_PLASTIC, 1.f, 1.f, 1.f); // HDPE + addDamageAndEnergy(LL_MCODE_RUBBER, 1.f, 1.f, 1.f); // + addDamageAndEnergy(LL_MCODE_LIGHT, 1.f, 1.f, 1.f); // + + addFriction(LL_MCODE_STONE,0.8f); // concrete + addFriction(LL_MCODE_METAL,0.3f); // steel + addFriction(LL_MCODE_GLASS,0.2f); // borosilicate glass + addFriction(LL_MCODE_WOOD,0.6f); // southern pine + addFriction(LL_MCODE_FLESH,0.9f); // saltwater + addFriction(LL_MCODE_PLASTIC,0.4f); // HDPE + addFriction(LL_MCODE_RUBBER,0.9f); // + addFriction(LL_MCODE_LIGHT,0.2f); // + + addRestitution(LL_MCODE_STONE,0.4f); // concrete + addRestitution(LL_MCODE_METAL,0.4f); // steel + addRestitution(LL_MCODE_GLASS,0.7f); // borosilicate glass + addRestitution(LL_MCODE_WOOD,0.5f); // southern pine + addRestitution(LL_MCODE_FLESH,0.3f); // saltwater + addRestitution(LL_MCODE_PLASTIC,0.7f); // HDPE + addRestitution(LL_MCODE_RUBBER,0.9f); // + addRestitution(LL_MCODE_LIGHT,0.7f); // + + addShatterSound(LL_MCODE_STONE,LLUUID("ea296329-0f09-4993-af1b-e6784bab1dc9")); + addShatterSound(LL_MCODE_METAL,LLUUID("d1375446-1c4d-470b-9135-30132433b678")); + addShatterSound(LL_MCODE_GLASS,LLUUID("85cda060-b393-48e6-81c8-2cfdfb275351")); + addShatterSound(LL_MCODE_WOOD,LLUUID("6f00669f-15e0-4793-a63e-c03f62fee43a")); + addShatterSound(LL_MCODE_FLESH,LLUUID("2d8c6f51-149e-4e23-8413-93a379b42b67")); + addShatterSound(LL_MCODE_PLASTIC,LLUUID("d55c7f3c-e1c3-4ddc-9eff-9ef805d9190e")); + addShatterSound(LL_MCODE_RUBBER,LLUUID("212b6d1e-8d9c-4986-b3aa-f3c6df8d987d")); + addShatterSound(LL_MCODE_LIGHT,LLUUID("d55c7f3c-e1c3-4ddc-9eff-9ef805d9190e")); + + // CollisionSounds + mCollisionSoundMatrix = new LLUUID[LL_MCODE_END*LL_MCODE_END]; + if (mCollisionSoundMatrix) + { + addCollisionSound(LL_MCODE_STONE, LL_MCODE_STONE, SND_STONE_STONE); + addCollisionSound(LL_MCODE_STONE, LL_MCODE_METAL, SND_STONE_METAL); + addCollisionSound(LL_MCODE_STONE, LL_MCODE_GLASS, SND_STONE_GLASS); + addCollisionSound(LL_MCODE_STONE, LL_MCODE_WOOD, SND_STONE_WOOD); + addCollisionSound(LL_MCODE_STONE, LL_MCODE_FLESH, SND_STONE_FLESH); + addCollisionSound(LL_MCODE_STONE, LL_MCODE_PLASTIC, SND_STONE_PLASTIC); + addCollisionSound(LL_MCODE_STONE, LL_MCODE_RUBBER, SND_STONE_RUBBER); + addCollisionSound(LL_MCODE_STONE, LL_MCODE_LIGHT, SND_STONE_PLASTIC); + + addCollisionSound(LL_MCODE_METAL, LL_MCODE_METAL, SND_METAL_METAL); + addCollisionSound(LL_MCODE_METAL, LL_MCODE_GLASS, SND_METAL_GLASS); + addCollisionSound(LL_MCODE_METAL, LL_MCODE_WOOD, SND_METAL_WOOD); + addCollisionSound(LL_MCODE_METAL, LL_MCODE_FLESH, SND_METAL_FLESH); + addCollisionSound(LL_MCODE_METAL, LL_MCODE_PLASTIC, SND_METAL_PLASTIC); + addCollisionSound(LL_MCODE_METAL, LL_MCODE_LIGHT, SND_METAL_PLASTIC); + addCollisionSound(LL_MCODE_METAL, LL_MCODE_RUBBER, SND_METAL_RUBBER); + + addCollisionSound(LL_MCODE_GLASS, LL_MCODE_GLASS, SND_GLASS_GLASS); + addCollisionSound(LL_MCODE_GLASS, LL_MCODE_WOOD, SND_GLASS_WOOD); + addCollisionSound(LL_MCODE_GLASS, LL_MCODE_FLESH, SND_GLASS_FLESH); + addCollisionSound(LL_MCODE_GLASS, LL_MCODE_PLASTIC, SND_GLASS_PLASTIC); + addCollisionSound(LL_MCODE_GLASS, LL_MCODE_RUBBER, SND_GLASS_RUBBER); + addCollisionSound(LL_MCODE_GLASS, LL_MCODE_LIGHT, SND_GLASS_PLASTIC); + + addCollisionSound(LL_MCODE_WOOD, LL_MCODE_WOOD, SND_WOOD_WOOD); + addCollisionSound(LL_MCODE_WOOD, LL_MCODE_FLESH, SND_WOOD_FLESH); + addCollisionSound(LL_MCODE_WOOD, LL_MCODE_PLASTIC, SND_WOOD_PLASTIC); + addCollisionSound(LL_MCODE_WOOD, LL_MCODE_RUBBER, SND_WOOD_RUBBER); + addCollisionSound(LL_MCODE_WOOD, LL_MCODE_LIGHT, SND_WOOD_PLASTIC); + + addCollisionSound(LL_MCODE_FLESH, LL_MCODE_FLESH, SND_FLESH_FLESH); + addCollisionSound(LL_MCODE_FLESH, LL_MCODE_PLASTIC, SND_FLESH_PLASTIC); + addCollisionSound(LL_MCODE_FLESH, LL_MCODE_RUBBER, SND_FLESH_RUBBER); + addCollisionSound(LL_MCODE_FLESH, LL_MCODE_LIGHT, SND_FLESH_PLASTIC); + + addCollisionSound(LL_MCODE_RUBBER, LL_MCODE_RUBBER, SND_RUBBER_RUBBER); + addCollisionSound(LL_MCODE_RUBBER, LL_MCODE_PLASTIC, SND_RUBBER_PLASTIC); + addCollisionSound(LL_MCODE_RUBBER, LL_MCODE_LIGHT, SND_RUBBER_PLASTIC); + + addCollisionSound(LL_MCODE_PLASTIC, LL_MCODE_PLASTIC, SND_PLASTIC_PLASTIC); + addCollisionSound(LL_MCODE_PLASTIC, LL_MCODE_LIGHT, SND_PLASTIC_PLASTIC); + + addCollisionSound(LL_MCODE_LIGHT, LL_MCODE_LIGHT, SND_PLASTIC_PLASTIC); + } + + // Sliding Sounds + mSlidingSoundMatrix = new LLUUID[LL_MCODE_END*LL_MCODE_END]; + if (mSlidingSoundMatrix) + { + addSlidingSound(LL_MCODE_STONE, LL_MCODE_STONE, SND_SLIDE_STONE_STONE); + addSlidingSound(LL_MCODE_STONE, LL_MCODE_METAL, SND_SLIDE_STONE_STONE_01); + addSlidingSound(LL_MCODE_STONE, LL_MCODE_GLASS, SND_SLIDE_STONE_STONE_01); + addSlidingSound(LL_MCODE_STONE, LL_MCODE_WOOD, SND_SLIDE_STONE_WOOD); + addSlidingSound(LL_MCODE_STONE, LL_MCODE_FLESH, SND_SLIDE_STONE_STONE_01); + addSlidingSound(LL_MCODE_STONE, LL_MCODE_PLASTIC, SND_SLIDE_STONE_PLASTIC); + addSlidingSound(LL_MCODE_STONE, LL_MCODE_RUBBER, SND_SLIDE_STONE_RUBBER); + addSlidingSound(LL_MCODE_STONE, LL_MCODE_LIGHT, SND_SLIDE_STONE_PLASTIC); + + addSlidingSound(LL_MCODE_METAL, LL_MCODE_METAL, SND_SLIDE_METAL_METAL); + addSlidingSound(LL_MCODE_METAL, LL_MCODE_GLASS, SND_SLIDE_METAL_GLASS); + addSlidingSound(LL_MCODE_METAL, LL_MCODE_WOOD, SND_SLIDE_METAL_WOOD); + addSlidingSound(LL_MCODE_METAL, LL_MCODE_FLESH, SND_SLIDE_METAL_FLESH); + addSlidingSound(LL_MCODE_METAL, LL_MCODE_PLASTIC, SND_SLIDE_STONE_STONE_01); + addSlidingSound(LL_MCODE_METAL, LL_MCODE_RUBBER, SND_SLIDE_METAL_RUBBER); + addSlidingSound(LL_MCODE_METAL, LL_MCODE_LIGHT, SND_SLIDE_STONE_STONE_01); + + addSlidingSound(LL_MCODE_GLASS, LL_MCODE_GLASS, SND_SLIDE_STONE_STONE_01); + addSlidingSound(LL_MCODE_GLASS, LL_MCODE_WOOD, SND_SLIDE_STONE_STONE_01); + addSlidingSound(LL_MCODE_GLASS, LL_MCODE_FLESH, SND_SLIDE_STONE_STONE_01); + addSlidingSound(LL_MCODE_GLASS, LL_MCODE_PLASTIC, SND_SLIDE_STONE_STONE_01); + addSlidingSound(LL_MCODE_GLASS, LL_MCODE_RUBBER, SND_SLIDE_STONE_STONE_01); + addSlidingSound(LL_MCODE_GLASS, LL_MCODE_LIGHT, SND_SLIDE_STONE_STONE_01); + + addSlidingSound(LL_MCODE_WOOD, LL_MCODE_WOOD, SND_SLIDE_WOOD_WOOD); + addSlidingSound(LL_MCODE_WOOD, LL_MCODE_FLESH, SND_SLIDE_WOOD_FLESH); + addSlidingSound(LL_MCODE_WOOD, LL_MCODE_PLASTIC, SND_SLIDE_WOOD_PLASTIC); + addSlidingSound(LL_MCODE_WOOD, LL_MCODE_RUBBER, SND_SLIDE_STONE_STONE_01); + addSlidingSound(LL_MCODE_WOOD, LL_MCODE_LIGHT, SND_SLIDE_WOOD_PLASTIC); + + addSlidingSound(LL_MCODE_FLESH, LL_MCODE_FLESH, SND_SLIDE_FLESH_FLESH); + addSlidingSound(LL_MCODE_FLESH, LL_MCODE_PLASTIC, SND_SLIDE_STONE_STONE_01); + addSlidingSound(LL_MCODE_FLESH, LL_MCODE_RUBBER, SND_SLIDE_STONE_STONE_01); + addSlidingSound(LL_MCODE_FLESH, LL_MCODE_LIGHT, SND_SLIDE_STONE_STONE_01); + + addSlidingSound(LL_MCODE_RUBBER, LL_MCODE_RUBBER, SND_SLIDE_STONE_STONE_01); + addSlidingSound(LL_MCODE_RUBBER, LL_MCODE_PLASTIC, SND_SLIDE_RUBBER_PLASTIC); + addSlidingSound(LL_MCODE_RUBBER, LL_MCODE_LIGHT, SND_SLIDE_RUBBER_PLASTIC); + + addSlidingSound(LL_MCODE_PLASTIC, LL_MCODE_PLASTIC, SND_SLIDE_STONE_STONE_01); + addSlidingSound(LL_MCODE_PLASTIC, LL_MCODE_LIGHT, SND_SLIDE_STONE_STONE_01); + + addSlidingSound(LL_MCODE_LIGHT, LL_MCODE_LIGHT, SND_SLIDE_STONE_STONE_01); + } + + // Rolling Sounds + mRollingSoundMatrix = new LLUUID[LL_MCODE_END*LL_MCODE_END]; + if (mRollingSoundMatrix) + { + addRollingSound(LL_MCODE_STONE, LL_MCODE_STONE, SND_ROLL_STONE_STONE); + addRollingSound(LL_MCODE_STONE, LL_MCODE_METAL, SND_SLIDE_STONE_STONE_01); + addRollingSound(LL_MCODE_STONE, LL_MCODE_GLASS, SND_SLIDE_STONE_STONE_01); + addRollingSound(LL_MCODE_STONE, LL_MCODE_WOOD, SND_ROLL_STONE_WOOD); + addRollingSound(LL_MCODE_STONE, LL_MCODE_FLESH, SND_SLIDE_STONE_STONE_01); + addRollingSound(LL_MCODE_STONE, LL_MCODE_PLASTIC, SND_ROLL_STONE_PLASTIC); + addRollingSound(LL_MCODE_STONE, LL_MCODE_RUBBER, SND_SLIDE_STONE_STONE_01); + addRollingSound(LL_MCODE_STONE, LL_MCODE_LIGHT, SND_ROLL_STONE_PLASTIC); + + addRollingSound(LL_MCODE_METAL, LL_MCODE_METAL, SND_SLIDE_STONE_STONE_01); + addRollingSound(LL_MCODE_METAL, LL_MCODE_GLASS, SND_ROLL_METAL_GLASS); + addRollingSound(LL_MCODE_METAL, LL_MCODE_WOOD, SND_ROLL_METAL_WOOD); + addRollingSound(LL_MCODE_METAL, LL_MCODE_FLESH, SND_SLIDE_STONE_STONE_01); + addRollingSound(LL_MCODE_METAL, LL_MCODE_PLASTIC, SND_ROLL_METAL_WOOD); + addRollingSound(LL_MCODE_METAL, LL_MCODE_RUBBER, SND_SLIDE_STONE_STONE_01); + addRollingSound(LL_MCODE_METAL, LL_MCODE_LIGHT, SND_ROLL_METAL_WOOD); + + addRollingSound(LL_MCODE_GLASS, LL_MCODE_GLASS, SND_SLIDE_STONE_STONE_01); + addRollingSound(LL_MCODE_GLASS, LL_MCODE_WOOD, SND_ROLL_GLASS_WOOD); + addRollingSound(LL_MCODE_GLASS, LL_MCODE_FLESH, SND_SLIDE_STONE_STONE_01); + addRollingSound(LL_MCODE_GLASS, LL_MCODE_PLASTIC, SND_SLIDE_STONE_STONE_01); + addRollingSound(LL_MCODE_GLASS, LL_MCODE_RUBBER, SND_SLIDE_STONE_STONE_01); + addRollingSound(LL_MCODE_GLASS, LL_MCODE_LIGHT, SND_SLIDE_STONE_STONE_01); + + addRollingSound(LL_MCODE_WOOD, LL_MCODE_WOOD, SND_ROLL_WOOD_WOOD); + addRollingSound(LL_MCODE_WOOD, LL_MCODE_FLESH, SND_ROLL_WOOD_FLESH); + addRollingSound(LL_MCODE_WOOD, LL_MCODE_PLASTIC, SND_ROLL_WOOD_PLASTIC); + addRollingSound(LL_MCODE_WOOD, LL_MCODE_RUBBER, SND_SLIDE_STONE_STONE_01); + addRollingSound(LL_MCODE_WOOD, LL_MCODE_LIGHT, SND_ROLL_WOOD_PLASTIC); + + addRollingSound(LL_MCODE_FLESH, LL_MCODE_FLESH, SND_SLIDE_STONE_STONE_01); + addRollingSound(LL_MCODE_FLESH, LL_MCODE_PLASTIC, SND_ROLL_FLESH_PLASTIC); + addRollingSound(LL_MCODE_FLESH, LL_MCODE_RUBBER, SND_SLIDE_STONE_STONE_01); + addRollingSound(LL_MCODE_FLESH, LL_MCODE_LIGHT, SND_ROLL_FLESH_PLASTIC); + + addRollingSound(LL_MCODE_RUBBER, LL_MCODE_RUBBER, SND_SLIDE_STONE_STONE_01); + addRollingSound(LL_MCODE_RUBBER, LL_MCODE_PLASTIC, SND_SLIDE_STONE_STONE_01); + addRollingSound(LL_MCODE_RUBBER, LL_MCODE_LIGHT, SND_SLIDE_STONE_STONE_01); + + addRollingSound(LL_MCODE_PLASTIC, LL_MCODE_PLASTIC, SND_ROLL_PLASTIC_PLASTIC); + addRollingSound(LL_MCODE_PLASTIC, LL_MCODE_LIGHT, SND_ROLL_PLASTIC_PLASTIC); + + addRollingSound(LL_MCODE_LIGHT, LL_MCODE_LIGHT, SND_ROLL_PLASTIC_PLASTIC); + } +} + +BOOL LLMaterialTable::add(U8 mcode, char* name, const LLUUID &uuid) +{ + LLMaterialInfo *infop; + + infop = new LLMaterialInfo(mcode,name,uuid); + if (!infop) return FALSE; + + // Add at the end so the order in menus matches the order in this + // file. JNC 11.30.01 + mMaterialInfoList.addDataAtEnd(infop); + + return TRUE; +} + +BOOL LLMaterialTable::addCollisionSound(U8 mcode, U8 mcode2, const LLUUID &uuid) +{ + if (mCollisionSoundMatrix && (mcode < LL_MCODE_END) && (mcode2 < LL_MCODE_END)) + { + mCollisionSoundMatrix[mcode * LL_MCODE_END + mcode2] = uuid; + if (mcode != mcode2) + { + mCollisionSoundMatrix[mcode2 * LL_MCODE_END + mcode] = uuid; + } + } + return TRUE; +} + +BOOL LLMaterialTable::addSlidingSound(U8 mcode, U8 mcode2, const LLUUID &uuid) +{ + if (mSlidingSoundMatrix && (mcode < LL_MCODE_END) && (mcode2 < LL_MCODE_END)) + { + mSlidingSoundMatrix[mcode * LL_MCODE_END + mcode2] = uuid; + if (mcode != mcode2) + { + mSlidingSoundMatrix[mcode2 * LL_MCODE_END + mcode] = uuid; + } + } + return TRUE; +} + +BOOL LLMaterialTable::addRollingSound(U8 mcode, U8 mcode2, const LLUUID &uuid) +{ + if (mRollingSoundMatrix && (mcode < LL_MCODE_END) && (mcode2 < LL_MCODE_END)) + { + mRollingSoundMatrix[mcode * LL_MCODE_END + mcode2] = uuid; + if (mcode != mcode2) + { + mRollingSoundMatrix[mcode2 * LL_MCODE_END + mcode] = uuid; + } + } + return TRUE; +} + +BOOL LLMaterialTable::addShatterSound(U8 mcode, const LLUUID &uuid) +{ + LLMaterialInfo *infop; + + for (infop = mMaterialInfoList.getFirstData(); infop != NULL; infop = mMaterialInfoList.getNextData() ) + { + if (mcode == infop->mMCode) + { + infop->mShatterSoundID = uuid; + return TRUE; + } + } + + return FALSE; +} + +BOOL LLMaterialTable::addDensity(U8 mcode, const F32 &density) +{ + LLMaterialInfo *infop; + + for (infop = mMaterialInfoList.getFirstData(); infop != NULL; infop = mMaterialInfoList.getNextData() ) + { + if (mcode == infop->mMCode) + { + infop->mDensity = density; + return TRUE; + } + } + + return FALSE; +} + +BOOL LLMaterialTable::addRestitution(U8 mcode, const F32 &restitution) +{ + LLMaterialInfo *infop; + + for (infop = mMaterialInfoList.getFirstData(); infop != NULL; infop = mMaterialInfoList.getNextData() ) + { + if (mcode == infop->mMCode) + { + infop->mRestitution = restitution; + return TRUE; + } + } + + return FALSE; +} + +BOOL LLMaterialTable::addFriction(U8 mcode, const F32 &friction) +{ + LLMaterialInfo *infop; + + for (infop = mMaterialInfoList.getFirstData(); infop != NULL; infop = mMaterialInfoList.getNextData() ) + { + if (mcode == infop->mMCode) + { + infop->mFriction = friction; + return TRUE; + } + } + + return FALSE; +} + +BOOL LLMaterialTable::addDamageAndEnergy(U8 mcode, const F32 &hp_mod, const F32 &damage_mod, const F32 &ep_mod) +{ + LLMaterialInfo *infop; + + for (infop = mMaterialInfoList.getFirstData(); infop != NULL; infop = mMaterialInfoList.getNextData() ) + { + if (mcode == infop->mMCode) + { + infop->mHPModifier = hp_mod; + infop->mDamageModifier = damage_mod; + infop->mEPModifier = ep_mod; + return TRUE; + } + } + + return FALSE; +} + +LLUUID LLMaterialTable::getDefaultTextureID(char* name) +{ + LLMaterialInfo *infop; + + for (infop = mMaterialInfoList.getFirstData(); infop != NULL; infop = mMaterialInfoList.getNextData() ) + { + if (!strcmp(name, infop->mName)) + { + return infop->mDefaultTextureID; + } + } + + return LLUUID::null; +} + + +LLUUID LLMaterialTable::getDefaultTextureID(U8 mcode) +{ + LLMaterialInfo *infop; + + mcode &= LL_MCODE_MASK; + + for (infop = mMaterialInfoList.getFirstData(); infop != NULL; infop = mMaterialInfoList.getNextData() ) + { + if (mcode == infop->mMCode) + { + return infop->mDefaultTextureID; + } + } + + return LLUUID::null; +} + + +U8 LLMaterialTable::getMCode(const char* name) +{ + LLMaterialInfo *infop; + + for (infop = mMaterialInfoList.getFirstData(); infop != NULL; infop = mMaterialInfoList.getNextData() ) + { + if (!strcmp(name, infop->mName)) + { + return infop->mMCode; + } + } + + return 0; +} + + +char* LLMaterialTable::getName(U8 mcode) +{ + LLMaterialInfo *infop; + + mcode &= LL_MCODE_MASK; + + for (infop = mMaterialInfoList.getFirstData(); infop != NULL; infop = mMaterialInfoList.getNextData() ) + { + if (mcode == infop->mMCode) + { + return infop->mName; + } + } + + return NULL; +} + + +LLUUID LLMaterialTable::getCollisionSoundUUID(U8 mcode, U8 mcode2) +{ + mcode &= LL_MCODE_MASK; + mcode2 &= LL_MCODE_MASK; + + //llinfos << "code 1: " << ((U32) mcode) << " code 2:" << ((U32) mcode2) << llendl; + if (mCollisionSoundMatrix && (mcode < LL_MCODE_END) && (mcode2 < LL_MCODE_END)) + { + return(mCollisionSoundMatrix[mcode * LL_MCODE_END + mcode2]); + } + else + { + //llinfos << "Null Sound" << llendl; + return(SND_NULL); + } +} + +LLUUID LLMaterialTable::getSlidingSoundUUID(U8 mcode, U8 mcode2) +{ + mcode &= LL_MCODE_MASK; + mcode2 &= LL_MCODE_MASK; + + if (mSlidingSoundMatrix && (mcode < LL_MCODE_END) && (mcode2 < LL_MCODE_END)) + { + return(mSlidingSoundMatrix[mcode * LL_MCODE_END + mcode2]); + } + else + { + return(SND_NULL); + } +} + +LLUUID LLMaterialTable::getRollingSoundUUID(U8 mcode, U8 mcode2) +{ + mcode &= LL_MCODE_MASK; + mcode2 &= LL_MCODE_MASK; + + if (mRollingSoundMatrix && (mcode < LL_MCODE_END) && (mcode2 < LL_MCODE_END)) + { + return(mRollingSoundMatrix[mcode * LL_MCODE_END + mcode2]); + } + else + { + return(SND_NULL); + } +} + +LLUUID LLMaterialTable::getGroundCollisionSoundUUID(U8 mcode) +{ + // Create material appropriate sounds for collisions with the ground + // For now, simply return a single sound for all materials + return SND_STONE_DIRT_02; +} + +LLUUID LLMaterialTable::getGroundSlidingSoundUUID(U8 mcode) +{ + // Create material-specific sound for sliding on ground + // For now, just return a single sound + return SND_SLIDE_STONE_STONE_01; +} + +LLUUID LLMaterialTable::getGroundRollingSoundUUID(U8 mcode) +{ + // Create material-specific sound for rolling on ground + // For now, just return a single sound + return SND_SLIDE_STONE_STONE_01; +} + +LLUUID LLMaterialTable::getCollisionParticleUUID(U8 mcode, U8 mcode2) +{ + // Returns an appropriate UUID to use as sprite at collision betweeen objects + // For now, just return a single image + return IMG_SHOT; +} + +LLUUID LLMaterialTable::getGroundCollisionParticleUUID(U8 mcode) +{ + // Returns an appropriate + // For now, just return a single sound + return IMG_SMOKE_POOF; +} + + +F32 LLMaterialTable::getDensity(U8 mcode) +{ + LLMaterialInfo *infop; + + mcode &= LL_MCODE_MASK; + for (infop = mMaterialInfoList.getFirstData(); infop != NULL; infop = mMaterialInfoList.getNextData() ) + { + if (mcode == infop->mMCode) + { + return infop->mDensity; + } + } + + return 0.f; +} + +F32 LLMaterialTable::getRestitution(U8 mcode) +{ + LLMaterialInfo *infop; + + mcode &= LL_MCODE_MASK; + for (infop = mMaterialInfoList.getFirstData(); infop != NULL; infop = mMaterialInfoList.getNextData() ) + { + if (mcode == infop->mMCode) + { + return infop->mRestitution; + } + } + + return LLMaterialTable::DEFAULT_RESTITUTION; +} + +F32 LLMaterialTable::getFriction(U8 mcode) +{ + LLMaterialInfo *infop; + + mcode &= LL_MCODE_MASK; + for (infop = mMaterialInfoList.getFirstData(); infop != NULL; infop = mMaterialInfoList.getNextData() ) + { + if (mcode == infop->mMCode) + { + return infop->mFriction; + } + } + + return LLMaterialTable::DEFAULT_FRICTION; +} + +F32 LLMaterialTable::getHPMod(U8 mcode) +{ + LLMaterialInfo *infop; + + mcode &= LL_MCODE_MASK; + for (infop = mMaterialInfoList.getFirstData(); infop != NULL; infop = mMaterialInfoList.getNextData() ) + { + if (mcode == infop->mMCode) + { + return infop->mHPModifier; + } + } + + return 1.f; +} + +F32 LLMaterialTable::getDamageMod(U8 mcode) +{ + LLMaterialInfo *infop; + + mcode &= LL_MCODE_MASK; + for (infop = mMaterialInfoList.getFirstData(); infop != NULL; infop = mMaterialInfoList.getNextData() ) + { + if (mcode == infop->mMCode) + { + return infop->mDamageModifier; + } + } + + return 1.f; +} + +F32 LLMaterialTable::getEPMod(U8 mcode) +{ + LLMaterialInfo *infop; + + mcode &= LL_MCODE_MASK; + for (infop = mMaterialInfoList.getFirstData(); infop != NULL; infop = mMaterialInfoList.getNextData() ) + { + if (mcode == infop->mMCode) + { + return infop->mEPModifier; + } + } + + return 1.f; +} + +LLUUID LLMaterialTable::getShatterSoundUUID(U8 mcode) +{ + LLMaterialInfo *infop; + + mcode &= LL_MCODE_MASK; + for (infop = mMaterialInfoList.getFirstData(); infop != NULL; infop = mMaterialInfoList.getNextData() ) + { + if (mcode == infop->mMCode) + { + return infop->mShatterSoundID; + } + } + + return SND_NULL; +} diff --git a/indra/llprimitive/llmaterialtable.h b/indra/llprimitive/llmaterialtable.h new file mode 100644 index 0000000000..7146be54cf --- /dev/null +++ b/indra/llprimitive/llmaterialtable.h @@ -0,0 +1,116 @@ +/** + * @file llmaterialtable.h + * @brief Table of material information for the viewer UI + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMATERIALTABLE_H +#define LL_LLMATERIALTABLE_H + +#include "lluuid.h" +#include "linked_lists.h" +#include "llstring.h" + +const U32 LLMATERIAL_INFO_NAME_LENGTH = 256; + +class LLMaterialInfo +{ +public: + U8 mMCode; + char mName[LLMATERIAL_INFO_NAME_LENGTH]; + LLUUID mDefaultTextureID; + LLUUID mShatterSoundID; + F32 mDensity; // kg/m^3 + F32 mFriction; + F32 mRestitution; + + // damage and energy constants + F32 mHPModifier; // modifier on mass based HP total + F32 mDamageModifier; // modifier on KE based damage + F32 mEPModifier; // modifier on mass based EP total + + LLMaterialInfo(U8 mcode, char* name, const LLUUID &uuid) + { + init(mcode,name,uuid); + }; + + void init(U8 mcode, char* name, const LLUUID &uuid) + { + mName[0] = 0; + mDensity = 1000.f; // default to 1000.0 (water) + mHPModifier = 1.f; + mDamageModifier = 1.f; + mEPModifier = 1.f; + + mMCode = mcode; + if (name) + { + LLString::copy(mName,name,LLMATERIAL_INFO_NAME_LENGTH); + } + mDefaultTextureID = uuid; + }; + + ~LLMaterialInfo() + { + }; + +}; + +class LLMaterialTable +{ +public: + LLLinkedList mMaterialInfoList; + LLUUID *mCollisionSoundMatrix; + LLUUID *mSlidingSoundMatrix; + LLUUID *mRollingSoundMatrix; + + static const F32 DEFAULT_FRICTION; + static const F32 DEFAULT_RESTITUTION; + +public: + LLMaterialTable(); + LLMaterialTable(U8); // cheat with an overloaded constructor to build our basic table + ~LLMaterialTable(); + + void initBasicTable(); + + BOOL add(U8 mcode, char* name, const LLUUID &uuid); + BOOL addCollisionSound(U8 mcode, U8 mcode2, const LLUUID &uuid); + BOOL addSlidingSound(U8 mcode, U8 mcode2, const LLUUID &uuid); + BOOL addRollingSound(U8 mcode, U8 mcode2, const LLUUID &uuid); + BOOL addShatterSound(U8 mcode, const LLUUID &uuid); + BOOL addDensity(U8 mcode, const F32 &density); + BOOL addFriction(U8 mcode, const F32 &friction); + BOOL addRestitution(U8 mcode, const F32 &restitution); + BOOL addDamageAndEnergy(U8 mcode, const F32 &hp_mod, const F32 &damage_mod, const F32 &ep_mod); + + LLUUID getDefaultTextureID(char* name); // LLUUID::null if not found + LLUUID getDefaultTextureID(U8 mcode); // LLUUID::null if not found + U8 getMCode(const char* name); // 0 if not found + char* getName(U8 mcode); + + F32 getDensity(U8 mcode); // kg/m^3, 0 if not found + F32 getFriction(U8 mcode); // havok values + F32 getRestitution(U8 mcode); // havok values + F32 getHPMod(U8 mcode); + F32 getDamageMod(U8 mcode); + F32 getEPMod(U8 mcode); + + LLUUID getCollisionSoundUUID(U8 mcode, U8 mcode2); + LLUUID getSlidingSoundUUID(U8 mcode, U8 mcode2); + LLUUID getRollingSoundUUID(U8 mcode, U8 mcode2); + LLUUID getShatterSoundUUID(U8 mcode); // LLUUID::null if not found + + LLUUID getGroundCollisionSoundUUID(U8 mcode); + LLUUID getGroundSlidingSoundUUID(U8 mcode); + LLUUID getGroundRollingSoundUUID(U8 mcode); + LLUUID getCollisionParticleUUID(U8 mcode, U8 mcode2); + LLUUID getGroundCollisionParticleUUID(U8 mcode); + + static LLMaterialTable basic; +}; + +#endif + diff --git a/indra/llprimitive/llprimitive.cpp b/indra/llprimitive/llprimitive.cpp new file mode 100644 index 0000000000..fa8010eb6b --- /dev/null +++ b/indra/llprimitive/llprimitive.cpp @@ -0,0 +1,1749 @@ +/** + * @file llprimitive.cpp + * @brief LLPrimitive base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "material_codes.h" +#include "llerror.h" +#include "message.h" +#include "llprimitive.h" +#include "llvolume.h" +#include "legacy_object_types.h" +#include "v4coloru.h" +#include "llvolumemgr.h" +#include "llstring.h" +#include "lldatapacker.h" + +/** + * exported constants + */ + +const F32 OBJECT_CUT_MIN = 0.f; +const F32 OBJECT_CUT_MAX = 1.f; +const F32 OBJECT_CUT_INC = 0.05f; +const F32 OBJECT_MIN_CUT_INC = 0.02f; +const F32 OBJECT_ROTATION_PRECISION = 0.05f; + +const F32 OBJECT_TWIST_MIN = -360.f; +const F32 OBJECT_TWIST_MAX = 360.f; +const F32 OBJECT_TWIST_INC = 18.f; + +// This is used for linear paths, +// since twist is used in a slightly different manner. +const F32 OBJECT_TWIST_LINEAR_MIN = -180.f; +const F32 OBJECT_TWIST_LINEAR_MAX = 180.f; +const F32 OBJECT_TWIST_LINEAR_INC = 9.f; + +const F32 OBJECT_MIN_HOLE_SIZE = 0.05f; +const F32 OBJECT_MAX_HOLE_SIZE_X = 1.0f; +const F32 OBJECT_MAX_HOLE_SIZE_Y = 0.5f; + +// Revolutions parameters. +const F32 OBJECT_REV_MIN = 1.0f; +const F32 OBJECT_REV_MAX = 4.0f; +const F32 OBJECT_REV_INC = 0.1f; + +// lights +const F32 LIGHT_MIN_RADIUS = 0.0f; +const F32 LIGHT_DEFAULT_RADIUS = 5.0f; +const F32 LIGHT_MAX_RADIUS = 20.0f; +const F32 LIGHT_MIN_FALLOFF = 0.0f; +const F32 LIGHT_DEFAULT_FALLOFF = 1.0f; +const F32 LIGHT_MAX_FALLOFF = 2.0f; +const F32 LIGHT_MIN_CUTOFF = 0.0f; +const F32 LIGHT_DEFAULT_CUTOFF = 0.0f; +const F32 LIGHT_MAX_CUTOFF = 180.f; + +// "Tension" => [0,10], increments of 0.1 +const F32 FLEXIBLE_OBJECT_MIN_TENSION = 0.0f; +const F32 FLEXIBLE_OBJECT_DEFAULT_TENSION = 1.0f; +const F32 FLEXIBLE_OBJECT_MAX_TENSION = 10.0f; + +// "Drag" => [0,10], increments of 0.1 +const F32 FLEXIBLE_OBJECT_MIN_AIR_FRICTION = 0.0f; +const F32 FLEXIBLE_OBJECT_DEFAULT_AIR_FRICTION = 2.0f; +const F32 FLEXIBLE_OBJECT_MAX_AIR_FRICTION = 10.0f; + +// "Gravity" = [-10,10], increments of 0.1 +const F32 FLEXIBLE_OBJECT_MIN_GRAVITY = -10.0f; +const F32 FLEXIBLE_OBJECT_DEFAULT_GRAVITY = 0.3f; +const F32 FLEXIBLE_OBJECT_MAX_GRAVITY = 10.0f; + +// "Wind" = [0,10], increments of 0.1 +const F32 FLEXIBLE_OBJECT_MIN_WIND_SENSITIVITY = 0.0f; +const F32 FLEXIBLE_OBJECT_DEFAULT_WIND_SENSITIVITY = 0.0f; +const F32 FLEXIBLE_OBJECT_MAX_WIND_SENSITIVITY = 10.0f; + +// I'll explain later... +const F32 FLEXIBLE_OBJECT_MAX_INTERNAL_TENSION_FORCE = 0.99f; + +const F32 FLEXIBLE_OBJECT_DEFAULT_LENGTH = 1.0f; +const BOOL FLEXIBLE_OBJECT_DEFAULT_USING_COLLISION_SPHERE = FALSE; +const BOOL FLEXIBLE_OBJECT_DEFAULT_RENDERING_COLLISION_SPHERE = FALSE; + + +//=============================================================== +LLPrimitive::LLPrimitive() +{ + mPrimitiveCode = 0; + + mMaterial = LL_MCODE_STONE; + mVolumep = NULL; + + mChanged = UNCHANGED; + + mPosition.setVec(0.f,0.f,0.f); + mVelocity.setVec(0.f,0.f,0.f); + mAcceleration.setVec(0.f,0.f,0.f); + + mRotation.loadIdentity(); + mAngularVelocity.setVec(0.f,0.f,0.f); + + mScale.setVec(1.f,1.f,1.f); + + mNumTEs = 0; + mTextureList = NULL; +} + +//=============================================================== +LLPrimitive::~LLPrimitive() +{ + if (mTextureList) + { + delete [] mTextureList; + mTextureList = NULL; + } + + // Cleanup handled by volume manager + if (mVolumep) + { + gVolumeMgr->cleanupVolume(mVolumep); + } + mVolumep = NULL; +} + +//=============================================================== +// static +LLPrimitive *LLPrimitive::createPrimitive(LLPCode p_code) +{ + LLPrimitive *retval = new LLPrimitive(); + + if (retval) + { + retval->init(p_code); + } + else + { + llerrs << "primitive allocation failed" << llendl; + } + + return retval; +} + +//=============================================================== +void LLPrimitive::init(LLPCode p_code) +{ + if (mNumTEs) + { + if (mTextureList) + { + delete [] mTextureList; + } + mTextureList = new LLTextureEntry[mNumTEs]; + } + + mPrimitiveCode = p_code; +} + +void LLPrimitive::setPCode(const U8 p_code) +{ + mPrimitiveCode = p_code; +} + +//=============================================================== +const LLTextureEntry * LLPrimitive::getTE(const U8 te_num) const +{ + // if we're asking for a non-existent face, return null + if (mNumTEs && (te_num< mNumTEs)) + { + return(&mTextureList[te_num]); + } + else + { + return(NULL); + } +} + +//=============================================================== +void LLPrimitive::setNumTEs(const U8 num_tes) +{ + if (num_tes == mNumTEs) + { + return; + } + + // Right now, we don't try and preserve entries when the number of faces + // changes. + + if (num_tes) + { + LLTextureEntry *new_tes; + new_tes = new LLTextureEntry[num_tes]; + U32 i; + for (i = 0; i < num_tes; i++) + { + if (i < mNumTEs) + { + new_tes[i] = mTextureList[i]; + } + else if (mNumTEs) + { + new_tes[i] = mTextureList[mNumTEs - 1]; + } + else + { + new_tes[i] = LLTextureEntry(); + } + } + delete[] mTextureList; + mTextureList = new_tes; + } + else + { + delete[] mTextureList; + mTextureList = NULL; + } + + + mNumTEs = num_tes; +} + +//=============================================================== +void LLPrimitive::setAllTETextures(const LLUUID &tex_id) +{ + U8 i; + + for (i = 0; i < mNumTEs; i++) + { + mTextureList[i].setID(tex_id); + } +} + +//=============================================================== +void LLPrimitive::setTE(const U8 index, const LLTextureEntry &te) +{ + mTextureList[index] = te; +} + +S32 LLPrimitive::setTETexture(const U8 te, const LLUUID &tex_id) +{ + // if we're asking for a non-existent face, return null + if (te >= mNumTEs) + { + llwarns << "setting non-existent te " << te << llendl + return 0; + } + + return mTextureList[te].setID(tex_id); +} + +S32 LLPrimitive::setTEColor(const U8 te, const LLColor4 &color) +{ + // if we're asking for a non-existent face, return null + if (te >= mNumTEs) + { + llwarns << "setting non-existent te " << te << llendl + return 0; + } + + return mTextureList[te].setColor(color); +} + +S32 LLPrimitive::setTEColor(const U8 te, const LLColor3 &color) +{ + // if we're asking for a non-existent face, return null + if (te >= mNumTEs) + { + llwarns << "setting non-existent te " << te << llendl + return 0; + } + + return mTextureList[te].setColor(color); +} + +S32 LLPrimitive::setTEAlpha(const U8 te, const F32 alpha) +{ + // if we're asking for a non-existent face, return null + if (te >= mNumTEs) + { + llwarns << "setting non-existent te " << te << llendl + return 0; + } + + return mTextureList[te].setAlpha(alpha); +} + +//=============================================================== +S32 LLPrimitive::setTEScale(const U8 te, const F32 s, const F32 t) +{ + // if we're asking for a non-existent face, return null + if (te >= mNumTEs) + { + llwarns << "Setting nonexistent face" << llendl; + return 0; + } + + return mTextureList[te].setScale(s,t); +} + + +// BUG: slow - done this way because texture entries have some +// voodoo related to texture coords +S32 LLPrimitive::setTEScaleS(const U8 te, const F32 s) +{ + if (te >= mNumTEs) + { + llwarns << "Setting nonexistent face" << llendl; + return 0; + } + + F32 ignore, t; + mTextureList[te].getScale(&ignore, &t); + return mTextureList[te].setScale(s,t); +} + + +// BUG: slow - done this way because texture entries have some +// voodoo related to texture coords +S32 LLPrimitive::setTEScaleT(const U8 te, const F32 t) +{ + if (te >= mNumTEs) + { + llwarns << "Setting nonexistent face" << llendl; + return 0; + } + + F32 s, ignore; + mTextureList[te].getScale(&s, &ignore); + return mTextureList[te].setScale(s,t); +} + + +//=============================================================== +S32 LLPrimitive::setTEOffset(const U8 te, const F32 s, const F32 t) +{ + // if we're asking for a non-existent face, return null + if (te >= mNumTEs) + { + llwarns << "Setting nonexistent face" << llendl; + return 0; + } + + return mTextureList[te].setOffset(s,t); +} + + +// BUG: slow - done this way because texture entries have some +// voodoo related to texture coords +S32 LLPrimitive::setTEOffsetS(const U8 te, const F32 s) +{ + if (te >= mNumTEs) + { + llwarns << "Setting nonexistent face" << llendl; + return 0; + } + + F32 ignore, t; + mTextureList[te].getOffset(&ignore, &t); + return mTextureList[te].setOffset(s,t); +} + + +// BUG: slow - done this way because texture entries have some +// voodoo related to texture coords +S32 LLPrimitive::setTEOffsetT(const U8 te, const F32 t) +{ + if (te >= mNumTEs) + { + llwarns << "Setting nonexistent face" << llendl; + return 0; + } + + F32 s, ignore; + mTextureList[te].getOffset(&s, &ignore); + return mTextureList[te].setOffset(s,t); +} + + +//=============================================================== +S32 LLPrimitive::setTERotation(const U8 te, const F32 r) +{ + // if we're asking for a non-existent face, return null + if (te >= mNumTEs) + { + llwarns << "Setting nonexistent face" << llendl; + return 0; + } + + return mTextureList[te].setRotation(r); +} + + +//=============================================================== +S32 LLPrimitive::setTEBumpShinyFullbright(const U8 te, const U8 bump) +{ + // if we're asking for a non-existent face, return null + if (te >= mNumTEs) + { + llwarns << "setting non-existent te " << te << llendl + return 0; + } + + return mTextureList[te].setBumpShinyFullbright( bump ); +} + +S32 LLPrimitive::setTEMediaTexGen(const U8 te, const U8 media) +{ + // if we're asking for a non-existent face, return null + if (te >= mNumTEs) + { + llwarns << "setting non-existent te " << te << llendl + return 0; + } + + return mTextureList[te].setMediaTexGen( media ); +} + +S32 LLPrimitive::setTEBumpmap(const U8 te, const U8 bump) +{ + // if we're asking for a non-existent face, return null + if (te >= mNumTEs) + { + llwarns << "setting non-existent te " << te << llendl + return 0; + } + + return mTextureList[te].setBumpmap( bump ); +} + +S32 LLPrimitive::setTEBumpShiny(const U8 te, const U8 bump_shiny) +{ + // if we're asking for a non-existent face, return null + if (te >= mNumTEs) + { + llwarns << "setting non-existent te " << te << llendl + return 0; + } + + return mTextureList[te].setBumpShiny( bump_shiny ); +} + +S32 LLPrimitive::setTETexGen(const U8 te, const U8 texgen) +{ + // if we're asking for a non-existent face, return null + if (te >= mNumTEs) + { + llwarns << "setting non-existent te " << te << llendl + return 0; + } + + return mTextureList[te].setTexGen( texgen ); +} + +S32 LLPrimitive::setTEShiny(const U8 te, const U8 shiny) +{ + // if we're asking for a non-existent face, return null + if (te >= mNumTEs) + { + llwarns << "setting non-existent te " << te << llendl + return 0; + } + + return mTextureList[te].setShiny( shiny ); +} + +S32 LLPrimitive::setTEFullbright(const U8 te, const U8 fullbright) +{ + // if we're asking for a non-existent face, return null + if (te >= mNumTEs) + { + llwarns << "setting non-existent te " << te << llendl + return 0; + } + + return mTextureList[te].setFullbright( fullbright ); +} + +S32 LLPrimitive::setTEMediaFlags(const U8 te, const U8 media_flags) +{ + // if we're asking for a non-existent face, return null + if (te >= mNumTEs) + { + llwarns << "setting non-existent te " << te << llendl + return 0; + } + + return mTextureList[te].setMediaFlags( media_flags ); +} + + +LLPCode LLPrimitive::legacyToPCode(const U8 legacy) +{ + LLPCode pcode = 0; + + switch (legacy) + { + /* + case BOX: + pcode = LL_PCODE_CUBE; + break; + case CYLINDER: + pcode = LL_PCODE_CYLINDER; + break; + case CONE: + pcode = LL_PCODE_CONE; + break; + case HALF_CONE: + pcode = LL_PCODE_CONE_HEMI; + break; + case HALF_CYLINDER: + pcode = LL_PCODE_CYLINDER_HEMI; + break; + case HALF_SPHERE: + pcode = LL_PCODE_SPHERE_HEMI; + break; + case PRISM: + pcode = LL_PCODE_PRISM; + break; + case PYRAMID: + pcode = LL_PCODE_PYRAMID; + break; + case SPHERE: + pcode = LL_PCODE_SPHERE; + break; + case TETRAHEDRON: + pcode = LL_PCODE_TETRAHEDRON; + break; + case DEMON: + pcode = LL_PCODE_LEGACY_DEMON; + break; + case LSL_TEST: + pcode = LL_PCODE_LEGACY_LSL_TEST; + break; + case ORACLE: + pcode = LL_PCODE_LEGACY_ORACLE; + break; + case TEXTBUBBLE: + pcode = LL_PCODE_LEGACY_TEXT_BUBBLE; + break; + case ATOR: + pcode = LL_PCODE_LEGACY_ATOR; + break; + case BASIC_SHOT: + pcode = LL_PCODE_LEGACY_SHOT; + break; + case BIG_SHOT: + pcode = LL_PCODE_LEGACY_SHOT_BIG; + break; + case BIRD: + pcode = LL_PCODE_LEGACY_BIRD; + break; + case ROCK: + pcode = LL_PCODE_LEGACY_ROCK; + break; + case SMOKE: + pcode = LL_PCODE_LEGACY_SMOKE; + break; + case SPARK: + pcode = LL_PCODE_LEGACY_SPARK; + break; + */ + case PRIMITIVE_VOLUME: + pcode = LL_PCODE_VOLUME; + break; + case GRASS: + pcode = LL_PCODE_LEGACY_GRASS; + break; + case PART_SYS: + pcode = LL_PCODE_LEGACY_PART_SYS; + break; + case PLAYER: + pcode = LL_PCODE_LEGACY_AVATAR; + break; + case TREE: + pcode = LL_PCODE_LEGACY_TREE; + break; + case TREE_NEW: + pcode = LL_PCODE_TREE_NEW; + break; + default: + llwarns << "Unknown legacy code " << legacy << "!" << llendl; + } + + return pcode; +} + +U8 LLPrimitive::pCodeToLegacy(const LLPCode pcode) +{ + U8 legacy; + switch (pcode) + { +/* + case LL_PCODE_CUBE: + legacy = BOX; + break; + case LL_PCODE_CYLINDER: + legacy = CYLINDER; + break; + case LL_PCODE_CONE: + legacy = CONE; + break; + case LL_PCODE_CONE_HEMI: + legacy = HALF_CONE; + break; + case LL_PCODE_CYLINDER_HEMI: + legacy = HALF_CYLINDER; + break; + case LL_PCODE_SPHERE_HEMI: + legacy = HALF_SPHERE; + break; + case LL_PCODE_PRISM: + legacy = PRISM; + break; + case LL_PCODE_PYRAMID: + legacy = PYRAMID; + break; + case LL_PCODE_SPHERE: + legacy = SPHERE; + break; + case LL_PCODE_TETRAHEDRON: + legacy = TETRAHEDRON; + break; + case LL_PCODE_LEGACY_ATOR: + legacy = ATOR; + break; + case LL_PCODE_LEGACY_SHOT: + legacy = BASIC_SHOT; + break; + case LL_PCODE_LEGACY_SHOT_BIG: + legacy = BIG_SHOT; + break; + case LL_PCODE_LEGACY_BIRD: + legacy = BIRD; + break; + case LL_PCODE_LEGACY_DEMON: + legacy = DEMON; + break; + case LL_PCODE_LEGACY_LSL_TEST: + legacy = LSL_TEST; + break; + case LL_PCODE_LEGACY_ORACLE: + legacy = ORACLE; + break; + case LL_PCODE_LEGACY_ROCK: + legacy = ROCK; + break; + case LL_PCODE_LEGACY_TEXT_BUBBLE: + legacy = TEXTBUBBLE; + break; + case LL_PCODE_LEGACY_SMOKE: + legacy = SMOKE; + break; + case LL_PCODE_LEGACY_SPARK: + legacy = SPARK; + break; +*/ + case LL_PCODE_VOLUME: + legacy = PRIMITIVE_VOLUME; + break; + case LL_PCODE_LEGACY_GRASS: + legacy = GRASS; + break; + case LL_PCODE_LEGACY_PART_SYS: + legacy = PART_SYS; + break; + case LL_PCODE_LEGACY_AVATAR: + legacy = PLAYER; + break; + case LL_PCODE_LEGACY_TREE: + legacy = TREE; + break; + case LL_PCODE_TREE_NEW: + legacy = TREE_NEW; + break; + default: + llwarns << "Unknown pcode " << (S32)pcode << ":" << pcode << "!" << llendl; + return 0; + } + return legacy; +} + + +// static +// Don't crash or llerrs here! This function is used for debug strings. +const char * LLPrimitive::pCodeToString(const LLPCode pcode) +{ + static char pcode_string[255]; + + U8 base_code = pcode & LL_PCODE_BASE_MASK; + pcode_string[0] = 0; + if (!pcode) + { + sprintf(pcode_string, "null"); + } + else if ((base_code) == LL_PCODE_LEGACY) + { + // It's a legacy object + switch (pcode) + { + case LL_PCODE_LEGACY_GRASS: + sprintf(pcode_string, "grass"); + break; + case LL_PCODE_LEGACY_PART_SYS: + sprintf(pcode_string, "particle system"); + break; + case LL_PCODE_LEGACY_AVATAR: + sprintf(pcode_string, "avatar"); + break; + case LL_PCODE_LEGACY_TEXT_BUBBLE: + sprintf(pcode_string, "text bubble"); + break; + case LL_PCODE_LEGACY_TREE: + sprintf(pcode_string, "tree"); + break; + case LL_PCODE_TREE_NEW: + sprintf(pcode_string, "tree_new"); + break; + default: + sprintf(pcode_string, "unknown legacy pcode %i",(U32)pcode); + } + } + else + { + char shape[32]; + char mask[32]; + if (base_code == LL_PCODE_CUBE) + { + sprintf(shape, "cube"); + } + else if (base_code == LL_PCODE_CYLINDER) + { + sprintf(shape, "cylinder"); + } + else if (base_code == LL_PCODE_CONE) + { + sprintf(shape, "cone"); + } + else if (base_code == LL_PCODE_PRISM) + { + sprintf(shape, "prism"); + } + else if (base_code == LL_PCODE_PYRAMID) + { + sprintf(shape, "pyramid"); + } + else if (base_code == LL_PCODE_SPHERE) + { + sprintf(shape, "sphere"); + } + else if (base_code == LL_PCODE_TETRAHEDRON) + { + sprintf(shape, "tetrahedron"); + } + else if (base_code == LL_PCODE_VOLUME) + { + sprintf(shape, "volume"); + } + else if (base_code == LL_PCODE_APP) + { + sprintf(shape, "app"); + } + else + { + llwarns << "Unknown base mask for pcode: " << base_code << llendl; + } + + U8 mask_code = pcode & (~LL_PCODE_BASE_MASK); + if (base_code == LL_PCODE_APP) + { + sprintf(mask, "%x", mask_code); + } + else if (mask_code & LL_PCODE_HEMI_MASK) + { + sprintf(mask, "hemi"); + } + else if (mask != 0) + { + sprintf(mask, "%x", mask_code); + } + else + { + mask[0] = 0; + } + + if (mask[0]) + { + sprintf(pcode_string, "%s-%s", shape, mask); + } + else + { + sprintf(pcode_string, "%s", shape); + } + } + return pcode_string; +} + + +void LLPrimitive::copyTEs(const LLPrimitive *primitivep) +{ + U32 i; + if (primitivep->getNumTEs() != getNumTEs()) + { + llwarns << "Primitives don't have same number of TE's" << llendl; + } + U32 num_tes = llmin(primitivep->getNumTEs(), getNumTEs()); + for (i = 0; i < num_tes; i++) + { + const LLTextureEntry *tep = primitivep->getTE(i); + F32 s, t; + setTETexture(i, tep->getID()); + setTEColor(i, tep->getColor()); + tep->getScale(&s, &t); + setTEScale(i, s, t); + tep->getOffset(&s, &t); + setTEOffset(i, s, t); + setTERotation(i, tep->getRotation()); + setTEBumpShinyFullbright(i, tep->getBumpShinyFullbright()); + setTEMediaTexGen(i, tep->getMediaTexGen()); + } +} + +S32 face_index_from_id(LLFaceID face_ID, const std::vector& faceArray) +{ + S32 i; + for (i = 0; i < (S32)faceArray.size(); i++) + { + if (faceArray[i].mFaceID == face_ID) + { + return i; + } + } + return -1; +} + +BOOL LLPrimitive::setVolume(const LLVolumeParams &volume_params, const S32 detail, bool unique_volume) +{ + LLVolume *volumep; + if (unique_volume) + { + F32 volume_detail = LLVolumeLODGroup::getVolumeScaleFromDetail(detail); + if (mVolumep.notNull() && volume_params == mVolumep->getParams() && (volume_detail == mVolumep->getDetail())) + { + return FALSE; + } + volumep = new LLVolume(volume_params, volume_detail, FALSE, TRUE); + } + else + { + if (mVolumep.notNull()) + { + F32 volume_detail = LLVolumeLODGroup::getVolumeScaleFromDetail(detail); + if (volume_params == mVolumep->getParams() && (volume_detail == mVolumep->getDetail())) + { + return FALSE; + } + } + + volumep = gVolumeMgr->getVolume(volume_params, detail); + if (volumep == mVolumep) + { + gVolumeMgr->cleanupVolume( volumep ); // gVolumeMgr->getVolume() creates a reference, but we don't need a second one. + return TRUE; + } + } + + setChanged(GEOMETRY); + + + if (!mVolumep) + { + mVolumep = volumep; + //mFaceMask = mVolumep->generateFaceMask(); + setNumTEs(mVolumep->getNumFaces()); + return TRUE; + } + + U32 old_face_mask = mVolumep->mFaceMask; + + S32 face_bit = 0; + S32 cur_mask = 0; + + // grab copies of the old faces so we can determine the TE mappings... + std::vector old_faces; // list of old faces for remapping texture entries + LLTextureEntry old_tes[9]; + + for (S32 face = 0; face < mVolumep->getNumFaces(); face++) + { + old_faces.push_back(mVolumep->getProfile().mFaces[face]); + } + + for (face_bit = 0; face_bit < 9; face_bit++) + { + cur_mask = 0x1 << face_bit; + if (old_face_mask & cur_mask) + { + S32 te_index = face_index_from_id(cur_mask, old_faces); + old_tes[face_bit] = *getTE(te_index); + //llinfos << face_bit << ":" << te_index << ":" << old_tes[face_bit].getID() << llendl; + } + } + + + // build the new object + gVolumeMgr->cleanupVolume(mVolumep); + mVolumep = volumep; + + U32 new_face_mask = mVolumep->mFaceMask; + S32 i; + + /* + LLString old_mask_string; + for (i = 0; i < 9; i++) + { + if (old_face_mask & (1 << i)) + { + old_mask_string.append("1"); + } + else + { + old_mask_string.append("0"); + } + } + LLString new_mask_string; + for (i = 0; i < 9; i++) + { + if (new_face_mask & (1 << i)) + { + new_mask_string.append("1"); + } + else + { + new_mask_string.append("0"); + } + } + + llinfos << "old mask: " << old_mask_string << llendl; + llinfos << "new mask: " << new_mask_string << llendl; + */ + + + if (old_face_mask == new_face_mask) + { + // nothing to do + return TRUE; + } + + if (mVolumep->getNumFaces() == 0 && new_face_mask != 0) + { + llwarns << "Object with 0 faces found...INCORRECT!" << llendl; + setNumTEs(mVolumep->getNumFaces()); + return TRUE; + } + + + S32 face_mapping[9]; + for (face_bit = 0; face_bit < 9; face_bit++) + { + face_mapping[face_bit] = face_bit; + } + + // Generate the face-type mappings + for (face_bit = 0; face_bit < 9; face_bit++) + { + cur_mask = 0x1 << face_bit; + if (!(new_face_mask & cur_mask)) + { + // Face doesn't exist in new map. + face_mapping[face_bit] = -1; + continue; + } + else if (old_face_mask & cur_mask) + { + // Face exists in new and old map. + face_mapping[face_bit] = face_bit; + continue; + } + + // OK, how we've got a mismatch, where we have to fill a new face with one from + // the old face. + if (cur_mask & (LL_FACE_PATH_BEGIN | LL_FACE_PATH_END | LL_FACE_INNER_SIDE)) + { + // It's a top/bottom/hollow interior face. + if (old_face_mask & LL_FACE_PATH_END) + { + face_mapping[face_bit] = 1; + continue; + } + else + { + S32 cur_outer_mask = LL_FACE_OUTER_SIDE_0; + for (i = 0; i < 4; i++) + { + if (old_face_mask & cur_outer_mask) + { + face_mapping[face_bit] = 5 + i; + break; + } + cur_outer_mask <<= 1; + } + if (i == 4) + { + llwarns << "No path end or outer face in volume!" << llendl; + } + continue; + } + } + + if (cur_mask & (LL_FACE_PROFILE_BEGIN | LL_FACE_PROFILE_END)) + { + // A cut slice. Use the hollow interior if we have it. + if (old_face_mask & LL_FACE_INNER_SIDE) + { + face_mapping[face_bit] = 2; + continue; + } + + // No interior, use the bottom face. + // Could figure out which of the outer faces was nearest, but that would be harder. + if (old_face_mask & LL_FACE_PATH_END) + { + face_mapping[face_bit] = 1; + continue; + } + else + { + S32 cur_outer_mask = LL_FACE_OUTER_SIDE_0; + for (i = 0; i < 4; i++) + { + if (old_face_mask & cur_outer_mask) + { + face_mapping[face_bit] = 5 + i; + break; + } + cur_outer_mask <<= 1; + } + if (i == 4) + { + llwarns << "No path end or outer face in volume!" << llendl; + } + continue; + } + } + + // OK, the face that's missing is an outer face... + // Pull from the nearest adjacent outer face (there's always guaranteed to be one... + S32 cur_outer = face_bit - 5; + S32 min_dist = 5; + S32 min_outer_bit = -1; + S32 i; + for (i = 0; i < 4; i++) + { + if (old_face_mask & (LL_FACE_OUTER_SIDE_0 << i)) + { + S32 dist = abs(i - cur_outer); + if (dist < min_dist) + { + min_dist = dist; + min_outer_bit = i + 5; + } + } + } + if (-1 == min_outer_bit) + { + llinfos << (LLVolume *)mVolumep << llendl; + llwarns << "Bad! No outer faces, impossible!" << llendl; + } + face_mapping[face_bit] = min_outer_bit; + } + + + setNumTEs(mVolumep->getNumFaces()); + for (face_bit = 0; face_bit < 9; face_bit++) + { + cur_mask = 0x1 << face_bit; + if (new_face_mask & cur_mask) + { + if (-1 == face_mapping[face_bit]) + { + llwarns << "No mapping from old face to new face!" << llendl; + } + + S32 te_num = face_index_from_id(cur_mask, mVolumep->getProfile().mFaces); + setTE(te_num, old_tes[face_mapping[face_bit]]); + } + } + return TRUE; +} + +BOOL LLPrimitive::setMaterial(U8 material) +{ + if (material != mMaterial) + { + mMaterial = material; + return TRUE; + } + else + { + return FALSE; + } +} + +void LLPrimitive::setTEArrays(const U8 size, + const LLUUID* image_ids, + const F32* scale_s, + const F32* scale_t) +{ + S32 cur_size = size; + if (cur_size > getNumTEs()) + { + llwarns << "Trying to set more TEs than exist!" << llendl; + cur_size = getNumTEs(); + } + + S32 i; + // Copy over image information + for (i = 0; i < cur_size; i++) + { + // This is very BAD!!!!!! + if (image_ids != NULL) + { + setTETexture(i,image_ids[i]); + } + if (scale_s && scale_t) + { + setTEScale(i, scale_s[i], scale_t[i]); + } + } + + if (i < getNumTEs()) + { + cur_size--; + for (i=i; i < getNumTEs(); i++) // the i=i removes a gcc warning + { + if (image_ids != NULL) + { + setTETexture(i, image_ids[cur_size]); + } + if (scale_s && scale_t) + { + setTEScale(i, scale_s[cur_size], scale_t[cur_size]); + } + } + } +} + +const F32 LL_MAX_SCALE_S = 100.0f; +const F32 LL_MAX_SCALE_T = 100.0f; +S32 LLPrimitive::packTEField(U8 *cur_ptr, U8 *data_ptr, U8 data_size, U8 last_face_index, EMsgVariableType type) const +{ + S32 face_index; + S32 i; + U64 exception_faces; + U8 *start_loc = cur_ptr; + + htonmemcpy(cur_ptr,data_ptr + (last_face_index * data_size), type, data_size); + cur_ptr += data_size; + + for (face_index = last_face_index-1; face_index >= 0; face_index--) + { + BOOL already_sent = FALSE; + for (i = face_index+1; i <= last_face_index; i++) + { + if (!memcmp(data_ptr+(data_size *face_index), data_ptr+(data_size *i), data_size)) + { + already_sent = TRUE; + break; + } + } + + if (!already_sent) + { + exception_faces = 0; + for (i = face_index; i >= 0; i--) + { + if (!memcmp(data_ptr+(data_size *face_index), data_ptr+(data_size *i), data_size)) + { + exception_faces |= (1 << i); + } + } + + //assign exception faces to cur_ptr + if (exception_faces >= (0x1 << 7)) + { + if (exception_faces >= (0x1 << 14)) + { + if (exception_faces >= (0x1 << 21)) + { + if (exception_faces >= (0x1 << 28)) + { + *cur_ptr++ = (U8)(((exception_faces >> 28) & 0x7F) | 0x80); + } + *cur_ptr++ = (U8)(((exception_faces >> 21) & 0x7F) | 0x80); + } + *cur_ptr++ = (U8)(((exception_faces >> 14) & 0x7F) | 0x80); + } + *cur_ptr++ = (U8)(((exception_faces >> 7) & 0x7F) | 0x80); + } + + *cur_ptr++ = (U8)(exception_faces & 0x7F); + + htonmemcpy(cur_ptr,data_ptr + (face_index * data_size), type, data_size); + cur_ptr += data_size; + } + } + return (S32)(cur_ptr - start_loc); +} + +S32 LLPrimitive::unpackTEField(U8 *cur_ptr, U8 *buffer_end, U8 *data_ptr, U8 data_size, U8 face_count, EMsgVariableType type) +{ + U8 *start_loc = cur_ptr; + U64 i; + htonmemcpy(data_ptr,cur_ptr, type,data_size); + cur_ptr += data_size; + + for (i = 1; i < face_count; i++) + { + // Already unswizzled, don't need to unswizzle it again! + memcpy(data_ptr+(i*data_size),data_ptr,data_size); + } + + while ((cur_ptr < buffer_end) && (*cur_ptr != 0)) + { +// llinfos << "TE exception" << llendl; + i = 0; + while (*cur_ptr & 0x80) + { + i |= ((*cur_ptr++) & 0x7F); + i = i << 7; + } + + i |= *cur_ptr++; + + for (S32 j = 0; j < face_count; j++) + { + if (i & 0x01) + { + htonmemcpy(data_ptr+(j*data_size),cur_ptr,type,data_size); +// char foo[64]; +// sprintf(foo,"%x %x",*(data_ptr+(j*data_size)), *(data_ptr+(j*data_size)+1)); +// llinfos << "Assigning " << foo << " to face " << j << llendl; + } + i = i >> 1; + } + cur_ptr += data_size; + } + return (S32)(cur_ptr - start_loc); +} + + +// Pack information about all texture entries into container: +// { TextureEntry Variable 2 } +// Includes information about image ID, color, scale S,T, offset S,T and rotation +BOOL LLPrimitive::packTEMessage(LLMessageSystem *mesgsys) const +{ + const U32 MAX_TES = 32; + + U8 image_ids[MAX_TES*16]; + U8 colors[MAX_TES*4]; + S16 scale_s[MAX_TES]; + S16 scale_t[MAX_TES]; + S16 offset_s[MAX_TES]; + S16 offset_t[MAX_TES]; + S16 image_rot[MAX_TES]; + U8 bump[MAX_TES]; + U8 media_flags[MAX_TES]; + + const U32 MAX_TE_BUFFER = 4096; + U8 packed_buffer[MAX_TE_BUFFER]; + U8 *cur_ptr = packed_buffer; + + S32 last_face_index = getNumTEs() - 1; + + if (last_face_index > -1) + { + // ...if we hit the front, send one image id + S8 face_index; + LLColor4U coloru; + for (face_index = 0; face_index <= last_face_index; face_index++) + { + // Directly sending image_ids is not safe! + memcpy(&image_ids[face_index*16],getTE(face_index)->getID().mData,16); + + // Cast LLColor4 to LLColor4U + coloru.setVec( getTE(face_index)->getColor() ); + + // Note: This is an optimization to send common colors (1.f, 1.f, 1.f, 1.f) + // as all zeros. However, the subtraction and addition must be done in unsigned + // byte space, not in float space, otherwise off-by-one errors occur. JC + colors[4*face_index] = 255 - coloru.mV[0]; + colors[4*face_index + 1] = 255 - coloru.mV[1]; + colors[4*face_index + 2] = 255 - coloru.mV[2]; + colors[4*face_index + 3] = 255 - coloru.mV[3]; + + const LLTextureEntry* te = getTE(face_index); + scale_s[face_index] = (S16) llround(((llclamp(te->mScaleS,-LL_MAX_SCALE_S, LL_MAX_SCALE_S)-1.0f)/(LL_MAX_SCALE_S+1.f) * (F32)0x7FFF)); + scale_t[face_index] = (S16) llround(((llclamp(te->mScaleT,-LL_MAX_SCALE_T, LL_MAX_SCALE_T)-1.0f)/(LL_MAX_SCALE_T+1.f) * (F32)0x7FFF)); + offset_s[face_index] = (S16) llround((llclamp(te->mOffsetS,-1.0f,1.0f) * (F32)0x7FFF)) ; + offset_t[face_index] = (S16) llround((llclamp(te->mOffsetT,-1.0f,1.0f) * (F32)0x7FFF)) ; + image_rot[face_index] = (S16) llround(((fmod(te->mRotation, F_TWO_PI)/F_TWO_PI) * (F32)0x7FFF)); + bump[face_index] = te->getBumpShinyFullbright(); + media_flags[face_index] = te->getMediaTexGen(); +// llinfos << "BUMP pack [" << (S32)face_index << "]=" << (S32) bump[face_index] << llendl; + } + + cur_ptr += packTEField(cur_ptr, (U8 *)image_ids, sizeof(LLUUID),last_face_index, MVT_LLUUID); + *cur_ptr++ = 0; + cur_ptr += packTEField(cur_ptr, (U8 *)colors, 4 ,last_face_index, MVT_U8); + *cur_ptr++ = 0; + cur_ptr += packTEField(cur_ptr, (U8 *)scale_s, 2 ,last_face_index, MVT_S16Array); + *cur_ptr++ = 0; + cur_ptr += packTEField(cur_ptr, (U8 *)scale_t, 2 ,last_face_index, MVT_S16Array); + *cur_ptr++ = 0; + cur_ptr += packTEField(cur_ptr, (U8 *)offset_s, 2 ,last_face_index, MVT_S16Array); + *cur_ptr++ = 0; + cur_ptr += packTEField(cur_ptr, (U8 *)offset_t, 2 ,last_face_index, MVT_S16Array); + *cur_ptr++ = 0; + cur_ptr += packTEField(cur_ptr, (U8 *)image_rot, 2 ,last_face_index, MVT_S16Array); + *cur_ptr++ = 0; + cur_ptr += packTEField(cur_ptr, (U8 *)bump, 1 ,last_face_index, MVT_U8); + *cur_ptr++ = 0; + cur_ptr += packTEField(cur_ptr, (U8 *)media_flags, 1 ,last_face_index, MVT_U8); + } + mesgsys->addBinaryDataFast(_PREHASH_TextureEntry, packed_buffer, (S32)(cur_ptr - packed_buffer)); + + return FALSE; +} + + +BOOL LLPrimitive::packTEMessage(LLDataPacker &dp) const +{ + const U32 MAX_TES = 32; + + U8 image_ids[MAX_TES*16]; + U8 colors[MAX_TES*4]; + S16 scale_s[MAX_TES]; + S16 scale_t[MAX_TES]; + S16 offset_s[MAX_TES]; + S16 offset_t[MAX_TES]; + S16 image_rot[MAX_TES]; + U8 bump[MAX_TES]; + U8 media_flags[MAX_TES]; + + const U32 MAX_TE_BUFFER = 4096; + U8 packed_buffer[MAX_TE_BUFFER]; + U8 *cur_ptr = packed_buffer; + + S32 last_face_index = getNumTEs() - 1; + + if (last_face_index > -1) + { + // ...if we hit the front, send one image id + S8 face_index; + LLColor4U coloru; + for (face_index = 0; face_index <= last_face_index; face_index++) + { + // Directly sending image_ids is not safe! + memcpy(&image_ids[face_index*16],getTE(face_index)->getID().mData,16); + + // Cast LLColor4 to LLColor4U + coloru.setVec( getTE(face_index)->getColor() ); + + // Note: This is an optimization to send common colors (1.f, 1.f, 1.f, 1.f) + // as all zeros. However, the subtraction and addition must be done in unsigned + // byte space, not in float space, otherwise off-by-one errors occur. JC + colors[4*face_index] = 255 - coloru.mV[0]; + colors[4*face_index + 1] = 255 - coloru.mV[1]; + colors[4*face_index + 2] = 255 - coloru.mV[2]; + colors[4*face_index + 3] = 255 - coloru.mV[3]; + + const LLTextureEntry* te = getTE(face_index); + scale_s[face_index] = (S16) llround(((llclamp(te->mScaleS,-LL_MAX_SCALE_S, LL_MAX_SCALE_S)-1.0f)/(LL_MAX_SCALE_S+1.f) * (F32)0x7FFF)); + scale_t[face_index] = (S16) llround(((llclamp(te->mScaleT,-LL_MAX_SCALE_T, LL_MAX_SCALE_T)-1.0f)/(LL_MAX_SCALE_T+1.f) * (F32)0x7FFF)); + offset_s[face_index] = (S16) llround((llclamp(te->mOffsetS,-1.0f,1.0f) * (F32)0x7FFF)) ; + offset_t[face_index] = (S16) llround((llclamp(te->mOffsetT,-1.0f,1.0f) * (F32)0x7FFF)) ; + image_rot[face_index] = (S16) llround(((fmod(te->mRotation, F_TWO_PI)/F_TWO_PI) * (F32)0x7FFF)); + bump[face_index] = te->getBumpShinyFullbright(); + media_flags[face_index] = te->getMediaTexGen(); + +// llinfos << "BUMP pack (Datapacker) [" << (S32)face_index << "]=" << (S32) bump[face_index] << llendl; + } + + cur_ptr += packTEField(cur_ptr, (U8 *)image_ids, sizeof(LLUUID),last_face_index, MVT_LLUUID); + *cur_ptr++ = 0; + cur_ptr += packTEField(cur_ptr, (U8 *)colors, 4 ,last_face_index, MVT_U8); + *cur_ptr++ = 0; + cur_ptr += packTEField(cur_ptr, (U8 *)scale_s, 2 ,last_face_index, MVT_S16Array); + *cur_ptr++ = 0; + cur_ptr += packTEField(cur_ptr, (U8 *)scale_t, 2 ,last_face_index, MVT_S16Array); + *cur_ptr++ = 0; + cur_ptr += packTEField(cur_ptr, (U8 *)offset_s, 2 ,last_face_index, MVT_S16Array); + *cur_ptr++ = 0; + cur_ptr += packTEField(cur_ptr, (U8 *)offset_t, 2 ,last_face_index, MVT_S16Array); + *cur_ptr++ = 0; + cur_ptr += packTEField(cur_ptr, (U8 *)image_rot, 2 ,last_face_index, MVT_S16Array); + *cur_ptr++ = 0; + cur_ptr += packTEField(cur_ptr, (U8 *)bump, 1 ,last_face_index, MVT_U8); + *cur_ptr++ = 0; + cur_ptr += packTEField(cur_ptr, (U8 *)media_flags, 1 ,last_face_index, MVT_U8); + } + + dp.packBinaryData(packed_buffer, (S32)(cur_ptr - packed_buffer), "TextureEntry"); + return FALSE; +} + +S32 LLPrimitive::unpackTEMessage(LLMessageSystem *mesgsys, char *block_name) +{ + return(unpackTEMessage(mesgsys,block_name,-1)); +} + +S32 LLPrimitive::unpackTEMessage(LLMessageSystem *mesgsys, char *block_name, const S32 block_num) +{ + // use a negative block_num to indicate a single-block read (a non-variable block) + S32 retval = 0; + const U32 MAX_TES = 32; + + // Avoid construction of 32 UUIDs per call. JC + + U8 image_data[MAX_TES*16]; + U8 colors[MAX_TES*4]; + S16 scale_s[MAX_TES]; + S16 scale_t[MAX_TES]; + S16 offset_s[MAX_TES]; + S16 offset_t[MAX_TES]; + S16 image_rot[MAX_TES]; + U8 bump[MAX_TES]; + U8 media_flags[MAX_TES]; + + const U32 MAX_TE_BUFFER = 4096; + U8 packed_buffer[MAX_TE_BUFFER]; + U8 *cur_ptr = packed_buffer; + + U32 size; + U32 face_count = 0; + + if (block_num < 0) + { + size = mesgsys->getSizeFast(block_name, _PREHASH_TextureEntry); + } + else + { + size = mesgsys->getSizeFast(block_name, block_num, _PREHASH_TextureEntry); + } + + if (size == 0) + { + return retval; + } + + if (block_num < 0) + { + mesgsys->getBinaryDataFast(block_name, _PREHASH_TextureEntry, packed_buffer, 0, 0, MAX_TE_BUFFER); + } + else + { + mesgsys->getBinaryDataFast(block_name, _PREHASH_TextureEntry, packed_buffer, 0, block_num, MAX_TE_BUFFER); + } + + face_count = getNumTEs(); + + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)image_data, 16, face_count, MVT_LLUUID); + cur_ptr++; + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)colors, 4, face_count, MVT_U8); + cur_ptr++; + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)scale_s, 2, face_count, MVT_S16Array); + cur_ptr++; + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)scale_t, 2, face_count, MVT_S16Array); + cur_ptr++; + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)offset_s, 2, face_count, MVT_S16Array); + cur_ptr++; + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)offset_t, 2, face_count, MVT_S16Array); + cur_ptr++; + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)image_rot, 2, face_count, MVT_S16Array); + cur_ptr++; + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)bump, 1, face_count, MVT_U8); + cur_ptr++; + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)media_flags, 1, face_count, MVT_U8); + + LLColor4 color; + LLColor4U coloru; + for (U32 i = 0; i < face_count; i++) + { + retval |= setTETexture(i, ((LLUUID*)image_data)[i]); + retval |= setTEScale(i, + floor((1.0f + ((((F32)scale_s[i] / (F32)0x7FFF)) * (LL_MAX_SCALE_S+1.f))) * 100.f + 0.5f) / 100.f, + floor((1.0f + ((((F32)scale_t[i] / (F32)0x7FFF)) * (LL_MAX_SCALE_T+1.f))) * 100.f + 0.5f) / 100.f); + retval |= setTEOffset(i, (F32)offset_s[i] / (F32)0x7FFF, (F32) offset_t[i] / (F32) 0x7FFF); + retval |= setTERotation(i, ((F32)image_rot[i]/ (F32)0x7FFF) * F_TWO_PI); + retval |= setTEBumpShinyFullbright(i, bump[i]); + retval |= setTEMediaTexGen(i, media_flags[i]); + coloru = LLColor4U(colors + 4*i); + + // Note: This is an optimization to send common colors (1.f, 1.f, 1.f, 1.f) + // as all zeros. However, the subtraction and addition must be done in unsigned + // byte space, not in float space, otherwise off-by-one errors occur. JC + color.mV[VRED] = F32(255 - coloru.mV[VRED]) / 255.f; + color.mV[VGREEN] = F32(255 - coloru.mV[VGREEN]) / 255.f; + color.mV[VBLUE] = F32(255 - coloru.mV[VBLUE]) / 255.f; + color.mV[VALPHA] = F32(255 - coloru.mV[VALPHA]) / 255.f; + + retval |= setTEColor(i, color); + } + + return retval; +} + +S32 LLPrimitive::unpackTEMessage(LLDataPacker &dp) +{ + // use a negative block_num to indicate a single-block read (a non-variable block) + S32 retval = 0; + const U32 MAX_TES = 32; + + // Avoid construction of 32 UUIDs per call + static LLUUID image_ids[MAX_TES]; + + U8 image_data[MAX_TES*16]; + U8 colors[MAX_TES*4]; + S16 scale_s[MAX_TES]; + S16 scale_t[MAX_TES]; + S16 offset_s[MAX_TES]; + S16 offset_t[MAX_TES]; + S16 image_rot[MAX_TES]; + U8 bump[MAX_TES]; + U8 media_flags[MAX_TES]; + + const U32 MAX_TE_BUFFER = 4096; + U8 packed_buffer[MAX_TE_BUFFER]; + U8 *cur_ptr = packed_buffer; + + S32 size; + U32 face_count = 0; + + if (!dp.unpackBinaryData(packed_buffer, size, "TextureEntry")) + { + retval = TEM_INVALID; + llwarns << "Bad texture entry block! Abort!" << llendl; + return retval; + } + + if (size == 0) + { + return retval; + } + + face_count = getNumTEs(); + U32 i; + + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)image_data, 16, face_count, MVT_LLUUID); + cur_ptr++; + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)colors, 4, face_count, MVT_U8); + cur_ptr++; + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)scale_s, 2, face_count, MVT_S16Array); + cur_ptr++; + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)scale_t, 2, face_count, MVT_S16Array); + cur_ptr++; + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)offset_s, 2, face_count, MVT_S16Array); + cur_ptr++; + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)offset_t, 2, face_count, MVT_S16Array); + cur_ptr++; + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)image_rot, 2, face_count, MVT_S16Array); + cur_ptr++; + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)bump, 1, face_count, MVT_U8); + cur_ptr++; + cur_ptr += unpackTEField(cur_ptr, packed_buffer+size, (U8 *)media_flags, 1, face_count, MVT_U8); + + for (i = 0; i < face_count; i++) + { +// llinfos << "BUMP unpack (Datapacker) [" << i << "]=" << S32(bump[i]) <mColor != mColor || + param->mRadius != mRadius || + param->mCutoff != mCutoff || + param->mFalloff != mFalloff) + { + return false; + } + return true; +} + +void LLLightParams::copy(const LLNetworkData& data) +{ + const LLLightParams *param = (LLLightParams*)&data; + mType = param->mType; + mColor = param->mColor; + mRadius = param->mRadius; + mCutoff = param->mCutoff; + mFalloff = param->mFalloff; +} + +//============================================================================ + +LLFlexibleObjectData::LLFlexibleObjectData() +{ + mSimulateLOD = FLEXIBLE_OBJECT_DEFAULT_NUM_SECTIONS; + mGravity = FLEXIBLE_OBJECT_DEFAULT_GRAVITY; + mAirFriction = FLEXIBLE_OBJECT_DEFAULT_AIR_FRICTION; + mWindSensitivity = FLEXIBLE_OBJECT_DEFAULT_WIND_SENSITIVITY; + mTension = FLEXIBLE_OBJECT_DEFAULT_TENSION; + //mUsingCollisionSphere = FLEXIBLE_OBJECT_DEFAULT_USING_COLLISION_SPHERE; + //mRenderingCollisionSphere = FLEXIBLE_OBJECT_DEFAULT_RENDERING_COLLISION_SPHERE; + mUserForce = LLVector3(0.f, 0.f, 0.f); + + mType = PARAMS_FLEXIBLE; +} + +BOOL LLFlexibleObjectData::pack(LLDataPacker &dp) const +{ + // Custom, uber-svelte pack "softness" in upper bits of tension & drag + U8 bit1 = (mSimulateLOD & 2) << 6; + U8 bit2 = (mSimulateLOD & 1) << 7; + dp.packU8((U8)(mTension*10.01f) + bit1, "tension"); + dp.packU8((U8)(mAirFriction*10.01f) + bit2, "drag"); + dp.packU8((U8)((mGravity+10.f)*10.01f), "gravity"); + dp.packU8((U8)(mWindSensitivity*10.01f), "wind"); + dp.packVector3(mUserForce, "userforce"); + return TRUE; +} + +BOOL LLFlexibleObjectData::unpack(LLDataPacker &dp) +{ + U8 tension, friction, gravity, wind; + U8 bit1, bit2; + dp.unpackU8(tension, "tension"); bit1 = (tension >> 6) & 2; + mTension = ((F32)(tension&0x7f))/10.f; + dp.unpackU8(friction, "drag"); bit2 = (friction >> 7) & 1; + mAirFriction = ((F32)(friction&0x7f))/10.f; + mSimulateLOD = bit1 | bit2; + dp.unpackU8(gravity, "gravity"); mGravity = ((F32)gravity)/10.f - 10.f; + dp.unpackU8(wind, "wind"); mWindSensitivity = ((F32)wind)/10.f; + if (dp.hasNext()) + { + dp.unpackVector3(mUserForce, "userforce"); + } + else + { + mUserForce.setVec(0.f, 0.f, 0.f); + } + return TRUE; +} + +bool LLFlexibleObjectData::operator==(const LLNetworkData& data) const +{ + if (data.mType != PARAMS_FLEXIBLE) + { + return false; + } + LLFlexibleObjectData *flex_data = (LLFlexibleObjectData*)&data; + return (mSimulateLOD == flex_data->mSimulateLOD && + mGravity == flex_data->mGravity && + mAirFriction == flex_data->mAirFriction && + mWindSensitivity == flex_data->mWindSensitivity && + mTension == flex_data->mTension && + mUserForce == flex_data->mUserForce); + //mUsingCollisionSphere == flex_data->mUsingCollisionSphere && + //mRenderingCollisionSphere == flex_data->mRenderingCollisionSphere +} + +void LLFlexibleObjectData::copy(const LLNetworkData& data) +{ + const LLFlexibleObjectData *flex_data = (LLFlexibleObjectData*)&data; + mSimulateLOD = flex_data->mSimulateLOD; + mGravity = flex_data->mGravity; + mAirFriction = flex_data->mAirFriction; + mWindSensitivity = flex_data->mWindSensitivity; + mTension = flex_data->mTension; + mUserForce = flex_data->mUserForce; + //mUsingCollisionSphere = flex_data->mUsingCollisionSphere; + //mRenderingCollisionSphere = flex_data->mRenderingCollisionSphere; +} diff --git a/indra/llprimitive/llprimitive.h b/indra/llprimitive/llprimitive.h new file mode 100644 index 0000000000..3ad96bf6e1 --- /dev/null +++ b/indra/llprimitive/llprimitive.h @@ -0,0 +1,510 @@ +/** + * @file llprimitive.h + * @brief LLPrimitive base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLPRIMITIVE_H +#define LL_LLPRIMITIVE_H + +#include "lluuid.h" +#include "v3math.h" +#include "xform.h" +#include "message.h" +#include "llmemory.h" +#include "llvolume.h" +#include "lltextureentry.h" + +// Moved to stdtypes.h --JC +// typedef U8 LLPCode; +class LLMessageSystem; +class LLVolumeParams; +class LLColor4; +class LLColor3; +class LLTextureEntry; +class LLDataPacker; + +enum LLGeomType // NOTE: same vals as GL Ids +{ + LLInvalid = 0, + LLLineLoop = 2, + LLLineStrip = 3, + LLTriangles = 4, + LLTriStrip = 5, + LLTriFan = 6, + LLQuads = 7, + LLQuadStrip = 8 +}; + +class LLVolume; + +/** + * exported constants + */ +extern const F32 OBJECT_CUT_MIN; +extern const F32 OBJECT_CUT_MAX; +extern const F32 OBJECT_CUT_INC; +extern const F32 OBJECT_MIN_CUT_INC; +extern const F32 OBJECT_ROTATION_PRECISION; + +extern const F32 OBJECT_TWIST_MIN; +extern const F32 OBJECT_TWIST_MAX; +extern const F32 OBJECT_TWIST_INC; + +// This is used for linear paths, +// since twist is used in a slightly different manner. +extern const F32 OBJECT_TWIST_LINEAR_MIN; +extern const F32 OBJECT_TWIST_LINEAR_MAX; +extern const F32 OBJECT_TWIST_LINEAR_INC; + +extern const F32 OBJECT_MIN_HOLE_SIZE; +extern const F32 OBJECT_MAX_HOLE_SIZE_X; +extern const F32 OBJECT_MAX_HOLE_SIZE_Y; + +// Revolutions parameters. +extern const F32 OBJECT_REV_MIN; +extern const F32 OBJECT_REV_MAX; +extern const F32 OBJECT_REV_INC; + + +//============================================================================ + +// TomY: Base class for things that pack & unpack themselves +class LLNetworkData +{ +public: + // Extra parameter IDs + enum + { + PARAMS_FLEXIBLE = 0x10, + PARAMS_LIGHT = 0x20 + }; + +public: + U16 mType; + virtual ~LLNetworkData() {}; + virtual BOOL pack(LLDataPacker &dp) const = 0; + virtual BOOL unpack(LLDataPacker &dp) = 0; + virtual bool operator==(const LLNetworkData& data) const = 0; + virtual void copy(const LLNetworkData& data) = 0; +}; + +extern const F32 LIGHT_MIN_RADIUS; +extern const F32 LIGHT_DEFAULT_RADIUS; +extern const F32 LIGHT_MAX_RADIUS; +extern const F32 LIGHT_MIN_FALLOFF; +extern const F32 LIGHT_DEFAULT_FALLOFF; +extern const F32 LIGHT_MAX_FALLOFF; +extern const F32 LIGHT_MIN_CUTOFF; +extern const F32 LIGHT_DEFAULT_CUTOFF; +extern const F32 LIGHT_MAX_CUTOFF; + +class LLLightParams : public LLNetworkData +{ +protected: + LLColor4 mColor; // alpha = intensity + F32 mRadius; + F32 mFalloff; + F32 mCutoff; + +public: + LLLightParams(); + /*virtual*/ BOOL pack(LLDataPacker &dp) const; + /*virtual*/ BOOL unpack(LLDataPacker &dp); + /*virtual*/ bool operator==(const LLNetworkData& data) const; + /*virtual*/ void copy(const LLNetworkData& data); + + void setColor(const LLColor4& color) { mColor = color; mColor.clamp(); } + void setRadius(F32 radius) { mRadius = llclamp(radius, LIGHT_MIN_RADIUS, LIGHT_MAX_RADIUS); } + void setFalloff(F32 falloff) { mFalloff = llclamp(falloff, LIGHT_MIN_FALLOFF, LIGHT_MAX_FALLOFF); } + void setCutoff(F32 cutoff) { mCutoff = llclamp(cutoff, LIGHT_MIN_CUTOFF, LIGHT_MAX_CUTOFF); } + + LLColor4 getColor() const { return mColor; } + F32 getRadius() const { return mRadius; } + F32 getFalloff() const { return mFalloff; } + F32 getCutoff() const { return mCutoff; } +}; + +//------------------------------------------------- +// This structure is also used in the part of the +// code that creates new flexible objects. +//------------------------------------------------- + +// These were made into enums so that they could be used as fixed size +// array bounds. +enum EFlexibleObjectConst +{ + // "Softness" => [0,3], increments of 1 + // Represents powers of 2: 0 -> 1, 3 -> 8 + FLEXIBLE_OBJECT_MIN_SECTIONS = 0, + FLEXIBLE_OBJECT_DEFAULT_NUM_SECTIONS = 2, + FLEXIBLE_OBJECT_MAX_SECTIONS = 3 +}; + +// "Tension" => [0,10], increments of 0.1 +extern const F32 FLEXIBLE_OBJECT_MIN_TENSION; +extern const F32 FLEXIBLE_OBJECT_DEFAULT_TENSION; +extern const F32 FLEXIBLE_OBJECT_MAX_TENSION; + +// "Drag" => [0,10], increments of 0.1 +extern const F32 FLEXIBLE_OBJECT_MIN_AIR_FRICTION; +extern const F32 FLEXIBLE_OBJECT_DEFAULT_AIR_FRICTION; +extern const F32 FLEXIBLE_OBJECT_MAX_AIR_FRICTION; + +// "Gravity" = [-10,10], increments of 0.1 +extern const F32 FLEXIBLE_OBJECT_MIN_GRAVITY; +extern const F32 FLEXIBLE_OBJECT_DEFAULT_GRAVITY; +extern const F32 FLEXIBLE_OBJECT_MAX_GRAVITY; + +// "Wind" = [0,10], increments of 0.1 +extern const F32 FLEXIBLE_OBJECT_MIN_WIND_SENSITIVITY; +extern const F32 FLEXIBLE_OBJECT_DEFAULT_WIND_SENSITIVITY; +extern const F32 FLEXIBLE_OBJECT_MAX_WIND_SENSITIVITY; + +extern const F32 FLEXIBLE_OBJECT_MAX_INTERNAL_TENSION_FORCE; + +extern const F32 FLEXIBLE_OBJECT_DEFAULT_LENGTH; +extern const BOOL FLEXIBLE_OBJECT_DEFAULT_USING_COLLISION_SPHERE; +extern const BOOL FLEXIBLE_OBJECT_DEFAULT_RENDERING_COLLISION_SPHERE; + + +class LLFlexibleObjectData : public LLNetworkData +{ +protected: + S32 mSimulateLOD; // 2^n = number of simulated sections + F32 mGravity; + F32 mAirFriction; // higher is more stable, but too much looks like it's underwater + F32 mWindSensitivity; // interacts with tension, air friction, and gravity + F32 mTension; //interacts in complex ways with other parameters + LLVector3 mUserForce; // custom user-defined force vector + //BOOL mUsingCollisionSphere; + //BOOL mRenderingCollisionSphere; + +public: + void setSimulateLOD(S32 lod) { mSimulateLOD = llclamp(lod, (S32)FLEXIBLE_OBJECT_MIN_SECTIONS, (S32)FLEXIBLE_OBJECT_MAX_SECTIONS); } + void setGravity(F32 gravity) { mGravity = llclamp(gravity, FLEXIBLE_OBJECT_MIN_GRAVITY, FLEXIBLE_OBJECT_MAX_GRAVITY); } + void setAirFriction(F32 friction) { mAirFriction = llclamp(friction, FLEXIBLE_OBJECT_MIN_AIR_FRICTION, FLEXIBLE_OBJECT_MAX_AIR_FRICTION); } + void setWindSensitivity(F32 wind) { mWindSensitivity = llclamp(wind, FLEXIBLE_OBJECT_MIN_WIND_SENSITIVITY, FLEXIBLE_OBJECT_MAX_WIND_SENSITIVITY); } + void setTension(F32 tension) { mTension = llclamp(tension, FLEXIBLE_OBJECT_MIN_TENSION, FLEXIBLE_OBJECT_MAX_TENSION); } + void setUserForce(LLVector3 &force) { mUserForce = force; } + + S32 getSimulateLOD() const { return mSimulateLOD; } + F32 getGravity() const { return mGravity; } + F32 getAirFriction() const { return mAirFriction; } + F32 getWindSensitivity() const { return mWindSensitivity; } + F32 getTension() const { return mTension; } + LLVector3 getUserForce() const { return mUserForce; } + + //------ the constructor for the structure ------------ + LLFlexibleObjectData(); + BOOL pack(LLDataPacker &dp) const; + BOOL unpack(LLDataPacker &dp); + bool operator==(const LLNetworkData& data) const; + void copy(const LLNetworkData& data); +};// end of attributes structure + +class LLPrimitive : public LLXform +{ +public: + LLPrimitive(); + virtual ~LLPrimitive(); + + static LLPrimitive *createPrimitive(LLPCode p_code); + void init(LLPCode p_code); + + void setPCode(const LLPCode pcode); + const LLVolume *getVolumeConst() const { return mVolumep; } // HACK for Windoze confusion about ostream operator in LLVolume + LLVolume *getVolume() const { return mVolumep; } + virtual BOOL setVolume(const LLVolumeParams &volume_params, const S32 detail, bool unique_volume = false); + + // Modify texture entry properties + inline BOOL validTE(const U8 te_num) const; + const LLTextureEntry *getTE(const U8 te_num) const; + + virtual void setNumTEs(const U8 num_tes); + virtual void setAllTETextures(const LLUUID &tex_id); + virtual void setTE(const U8 index, const LLTextureEntry &te); + virtual S32 setTEColor(const U8 te, const LLColor4 &color); + virtual S32 setTEColor(const U8 te, const LLColor3 &color); + virtual S32 setTEAlpha(const U8 te, const F32 alpha); + virtual S32 setTETexture(const U8 te, const LLUUID &tex_id); + virtual S32 setTEScale (const U8 te, const F32 s, const F32 t); + virtual S32 setTEScaleS(const U8 te, const F32 s); + virtual S32 setTEScaleT(const U8 te, const F32 t); + virtual S32 setTEOffset (const U8 te, const F32 s, const F32 t); + virtual S32 setTEOffsetS(const U8 te, const F32 s); + virtual S32 setTEOffsetT(const U8 te, const F32 t); + virtual S32 setTERotation(const U8 te, const F32 r); + virtual S32 setTEBumpShinyFullbright(const U8 te, const U8 bump); + virtual S32 setTEBumpShiny(const U8 te, const U8 bump); + virtual S32 setTEMediaTexGen(const U8 te, const U8 media); + virtual S32 setTEBumpmap(const U8 te, const U8 bump); + virtual S32 setTETexGen(const U8 te, const U8 texgen); + virtual S32 setTEShiny(const U8 te, const U8 shiny); + virtual S32 setTEFullbright(const U8 te, const U8 fullbright); + virtual S32 setTEMediaFlags(const U8 te, const U8 flags); + virtual BOOL setMaterial(const U8 material); // returns TRUE if material changed + + void setTEArrays(const U8 size, + const LLUUID* image_ids, + const F32* scale_s, + const F32* scale_t); + void copyTEs(const LLPrimitive *primitive); + S32 packTEField(U8 *cur_ptr, U8 *data_ptr, U8 data_size, U8 last_face_index, EMsgVariableType type) const; + S32 unpackTEField(U8 *cur_ptr, U8 *buffer_end, U8 *data_ptr, U8 data_size, U8 face_count, EMsgVariableType type); + BOOL packTEMessage(LLMessageSystem *mesgsys) const; + BOOL packTEMessage(LLDataPacker &dp) const; + S32 unpackTEMessage(LLMessageSystem *mesgsys, char *block_name); + S32 unpackTEMessage(LLMessageSystem *mesgsys, char *block_name, const S32 block_num); // Variable num of blocks + BOOL unpackTEMessage(LLDataPacker &dp); + +#ifdef CHECK_FOR_FINITE + inline void setPosition(const LLVector3& pos); + inline void setPosition(const F32 x, const F32 y, const F32 z); + inline void addPosition(const LLVector3& pos); + + inline void setAngularVelocity(const LLVector3& avel); + inline void setAngularVelocity(const F32 x, const F32 y, const F32 z); + inline void setVelocity(const LLVector3& vel); + inline void setVelocity(const F32 x, const F32 y, const F32 z); + inline void setVelocityX(const F32 x); + inline void setVelocityY(const F32 y); + inline void setVelocityZ(const F32 z); + inline void addVelocity(const LLVector3& vel); + inline void setAcceleration(const LLVector3& accel); + inline void setAcceleration(const F32 x, const F32 y, const F32 z); +#else + // Don't override the base LLXForm operators. + // Special case for setPosition. If not check-for-finite, fall through to LLXform method. + // void setPosition(F32 x, F32 y, F32 z) + // void setPosition(LLVector3) + + void setAngularVelocity(const LLVector3& avel) { mAngularVelocity = avel; } + void setAngularVelocity(const F32 x, const F32 y, const F32 z) { mAngularVelocity.setVec(x,y,z); } + void setVelocity(const LLVector3& vel) { mVelocity = vel; } + void setVelocity(const F32 x, const F32 y, const F32 z) { mVelocity.setVec(x,y,z); } + void setVelocityX(const F32 x) { mVelocity.mV[VX] = x; } + void setVelocityY(const F32 y) { mVelocity.mV[VY] = y; } + void setVelocityZ(const F32 z) { mVelocity.mV[VZ] = z; } + void addVelocity(const LLVector3& vel) { mVelocity += vel; } + void setAcceleration(const LLVector3& accel) { mAcceleration = accel; } + void setAcceleration(const F32 x, const F32 y, const F32 z) { mAcceleration.setVec(x,y,z); } +#endif + + const LLPCode getPCode() const { return mPrimitiveCode; } + const char * getPCodeString() const { return pCodeToString(mPrimitiveCode); } + const LLVector3& getAngularVelocity() const { return mAngularVelocity; } + const LLVector3& getVelocity() const { return mVelocity; } + const LLVector3& getAcceleration() const { return mAcceleration; } + const U8 getNumTEs() const { return mNumTEs; } + + const U8 getMaterial() const { return mMaterial; } + + void setVolumeType(const U8 code); + U8 getVolumeType(); + + void setTextureList(LLTextureEntry *listp); + + inline BOOL isAvatar() const; + + static const char *pCodeToString(const LLPCode pcode); + static LLPCode legacyToPCode(const U8 legacy); + static U8 pCodeToLegacy(const LLPCode pcode); + + inline static BOOL isPrimitive(const LLPCode pcode); + inline static BOOL isApp(const LLPCode pcode); + +protected: + LLPCode mPrimitiveCode; // Primitive code + LLVector3 mVelocity; // how fast are we moving? + LLVector3 mAcceleration; // are we under constant acceleration? + LLVector3 mAngularVelocity; // angular velocity + LLPointer mVolumep; + LLTextureEntry *mTextureList; // list of texture GUIDs, scales, offsets + U8 mMaterial; // Material code + U8 mNumTEs; // # of faces on the primitve +}; + +inline BOOL LLPrimitive::isAvatar() const +{ + return mPrimitiveCode == LL_PCODE_LEGACY_AVATAR; +} + +// static +inline BOOL LLPrimitive::isPrimitive(const LLPCode pcode) +{ + LLPCode base_type = pcode & LL_PCODE_BASE_MASK; + + if (base_type && (base_type < LL_PCODE_APP)) + { + return TRUE; + } + return FALSE; +} + +// static +inline BOOL LLPrimitive::isApp(const LLPCode pcode) +{ + LLPCode base_type = pcode & LL_PCODE_BASE_MASK; + + return (base_type == LL_PCODE_APP); +} + + +#ifdef CHECK_FOR_FINITE +// Special case for setPosition. If not check-for-finite, fall through to LLXform method. +void LLPrimitive::setPosition(const F32 x, const F32 y, const F32 z) +{ + if (llfinite(x) && llfinite(y) && llfinite(z)) + { + LLXform::setPosition(x, y, z); + } + else + { + llerrs << "Non Finite in LLPrimitive::setPosition(x,y,z) for " << pCodeToString(mPrimitiveCode) << llendl; + } +} + +// Special case for setPosition. If not check-for-finite, fall through to LLXform method. +void LLPrimitive::setPosition(const LLVector3& pos) +{ + if (pos.isFinite()) + { + LLXform::setPosition(pos); + } + else + { + llerrs << "Non Finite in LLPrimitive::setPosition(LLVector3) for " << pCodeToString(mPrimitiveCode) << llendl; + } +} + +void LLPrimitive::setAngularVelocity(const LLVector3& avel) +{ + if (avel.isFinite()) + { + mAngularVelocity = avel; + } + else + { + llerror("Non Finite in LLPrimitive::setAngularVelocity", 0); + } +} + +void LLPrimitive::setAngularVelocity(const F32 x, const F32 y, const F32 z) +{ + if (llfinite(x) && llfinite(y) && llfinite(z)) + { + mAngularVelocity.setVec(x,y,z); + } + else + { + llerror("Non Finite in LLPrimitive::setAngularVelocity", 0); + } +} + +void LLPrimitive::setVelocity(const LLVector3& vel) +{ + if (vel.isFinite()) + { + mVelocity = vel; + } + else + { + llerrs << "Non Finite in LLPrimitive::setVelocity(LLVector3) for " << pCodeToString(mPrimitiveCode) << llendl; + } +} + +void LLPrimitive::setVelocity(const F32 x, const F32 y, const F32 z) +{ + if (llfinite(x) && llfinite(y) && llfinite(z)) + { + mVelocity.setVec(x,y,z); + } + else + { + llerrs << "Non Finite in LLPrimitive::setVelocity(F32,F32,F32) for " << pCodeToString(mPrimitiveCode) << llendl; + } +} + +void LLPrimitive::setVelocityX(const F32 x) +{ + if (llfinite(x)) + { + mVelocity.mV[VX] = x; + } + else + { + llerror("Non Finite in LLPrimitive::setVelocityX", 0); + } +} + +void LLPrimitive::setVelocityY(const F32 y) +{ + if (llfinite(y)) + { + mVelocity.mV[VY] = y; + } + else + { + llerror("Non Finite in LLPrimitive::setVelocityY", 0); + } +} + +void LLPrimitive::setVelocityZ(const F32 z) +{ + if (llfinite(z)) + { + mVelocity.mV[VZ] = z; + } + else + { + llerror("Non Finite in LLPrimitive::setVelocityZ", 0); + } +} + +void LLPrimitive::addVelocity(const LLVector3& vel) +{ + if (vel.isFinite()) + { + mVelocity += vel; + } + else + { + llerror("Non Finite in LLPrimitive::addVelocity", 0); + } +} + +void LLPrimitive::setAcceleration(const LLVector3& accel) +{ + if (accel.isFinite()) + { + mAcceleration = accel; + } + else + { + llerrs << "Non Finite in LLPrimitive::setAcceleration(LLVector3) for " << pCodeToString(mPrimitiveCode) << llendl; + } +} + +void LLPrimitive::setAcceleration(const F32 x, const F32 y, const F32 z) +{ + if (llfinite(x) && llfinite(y) && llfinite(z)) + { + mAcceleration.setVec(x,y,z); + } + else + { + llerrs << "Non Finite in LLPrimitive::setAcceleration(F32,F32,F32) for " << pCodeToString(mPrimitiveCode) << llendl; + } +} +#endif // CHECK_FOR_FINITE + +inline BOOL LLPrimitive::validTE(const U8 te_num) const +{ + return (mNumTEs && te_num < mNumTEs); +} + +#endif + diff --git a/indra/llprimitive/lltextureanim.cpp b/indra/llprimitive/lltextureanim.cpp new file mode 100644 index 0000000000..3e2c80e782 --- /dev/null +++ b/indra/llprimitive/lltextureanim.cpp @@ -0,0 +1,221 @@ +/** + * @file lltextureanim.cpp + * @brief LLTextureAnim base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lltextureanim.h" +#include "message.h" +#include "lldatapacker.h" + +const S32 TA_BLOCK_SIZE = 16; + +LLTextureAnim::LLTextureAnim() +{ + reset(); +} + + +LLTextureAnim::~LLTextureAnim() +{ +} + + +void LLTextureAnim::reset() +{ + mMode = 0; + mFace = -1; + mSizeX = 4; + mSizeY = 4; + mStart = 0.f; + mLength = 0.f; + mRate = 1.f; +} + +BOOL LLTextureAnim::equals(const LLTextureAnim &other) const +{ + if (mMode != other.mMode) + { + return FALSE; + } + if (mFace != other.mFace) + { + return FALSE; + } + if (mSizeX != other.mSizeX) + { + return FALSE; + } + if (mSizeY != other.mSizeY) + { + return FALSE; + } + if (mStart != other.mStart) + { + return FALSE; + } + if (mLength != other.mLength) + { + return FALSE; + } + if (mRate != other.mRate) + { + return FALSE; + } + + return TRUE; +} +void LLTextureAnim::packTAMessage(LLMessageSystem *mesgsys) const +{ + U8 data[TA_BLOCK_SIZE]; + data[0] = mMode; + data[1] = mFace; + data[2] = mSizeX; + data[3] = mSizeY; + htonmemcpy(data + 4, &mStart, MVT_F32, sizeof(F32)); + htonmemcpy(data + 8, &mLength, MVT_F32, sizeof(F32)); + htonmemcpy(data + 12, &mRate, MVT_F32, sizeof(F32)); + + mesgsys->addBinaryDataFast(_PREHASH_TextureAnim, data, TA_BLOCK_SIZE); +} + + +void LLTextureAnim::packTAMessage(LLDataPacker &dp) const +{ + U8 data[TA_BLOCK_SIZE]; + data[0] = mMode; + data[1] = mFace; + data[2] = mSizeX; + data[3] = mSizeY; + htonmemcpy(data + 4, &mStart, MVT_F32, sizeof(F32)); + htonmemcpy(data + 8, &mLength, MVT_F32, sizeof(F32)); + htonmemcpy(data + 12, &mRate, MVT_F32, sizeof(F32)); + + dp.packBinaryData(data, TA_BLOCK_SIZE, "TextureAnimation"); +} + + +void LLTextureAnim::unpackTAMessage(LLMessageSystem *mesgsys, const S32 block_num) +{ + S32 size = mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_TextureAnim); + + if (size != TA_BLOCK_SIZE) + { + if (size) + { + llwarns << "Bad size " << size << " for TA block, ignoring." << llendl; + } + mMode = 0; + return; + } + + U8 data[TA_BLOCK_SIZE]; + mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_TextureAnim, data, TA_BLOCK_SIZE, block_num); + + mMode = data[0]; + mFace = data[1]; + if (mMode & LLTextureAnim::SMOOTH) + { + mSizeX = llmax((U8)0, data[2]); + mSizeY = llmax((U8)0, data[3]); + } + else + { + mSizeX = llmax((U8)1, data[2]); + mSizeY = llmax((U8)1, data[3]); + } + htonmemcpy(&mStart, data + 4, MVT_F32, sizeof(F32)); + htonmemcpy(&mLength, data + 8, MVT_F32, sizeof(F32)); + htonmemcpy(&mRate, data + 12, MVT_F32, sizeof(F32)); +} + +void LLTextureAnim::unpackTAMessage(LLDataPacker &dp) +{ + S32 size; + U8 data[TA_BLOCK_SIZE]; + dp.unpackBinaryData(data, size, "TextureAnimation"); + if (size != TA_BLOCK_SIZE) + { + if (size) + { + llwarns << "Bad size " << size << " for TA block, ignoring." << llendl; + } + mMode = 0; + return; + } + + mMode = data[0]; + mFace = data[1]; + mSizeX = llmax((U8)1, data[2]); + mSizeY = llmax((U8)1, data[3]); + htonmemcpy(&mStart, data + 4, MVT_F32, sizeof(F32)); + htonmemcpy(&mLength, data + 8, MVT_F32, sizeof(F32)); + htonmemcpy(&mRate, data + 12, MVT_F32, sizeof(F32)); +} + +LLSD LLTextureAnim::asLLSD() const +{ + LLSD sd; + sd["mode"] = mMode; + sd["face"] = mFace; + sd["sizeX"] = mSizeX; + sd["sizeY"] = mSizeY; + sd["start"] = mStart; + sd["length"] = mLength; + sd["rate"] = mRate; + return sd; +} + +bool LLTextureAnim::fromLLSD(LLSD& sd) +{ + const char *w; + w = "mode"; + if (sd.has(w)) + { + mMode = (U8)sd[w].asInteger(); + } else goto fail; + + w = "face"; + if (sd.has(w)) + { + mFace = (S8)sd[w].asInteger(); + } else goto fail; + + w = "sizeX"; + if (sd.has(w)) + { + mSizeX = (U8)sd[w].asInteger(); + } else goto fail; + + w = "sizeY"; + if (sd.has(w)) + { + mSizeY = (U8)sd[w].asInteger(); + } else goto fail; + + w = "start"; + if (sd.has(w)) + { + mStart = (F32)sd[w].asReal(); + } else goto fail; + + w = "length"; + if (sd.has(w)) + { + mLength = (F32)sd[w].asReal(); + } else goto fail; + + w = "rate"; + if (sd.has(w)) + { + mRate = (F32)sd[w].asReal(); + } else goto fail; + + return true; +fail: + return false; +} diff --git a/indra/llprimitive/lltextureanim.h b/indra/llprimitive/lltextureanim.h new file mode 100644 index 0000000000..db15642563 --- /dev/null +++ b/indra/llprimitive/lltextureanim.h @@ -0,0 +1,54 @@ +/** + * @file lltextureanim.h + * @brief LLTextureAnim base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTEXTUREANIM_H +#define LL_LLTEXTUREANIM_H + +#include "stdtypes.h" +#include "llsd.h" + +class LLMessageSystem; +class LLDataPacker; + +class LLTextureAnim +{ +public: + LLTextureAnim(); + virtual ~LLTextureAnim(); + + virtual void reset(); + void packTAMessage(LLMessageSystem *mesgsys) const; + void packTAMessage(LLDataPacker &dp) const; + void unpackTAMessage(LLMessageSystem *mesgsys, const S32 block_num); + void unpackTAMessage(LLDataPacker &dp); + BOOL equals(const LLTextureAnim &other) const; + LLSD asLLSD() const; + operator LLSD() const { return asLLSD(); } + bool fromLLSD(LLSD& sd); + + enum + { + ON = 0x01, + LOOP = 0x02, + REVERSE = 0x04, + PING_PONG = 0x08, + SMOOTH = 0x10, + ROTATE = 0x20, + SCALE = 0x40, + }; + +public: + U8 mMode; + S8 mFace; + U8 mSizeX; + U8 mSizeY; + F32 mStart; + F32 mLength; + F32 mRate; // Rate in frames per second. +}; +#endif diff --git a/indra/llprimitive/lltextureentry.cpp b/indra/llprimitive/lltextureentry.cpp new file mode 100644 index 0000000000..86952dfdb5 --- /dev/null +++ b/indra/llprimitive/lltextureentry.cpp @@ -0,0 +1,348 @@ +/** + * @file lltextureentry.cpp + * @brief LLTextureEntry base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lltextureentry.h" +#include "llsdutil.h" + +const U8 DEFAULT_BUMP_CODE = 0; // no bump or shininess + +const LLTextureEntry LLTextureEntry::null; + +//=============================================================== +LLTextureEntry::LLTextureEntry() +{ + init(LLUUID::null,1.f,1.f,0.f,0.f,0.f,DEFAULT_BUMP_CODE); +} + +LLTextureEntry::LLTextureEntry(const LLUUID& tex_id) +{ + init(tex_id,1.f,1.f,0.f,0.f,0.f,DEFAULT_BUMP_CODE); +} + +LLTextureEntry::LLTextureEntry(const LLTextureEntry &rhs) +{ + mID = rhs.mID; + mScaleS = rhs.mScaleS; + mScaleT = rhs.mScaleT; + mOffsetS = rhs.mOffsetS; + mOffsetT = rhs.mOffsetT; + mRotation = rhs.mRotation; + mColor = rhs.mColor; + mBump = rhs.mBump; + mMediaFlags = rhs.mMediaFlags; +} + +LLTextureEntry &LLTextureEntry::operator=(const LLTextureEntry &rhs) +{ + if (this != &rhs) + { + mID = rhs.mID; + mScaleS = rhs.mScaleS; + mScaleT = rhs.mScaleT; + mOffsetS = rhs.mOffsetS; + mOffsetT = rhs.mOffsetT; + mRotation = rhs.mRotation; + mColor = rhs.mColor; + mBump = rhs.mBump; + mMediaFlags = rhs.mMediaFlags; + } + + return *this; +} + +void LLTextureEntry::init(const LLUUID& tex_id, F32 scale_s, F32 scale_t, F32 offset_s, F32 offset_t, F32 rotation, U8 bump) +{ + setID(tex_id); + + mScaleS = scale_s; + mScaleT = scale_t; + mOffsetS = offset_s; + mOffsetT = offset_t; + mRotation = rotation; + mBump = bump; + mMediaFlags = 0x0; + + setColor(LLColor4(1.f, 1.f, 1.f, 1.f)); +} + +LLTextureEntry::~LLTextureEntry() +{ +} + +bool LLTextureEntry::operator!=(const LLTextureEntry &rhs) const +{ + if (mID != rhs.mID) return(true); + if (mScaleS != rhs.mScaleS) return(true); + if (mScaleT != rhs.mScaleT) return(true); + if (mOffsetS != rhs.mOffsetS) return(true); + if (mOffsetT != rhs.mOffsetT) return(true); + if (mRotation != rhs.mRotation) return(true); + if (mColor != rhs.mColor) return (true); + if (mBump != rhs.mBump) return (true); + if (mMediaFlags != rhs.mMediaFlags) return true; + return(false); +} + +bool LLTextureEntry::operator==(const LLTextureEntry &rhs) const +{ + if (mID != rhs.mID) return(false); + if (mScaleS != rhs.mScaleS) return(false); + if (mScaleT != rhs.mScaleT) return(false); + if (mOffsetS != rhs.mOffsetS) return(false); + if (mOffsetT != rhs.mOffsetT) return(false); + if (mRotation != rhs.mRotation) return(false); + if (mColor != rhs.mColor) return (false); + if (mBump != rhs.mBump) return (false); + if (mMediaFlags != rhs.mMediaFlags) return false; + return(true); +} + +LLSD LLTextureEntry::asLLSD() const +{ + LLSD sd; + + sd["imageid"] = getID(); + sd["colors"] = ll_sd_from_color4(getColor()); + sd["scales"] = mScaleS; + sd["scalet"] = mScaleT; + sd["offsets"] = mOffsetS; + sd["offsett"] = mOffsetT; + sd["imagerot"] = getRotation(); + sd["bump"] = getBumpShiny(); + sd["fullbright"] = getFullbright(); + sd["media_flags"] = getMediaTexGen(); + + return sd; +} + +bool LLTextureEntry::fromLLSD(LLSD& sd) +{ + const char *w, *x; + w = "imageid"; + if (sd.has(w)) + { + setID( sd[w] ); + } else goto fail; + w = "colors"; + if (sd.has(w)) + { + setColor( ll_color4_from_sd(sd["colors"]) ); + } else goto fail; + w = "scales"; + x = "scalet"; + if (sd.has(w) && sd.has(x)) + { + setScale( (F32)sd[w].asReal(), (F32)sd[x].asReal() ); + } else goto fail; + w = "offsets"; + x = "offsett"; + if (sd.has(w) && sd.has(x)) + { + setOffset( (F32)sd[w].asReal(), (F32)sd[x].asReal() ); + } else goto fail; + w = "imagerot"; + if (sd.has(w)) + { + setRotation( (F32)sd[w].asReal() ); + } else goto fail; + w = "bump"; + if (sd.has(w)) + { + setBumpShiny( sd[w].asInteger() ); + } else goto fail; + w = "fullbright"; + if (sd.has(w)) + { + setFullbright( sd[w].asInteger() ); + } else goto fail; + w = "media_flags"; + if (sd.has(w)) + { + setMediaTexGen( sd[w].asInteger() ); + } else goto fail; + + return true; +fail: + return false; +} + +S32 LLTextureEntry::setID(const LLUUID &tex_id) +{ + if (mID != tex_id) + { + mID = tex_id; + return TEM_CHANGE_TEXTURE; + } + return 0; +} + +S32 LLTextureEntry::setScale(F32 s, F32 t) +{ + S32 retval = 0; + + if ( (mScaleS != s) + ||(mScaleT != t)) + { + mScaleS = s; + mScaleT = t; + + retval = TEM_CHANGE_TEXTURE; + } + return retval; +} + +S32 LLTextureEntry::setColor(const LLColor4 &color) +{ + if (mColor != color) + { + mColor = color; + return TEM_CHANGE_COLOR; + } + return 0; +} + +S32 LLTextureEntry::setColor(const LLColor3 &color) +{ + if (mColor != color) + { + // This preserves alpha. + mColor.setVec(color); + return TEM_CHANGE_COLOR; + } + return 0; +} + +S32 LLTextureEntry::setAlpha(const F32 alpha) +{ + if (mColor.mV[VW] != alpha) + { + mColor.mV[VW] = alpha; + return TEM_CHANGE_COLOR; + } + return 0; +} + +S32 LLTextureEntry::setOffset(F32 s, F32 t) +{ + S32 retval = 0; + + if ( (mOffsetS != s) + ||(mOffsetT != t)) + { + mOffsetS = s; + mOffsetT = t; + + retval = TEM_CHANGE_TEXTURE; + } + return retval; +} + +S32 LLTextureEntry::setRotation(F32 theta) +{ + if (mRotation != theta) + { + mRotation = theta; + return TEM_CHANGE_TEXTURE; + } + return 0; +} + +S32 LLTextureEntry::setBumpShinyFullbright(U8 bump) +{ + if (mBump != bump) + { + mBump = bump; + return TEM_CHANGE_TEXTURE; + } + return 0; +} + +S32 LLTextureEntry::setMediaTexGen(U8 media) +{ + if (mMediaFlags != media) + { + mMediaFlags = media; + return TEM_CHANGE_TEXTURE; + } + return 0; +} + +S32 LLTextureEntry::setBumpmap(U8 bump) +{ + bump &= TEM_BUMP_MASK; + if (getBumpmap() != bump) + { + mBump &= ~TEM_BUMP_MASK; + mBump |= bump; + return TEM_CHANGE_TEXTURE; + } + return 0; +} + +S32 LLTextureEntry::setFullbright(U8 fullbright) +{ + fullbright &= TEM_FULLBRIGHT_MASK; + if (getFullbright() != fullbright) + { + mBump &= ~(TEM_FULLBRIGHT_MASK<>TEM_FULLBRIGHT_SHIFT) & TEM_FULLBRIGHT_MASK; } + U8 getShiny() const { return (mBump>>TEM_SHINY_SHIFT) & TEM_SHINY_MASK; } + U8 getBumpShiny() const { return mBump & TEM_BUMP_SHINY_MASK; } + U8 getBumpShinyFullbright() const { return mBump; } + + U8 getMediaFlags() const { return mMediaFlags & TEM_MEDIA_MASK; } + U8 getTexGen() const { return mMediaFlags & TEM_TEX_GEN_MASK; } + U8 getMediaTexGen() const { return mMediaFlags; } + + // Media flags + enum { MF_NONE = 0x0, MF_WEB_PAGE = 0x1 }; + +public: + F32 mScaleS; // S, T offset + F32 mScaleT; // S, T offset + F32 mOffsetS; // S, T offset + F32 mOffsetT; // S, T offset + F32 mRotation; // anti-clockwise rotation in rad about the bottom left corner + + static const LLTextureEntry null; +protected: + LLUUID mID; // Texture GUID + LLColor4 mColor; + U8 mBump; // Bump map, shiny, and fullbright + U8 mMediaFlags; // replace with web page, movie, etc. +}; + +#endif diff --git a/indra/llprimitive/lltreeparams.cpp b/indra/llprimitive/lltreeparams.cpp new file mode 100644 index 0000000000..ca3de36630 --- /dev/null +++ b/indra/llprimitive/lltreeparams.cpp @@ -0,0 +1,187 @@ +/** + * @file lltreeparams.cpp + * @brief implementation of the LLTreeParams class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +////////////////////////////////////////////////////////////////////// + +#include "linden_common.h" + +#include "llmath.h" + +#include "lltreeparams.h" + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + + +LLTreeParams::LLTreeParams() +{ + +// llinfos << "TREE PARAMS INITIALIZED" << llendl; + // init to basic something or other... + mShape = SR_TEND_FLAME; + mLevels = 1; + mScale = 15; + mScaleV = 0; + + mBaseSize = 0.3f; + + mRatio = 0.015f; + mRatioPower = 1.3f; + + mLobes = 0; + mLobeDepth = .1f; + + mFlare = 1.2f; + mFlarePercentage = 0.1f; + mFlareRes = 3; + + //mAttractionUp = .5f; + + mBaseSplits = 0; + + mScale0 = 2.0; + mScaleV0 = 0.0; + + // level 0 + + // scaling + mLength[0] = 1.0f; + mLengthV[0] = 0; + mTaper[0] = 1.0f; + + // stem splits + mSegSplits[0] = 0.15f; + mSplitAngle[0] = 15.0f; + mSplitAngleV[0] = 10.0f; + + mVertices[0] = 5; + + // curvature + mCurveRes[0] = 4; + mCurve[0] = 0; + mCurveV[0] = 25; + mCurveBack[0] = 0; + + // level 1 + + // scaling + mLength[1] = .3f; + mLengthV[1] = 0.05f; + mTaper[1] = 1.0f; + + // angle params + mDownAngle[0] = 60.0f; + mDownAngleV[0] = 20.0f; + mRotate[0] = 140.0f; + mRotateV[0] = 0.0f; + mBranches[0] = 35; + + mVertices[1] = 3; + + // stem splits + mSplitAngle[1] = 0.0f; + mSplitAngleV[1] = 0.0f; + mSegSplits[1] = 0.0f; + + // curvature + mCurveRes[1] = 4; + mCurve[1] = 0; + mCurveV[1] = 0; + mCurveBack[1] = 40; + + // level 2 + mLength[2] = .6f; + mLengthV[2] = .1f; + mTaper[2] = 1; + + mDownAngle[1] = 30; + mDownAngleV[1] = 10; + mRotate[1] = 140; + mRotateV[1] = 0; + + mBranches[1] = 20; + mVertices[2] = 3; + + mSplitAngle[2] = 0; + mSplitAngleV[2] = 0; + mSegSplits[2] = 0; + + mCurveRes[2] = 3; + mCurve[2] = 10; + mCurveV[2] = 150; + mCurveBack[2] = 0; + + // level 3 + mLength[3] = .4f; + mLengthV[3] = 0; + mTaper[3] = 1; + + mDownAngle[2] = 45; + mDownAngleV[2] = 10; + mRotate[2] = 140; + mRotateV[2] = 0; + + mBranches[2] = 5; + mVertices[3] = 3; + + + mSplitAngle[3] = 0; + mSplitAngleV[3] = 0; + mSegSplits[3] = 0; + + mCurveRes[3] = 2; + mCurve[3] = 0; + mCurveV[3] = 0; + mCurveBack[3] = 0; + + mLeaves = 0; + mLeafScaleX = 1.0f; + mLeafScaleY = 1.0f; + + mLeafQuality = 1.25; +} + +LLTreeParams::~LLTreeParams() +{ + +} + +F32 LLTreeParams::ShapeRatio(EShapeRatio shape, F32 ratio) +{ + switch (shape) { + case (SR_CONICAL): + return (.2f + .8f * ratio); + case (SR_SPHERICAL): + return (.2f + .8f * sinf(F_PI*ratio)); + case (SR_HEMISPHERICAL): + return (.2f + .8f * sinf(.5*F_PI*ratio)); + case (SR_CYLINDRICAL): + return (1); + case (SR_TAPERED_CYLINDRICAL): + return (.5f + .5f * ratio); + case (SR_FLAME): + if (ratio <= .7f) { + return ratio/.7f; + } else { + return ((1 - ratio)/.3f); + } + case (SR_INVERSE_CONICAL): + return (1 - .8f * ratio); + case (SR_TEND_FLAME): + if (ratio <= .7) { + return (.5f + .5f*(ratio/.7f)); + } else { + return (.5f + .5f * (1 - ratio)/.3f); + } + case (SR_ENVELOPE): + return 1; + default: + return 1; + } +} diff --git a/indra/llprimitive/lltreeparams.h b/indra/llprimitive/lltreeparams.h new file mode 100644 index 0000000000..fa55f584e3 --- /dev/null +++ b/indra/llprimitive/lltreeparams.h @@ -0,0 +1,183 @@ +/** + * @file lltreeparams.h + * @brief Implementation of the LLTreeParams class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTREEPARAMS_H +#define LL_LLTREEPARAMS_H + +/* for information about formulas associated with each type + * check the Weber + Penn paper + */ +typedef enum EShapeRatio { SR_CONICAL, SR_SPHERICAL, SR_HEMISPHERICAL, + SR_CYLINDRICAL, SR_TAPERED_CYLINDRICAL, SR_FLAME, + SR_INVERSE_CONICAL, SR_TEND_FLAME, SR_ENVELOPE}; + +const U32 TREE_BLOCK_SIZE = 16; + +const U8 MAX_NUM_LEVELS = 4; + +class LLTreeParams +{ +public: + LLTreeParams(); + virtual ~LLTreeParams(); + + static F32 ShapeRatio(EShapeRatio shape, F32 ratio); + +public: + + // Variables with an asterick (*) cannot be modified without a re-instancing the + // trunk/branches + + // Variables with an exclamation point (!) should probably not be modified outside and instead + // be tied directly to the species + + // Variables with a tilde (~) should be tied to a range specified by the + // species type but still slightly controllable by the user + + // GENERAL + + //! determines length/radius of branches on tree -- ie: general 'shape' + EShapeRatio mShape; + + //! number of recursive branch levels...limit to MAX_NUM_LEVELS + U8 mLevels; + + //~ percentage of trunk at bottom without branches + F32 mBaseSize; + + //~ the general scale + variance of tree + F32 mScale, mScaleV; + + // general scale of tree + F32 mScale0, mScaleV0; + + + + // LOBING + + //*! number of peaks in the radial distance about the perimeter + U8 mLobes; + // even numbers = obvius symmetry ... use odd numbers + + //*! magnitude of the variations as a fraction of the radius + F32 mLobeDepth; + + + + // FLARE + + //*! causes exponential expansion near base of trunk + F32 mFlare; + // scales radius base by min 1 to '1 + flare' + + //*! percentage of the height of the trunk to flair -- likely less than baseSize + F32 mFlarePercentage; + + //*! number of cross sections to make for the flair + U8 mFlareRes; + + + + // LEAVES + + //~ number of leaves to make + U8 mLeaves; + + //! scale of the leaves + F32 mLeafScaleX, mLeafScaleY; + + // quality/density of leaves + F32 mLeafQuality; + + // several params don't have level 0 values + + // BRANCHES + + //~ angle away from parent + F32 mDownAngle[MAX_NUM_LEVELS - 1]; + F32 mDownAngleV[MAX_NUM_LEVELS - 1]; + + //~ rotation around parent + F32 mRotate[MAX_NUM_LEVELS - 1]; + F32 mRotateV[MAX_NUM_LEVELS - 1]; + + //~ num branches to spawn + U8 mBranches[MAX_NUM_LEVELS - 1]; + + //~ fractional length of branch. 1 = same length as parent branch + F32 mLength[MAX_NUM_LEVELS]; + F32 mLengthV[MAX_NUM_LEVELS]; + + //!~ ratio and ratiopower determine radius/length + F32 mRatio, mRatioPower; + + //*! taper of branches + F32 mTaper[MAX_NUM_LEVELS]; + // 0 - non-tapering cylinder + // 1 - taper to a point + // 2 - taper to a spherical end + // 3 - periodic tapering (concatenated spheres) + + //! SEG SPLITTING + U8 mBaseSplits; //! num segsplits at first curve cross section of trunk + F32 mSegSplits[MAX_NUM_LEVELS]; //~ splits per cross section. 1 = 1 split per section + F32 mSplitAngle[MAX_NUM_LEVELS]; //~ angle that splits go from parent (tempered by height) + F32 mSplitAngleV[MAX_NUM_LEVELS]; //~ variance of the splits + + // CURVE + F32 mCurve[MAX_NUM_LEVELS]; //* general, 1-axis, overall curve of branch + F32 mCurveV[MAX_NUM_LEVELS]; //* curve variance at each cross section from general overall curve + U8 mCurveRes[MAX_NUM_LEVELS]; //* number of cross sections for curve + F32 mCurveBack[MAX_NUM_LEVELS]; //* curveback is amount branch curves back towards + + // vertices per cross section + U8 mVertices[MAX_NUM_LEVELS]; + + // * no longer useful with pre-instanced branches + // specifies upward tendency of branches. + //F32 mAttractionUp; + // 1 = each branch will slightly go upwards by the end of the branch + // >1 = branches tend to go upwards earlier in their length + // pruning not implemented + // Prune parameters + //F32 mPruneRatio; + //F32 mPruneWidth, mPruneWidthPeak; + //F32 mPrunePowerLow, mPrunePowerHigh; + + + // NETWORK MESSAGE DATA + // Below is the outline for network messages regarding trees. + // The general idea is that a user would pick a general 'tree type' (the first variable) + // and then several 'open ended' variables like 'branchiness' and 'leafiness'. + // The effect that each of these general user variables would then affect the actual + // tree parameters (like # branches, # segsplits) in different ways depending on + // the tree type selected. Essentially, each tree type should have a formula + // that expands the 'leafiness' and 'branchiness' user variables into actual + // values for the tree parameters. + + // These formulas aren't made yet and will certainly require some tuning. The + // estimates below for the # bits required seems like a good guesstimate. + + // VARIABLE - # bits (range) - VARIABLES AFFECTED + // tree type - 5 bits (32) - + // branches - 6 bits (64) - numBranches + // splits - 6 bits (64) - segsplits + // leafiness - 3 bits (8) - numLeaves + // branch spread - 5 bits (32) - splitAngle(V), rotate(V) + // angle - 5 bits (32) - downAngle(V) + // branch length - 6 bits (64) - branchlength(V) + // randomness - 7 bits (128) - percentage for randomness of the (V)'s + // basesize - 5 bits (32) - basesize + + // total - 48 bits + + //U8 mNetSpecies; + +}; + +#endif diff --git a/indra/llprimitive/llvolumemessage.cpp b/indra/llprimitive/llvolumemessage.cpp new file mode 100644 index 0000000000..d2f1e12526 --- /dev/null +++ b/indra/llprimitive/llvolumemessage.cpp @@ -0,0 +1,534 @@ +/** + * @file llvolumemessage.cpp + * @brief LLVolumeMessage base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "message.h" +#include "llvolumemessage.h" +#include "lldatapacker.h" + +//============================================================================ + +// LLVolumeMessage is just a wrapper class; all members are static + +//============================================================================ + +bool LLVolumeMessage::packProfileParams( + const LLProfileParams* params, + LLMessageSystem *mesgsys) +{ + // Default to cylinder + static LLProfileParams defaultparams(LL_PCODE_PROFILE_CIRCLE, U8(0), U8(0), U8(0)); + + if (!params) + params = &defaultparams; + + U8 tempU8; + tempU8 = params->getCurveType(); + mesgsys->addU8Fast(_PREHASH_ProfileCurve, tempU8); + + tempU8 = (U8) llround( params->getBegin() / CUT_QUANTA); + mesgsys->addU8Fast(_PREHASH_ProfileBegin, tempU8); + + tempU8 = 200 - (U8) llround(params->getEnd() / CUT_QUANTA); + mesgsys->addU8Fast(_PREHASH_ProfileEnd, tempU8); + + tempU8 = (S8) llround(params->getHollow() / SHEAR_QUANTA); + mesgsys->addU8Fast(_PREHASH_ProfileHollow, tempU8); + + return true; +} + +bool LLVolumeMessage::packProfileParams( + const LLProfileParams* params, + LLDataPacker &dp) +{ + // Default to cylinder + static LLProfileParams defaultparams(LL_PCODE_PROFILE_CIRCLE, U8(0), U8(0), U8(0)); + + if (!params) + params = &defaultparams; + + U8 tempU8; + tempU8 = params->getCurveType(); + dp.packU8(tempU8, "Curve"); + + tempU8 = (U8) llround( params->getBegin() / CUT_QUANTA); + dp.packU8(tempU8, "Begin"); + + tempU8 = 200 - (U8) llround(params->getEnd() / CUT_QUANTA); + dp.packU8(tempU8, "End"); + + tempU8 = (S8) llround(params->getHollow() / SHEAR_QUANTA); + dp.packU8(tempU8, "Hollow"); + return true; +} + +bool LLVolumeMessage::unpackProfileParams( + LLProfileParams* params, + LLMessageSystem* mesgsys, + char* block_name, + S32 block_num) +{ + bool ok = true; + U8 temp_u8; + F32 temp_f32; + + mesgsys->getU8Fast(block_name, _PREHASH_ProfileCurve, temp_u8, block_num); + params->setCurveType(temp_u8); + + mesgsys->getU8Fast(block_name, _PREHASH_ProfileBegin, temp_u8, block_num); + temp_f32 = temp_u8 * CUT_QUANTA; + if (temp_f32 > 1.f) + { + llwarns << "Profile begin out of range: " << temp_f32 + << ". Clamping to 0.0." << llendl; + temp_f32 = 0.f; + ok = false; + } + params->setBegin(temp_f32); + + mesgsys->getU8Fast(block_name, _PREHASH_ProfileEnd, temp_u8, block_num); + temp_f32 = temp_u8 * CUT_QUANTA; + if (temp_f32 > 1.f) + { + llwarns << "Profile end out of range: " << 1.f - temp_f32 + << ". Clamping to 1.0." << llendl; + temp_f32 = 1.f; + ok = false; + } + params->setEnd(1.f - temp_f32); + + mesgsys->getU8Fast(block_name, _PREHASH_ProfileHollow, temp_u8, block_num); + temp_f32 = temp_u8 * SCALE_QUANTA; + if (temp_f32 > 1.f) + { + llwarns << "Profile hollow out of range: " << temp_f32 + << ". Clamping to 0.0." << llendl; + temp_f32 = 0.f; + ok = false; + } + params->setHollow(temp_f32); + + /* + llinfos << "Unpacking Profile Block " << block_num << llendl; + llinfos << "Curve: " << (U32)getCurve() << llendl; + llinfos << "Begin: " << getBegin() << llendl; + llinfos << "End: " << getEnd() << llendl; + llinfos << "Hollow: " << getHollow() << llendl; + */ + return ok; + +} + +bool LLVolumeMessage::unpackProfileParams( + LLProfileParams* params, + LLDataPacker &dp) +{ + bool ok = true; + U8 temp_u8; + F32 temp_f32; + + dp.unpackU8(temp_u8, "Curve"); + params->setCurveType(temp_u8); + + dp.unpackU8(temp_u8, "Begin"); + temp_f32 = temp_u8 * CUT_QUANTA; + if (temp_f32 > 1.f) + { + llwarns << "Profile begin out of range: " << temp_f32 << llendl; + llwarns << "Clamping to 0.0" << llendl; + temp_f32 = 0.f; + ok = false; + } + params->setBegin(temp_f32); + + dp.unpackU8(temp_u8, "End"); + temp_f32 = temp_u8 * CUT_QUANTA; + if (temp_f32 > 1.f) + { + llwarns << "Profile end out of range: " << 1.f - temp_f32 << llendl; + llwarns << "Clamping to 1.0" << llendl; + temp_f32 = 1.f; + ok = false; + } + params->setEnd(1.f - temp_f32); + + dp.unpackU8(temp_u8, "Hollow"); + temp_f32 = temp_u8 * SCALE_QUANTA; + if (temp_f32 > 1.f) + { + llwarns << "Profile hollow out of range: " << temp_f32 << llendl; + llwarns << "Clamping to 0.0" << llendl; + temp_f32 = 0.f; + ok = false; + } + params->setHollow(temp_f32); + + return ok; +} + +//============================================================================ + +// Quantization: +// For cut begin, range is 0 to 1, quanta is 0.005, 0 maps to 0 +// For cut end, range is 0 to 1, quanta is 0.005, 1 maps to 0 +// For scale, range is 0 to 1, quanta is 0.01, 0 maps to 0, 1 maps to 100 +// For shear, range is -0.5 to 0.5, quanta is 0.01, 0 maps to 0 +// For taper, range is -1 to 1, quanta is 0.01, 0 maps to 0 +bool LLVolumeMessage::packPathParams( + const LLPathParams* params, + LLMessageSystem *mesgsys) +{ + // Default to cylinder with no cut, top same size as bottom, no shear, no twist + static LLPathParams defaultparams(LL_PCODE_PATH_LINE, U8(0), U8(0), U8(0), U8(0), U8(0), U8(0), U8(0), U8(0), U8(0), U8(0), U8(0), U8(0), 0); + if (!params) + params = &defaultparams; + + U8 curve = params->getCurveType(); + mesgsys->addU8Fast(_PREHASH_PathCurve, curve); + + U8 begin = (U8) llround(params->getBegin() / SCALE_QUANTA); + mesgsys->addU8Fast(_PREHASH_PathBegin, begin); + + U8 end = 100 - (U8) llround(params->getEnd() / SCALE_QUANTA); + mesgsys->addU8Fast(_PREHASH_PathEnd, end); + + // Avoid truncation problem with direct F32->U8 cast. + // (e.g., (U8) (0.50 / 0.01) = (U8) 49.9999999 = 49 not 50. + + U8 pack_scale_x = 200 - (U8) llround(params->getScaleX() / SCALE_QUANTA); + mesgsys->addU8Fast(_PREHASH_PathScaleX, pack_scale_x ); + + U8 pack_scale_y = 200 - (U8) llround(params->getScaleY() / SCALE_QUANTA); + mesgsys->addU8Fast(_PREHASH_PathScaleY, pack_scale_y ); + + U8 pack_shear_x = (U8) llround(params->getShearX() / SHEAR_QUANTA); + mesgsys->addU8Fast(_PREHASH_PathShearX, pack_shear_x ); + + U8 pack_shear_y = (U8) llround(params->getShearY() / SHEAR_QUANTA); + mesgsys->addU8Fast(_PREHASH_PathShearY, pack_shear_y ); + + S8 twist = (S8) llround(params->getTwist() / SCALE_QUANTA); + mesgsys->addS8Fast(_PREHASH_PathTwist, twist); + + S8 twist_begin = (S8) llround(params->getTwistBegin() / SCALE_QUANTA); + mesgsys->addS8Fast(_PREHASH_PathTwistBegin, twist_begin); + + S8 radius_offset = (S8) llround(params->getRadiusOffset() / SCALE_QUANTA); + mesgsys->addS8Fast(_PREHASH_PathRadiusOffset, radius_offset); + + S8 taper_x = (S8) llround(params->getTaperX() / TAPER_QUANTA); + mesgsys->addS8Fast(_PREHASH_PathTaperX, taper_x); + + S8 taper_y = (S8) llround(params->getTaperY() / TAPER_QUANTA); + mesgsys->addS8Fast(_PREHASH_PathTaperY, taper_y); + + U8 revolutions = (U8) llround( (params->getRevolutions() - 1.0f) / REV_QUANTA); + mesgsys->addU8Fast(_PREHASH_PathRevolutions, revolutions); + + S8 skew = (S8) llround(params->getSkew() / SCALE_QUANTA); + mesgsys->addS8Fast(_PREHASH_PathSkew, skew); + + return true; +} + +bool LLVolumeMessage::packPathParams( + const LLPathParams* params, + LLDataPacker &dp) +{ + // Default to cylinder with no cut, top same size as bottom, no shear, no twist + static LLPathParams defaultparams(LL_PCODE_PATH_LINE, U8(0), U8(0), U8(0), U8(0), U8(0), U8(0), U8(0), U8(0), U8(0), U8(0), U8(0), U8(0), 0); + if (!params) + params = &defaultparams; + + U8 curve = params->getCurveType(); + dp.packU8(curve, "Curve"); + + U8 begin = (U8) llround(params->getBegin() / SCALE_QUANTA); + dp.packU8(begin, "Begin"); + + U8 end = 100 - (U8) llround(params->getEnd() / SCALE_QUANTA); + dp.packU8(end, "End"); + + // Avoid truncation problem with direct F32->U8 cast. + // (e.g., (U8) (0.50 / 0.01) = (U8) 49.9999999 = 49 not 50. + + U8 pack_scale_x = 200 - (U8) llround(params->getScaleX() / SCALE_QUANTA); + dp.packU8(pack_scale_x, "ScaleX"); + + U8 pack_scale_y = 200 - (U8) llround(params->getScaleY() / SCALE_QUANTA); + dp.packU8(pack_scale_y, "ScaleY"); + + S8 pack_shear_x = (S8) llround(params->getShearX() / SHEAR_QUANTA); + dp.packU8(*(U8 *)&pack_shear_x, "ShearX"); + + S8 pack_shear_y = (S8) llround(params->getShearY() / SHEAR_QUANTA); + dp.packU8(*(U8 *)&pack_shear_y, "ShearY"); + + S8 twist = (S8) llround(params->getTwist() / SCALE_QUANTA); + dp.packU8(*(U8 *)&twist, "Twist"); + + S8 twist_begin = (S8) llround(params->getTwistBegin() / SCALE_QUANTA); + dp.packU8(*(U8 *)&twist_begin, "TwistBegin"); + + S8 radius_offset = (S8) llround(params->getRadiusOffset() / SCALE_QUANTA); + dp.packU8(*(U8 *)&radius_offset, "RadiusOffset"); + + S8 taper_x = (S8) llround(params->getTaperX() / TAPER_QUANTA); + dp.packU8(*(U8 *)&taper_x, "TaperX"); + + S8 taper_y = (S8) llround(params->getTaperY() / TAPER_QUANTA); + dp.packU8(*(U8 *)&taper_y, "TaperY"); + + U8 revolutions = (U8) llround( (params->getRevolutions() - 1.0f) / REV_QUANTA); + dp.packU8(*(U8 *)&revolutions, "Revolutions"); + + S8 skew = (S8) llround(params->getSkew() / SCALE_QUANTA); + dp.packU8(*(U8 *)&skew, "Skew"); + + return true; +} + +bool LLVolumeMessage::unpackPathParams( + LLPathParams* params, + LLMessageSystem* mesgsys, + char* block_name, + S32 block_num) +{ + U8 curve; + mesgsys->getU8Fast(block_name, _PREHASH_PathCurve, curve, block_num); + params->setCurveType(curve); + + U8 begin; + mesgsys->getU8Fast(block_name, _PREHASH_PathBegin, begin, block_num); + params->setBegin((F32)(begin * SCALE_QUANTA)); + + U8 end; + mesgsys->getU8Fast(block_name, _PREHASH_PathEnd, end, block_num); + params->setEnd((F32)((100 - end) * SCALE_QUANTA)); + + U8 pack_scale_x, pack_scale_y; + mesgsys->getU8Fast(block_name, _PREHASH_PathScaleX, pack_scale_x, block_num); + mesgsys->getU8Fast(block_name, _PREHASH_PathScaleY, pack_scale_y, block_num); + F32 x = (F32) (200 - pack_scale_x) * SCALE_QUANTA; + F32 y = (F32) (200 - pack_scale_y) * SCALE_QUANTA; + params->setScale( x, y ); + + S8 shear_x_quant, shear_y_quant; + mesgsys->getS8Fast(block_name, _PREHASH_PathShearX, shear_x_quant, block_num); + mesgsys->getS8Fast(block_name, _PREHASH_PathShearY, shear_y_quant, block_num); + F32 shear_x = (F32) shear_x_quant * SHEAR_QUANTA; + F32 shear_y = (F32) shear_y_quant * SHEAR_QUANTA; + params->setShear( shear_x, shear_y ); + + S8 twist; + mesgsys->getS8Fast(block_name, _PREHASH_PathTwist, twist, block_num ); + params->setTwist((F32)(twist * SCALE_QUANTA)); + + S8 twist_begin; + mesgsys->getS8Fast(block_name, _PREHASH_PathTwistBegin, twist_begin, block_num ); + params->setTwistBegin((F32)(twist_begin * SCALE_QUANTA)); + + S8 radius_offset; + mesgsys->getS8Fast(block_name, _PREHASH_PathRadiusOffset, radius_offset, block_num ); + params->setRadiusOffset((F32)(radius_offset * SCALE_QUANTA)); + + S8 taper_x_quant, taper_y_quant; + mesgsys->getS8Fast(block_name, _PREHASH_PathTaperX, taper_x_quant, block_num ); + mesgsys->getS8Fast(block_name, _PREHASH_PathTaperY, taper_y_quant, block_num ); + F32 taper_x = (F32)(taper_x_quant * TAPER_QUANTA); + F32 taper_y = (F32)(taper_y_quant * TAPER_QUANTA); + params->setTaper( taper_x, taper_y ); + + U8 revolutions; + mesgsys->getU8Fast(block_name, _PREHASH_PathRevolutions, revolutions, block_num ); + params->setRevolutions((F32)(revolutions * REV_QUANTA + 1.0f)); + + S8 skew; + mesgsys->getS8Fast(block_name, _PREHASH_PathSkew, skew, block_num ); + params->setSkew((F32)(skew * SCALE_QUANTA)); + +/* + llinfos << "Unpacking Path Block " << block_num << llendl; + llinfos << "Curve: " << (U32)params->getCurve() << llendl; + llinfos << "Begin: " << params->getBegin() << llendl; + llinfos << "End: " << params->getEnd() << llendl; + llinfos << "Scale: " << params->getScale() << llendl; + llinfos << "Twist: " << params->getTwist() << llendl; +*/ + + return true; + +} + +bool LLVolumeMessage::unpackPathParams(LLPathParams* params, LLDataPacker &dp) +{ + U8 value; + S8 svalue; + dp.unpackU8(value, "Curve"); + params->setCurveType( value ); + + dp.unpackU8(value, "Begin"); + params->setBegin((F32)(value * SCALE_QUANTA)); + + dp.unpackU8(value, "End"); + params->setEnd((F32)((100 - value) * SCALE_QUANTA)); + + dp.unpackU8(value, "ScaleX"); + F32 x = (F32) (200 - value) * SCALE_QUANTA; + dp.unpackU8(value, "ScaleY"); + F32 y = (F32) (200 - value) * SCALE_QUANTA; + params->setScale( x, y ); + + dp.unpackU8(value, "ShearX"); + svalue = *(S8 *)&value; + F32 shear_x = (F32) svalue * SHEAR_QUANTA; + dp.unpackU8(value, "ShearY"); + svalue = *(S8 *)&value; + F32 shear_y = (F32) svalue * SHEAR_QUANTA; + params->setShear( shear_x, shear_y ); + + dp.unpackU8(value, "Twist"); + svalue = *(S8 *)&value; + params->setTwist((F32)(svalue * SCALE_QUANTA)); + + dp.unpackU8(value, "TwistBegin"); + svalue = *(S8 *)&value; + params->setTwistBegin((F32)(svalue * SCALE_QUANTA)); + + dp.unpackU8(value, "RadiusOffset"); + svalue = *(S8 *)&value; + params->setRadiusOffset((F32)(svalue * SCALE_QUANTA)); + + dp.unpackU8(value, "TaperX"); + svalue = *(S8 *)&value; + params->setTaperX((F32)(svalue * TAPER_QUANTA)); + + dp.unpackU8(value, "TaperY"); + svalue = *(S8 *)&value; + params->setTaperY((F32)(svalue * TAPER_QUANTA)); + + dp.unpackU8(value, "Revolutions"); + params->setRevolutions((F32)(value * REV_QUANTA + 1.0f)); + + dp.unpackU8(value, "Skew"); + svalue = *(S8 *)&value; + params->setSkew((F32)(svalue * SCALE_QUANTA)); + + return true; +} + +//============================================================================ + +// static +bool LLVolumeMessage::constrainVolumeParams(LLVolumeParams& params) +{ + bool ok = true; + + // This is called immediately after an unpack. feed the raw data + // through the checked setters to constraint it to a valid set of + // volume params. + ok &= params.setType( + params.getProfileParams().getCurveType(), + params.getPathParams().getCurveType()); + ok &= params.setBeginAndEndS( + params.getProfileParams().getBegin(), + params.getProfileParams().getEnd()); + ok &= params.setBeginAndEndT( + params.getPathParams().getBegin(), + params.getPathParams().getEnd()); + ok &= params.setHollow(params.getProfileParams().getHollow()); + ok &= params.setTwistBegin(params.getPathParams().getTwistBegin()); + ok &= params.setTwistEnd(params.getPathParams().getTwistEnd()); + ok &= params.setRatio( + params.getPathParams().getScaleX(), + params.getPathParams().getScaleY()); + ok &= params.setShear( + params.getPathParams().getShearX(), + params.getPathParams().getShearY()); + ok &= params.setTaper( + params.getPathParams().getTaperX(), + params.getPathParams().getTaperY()); + ok &= params.setRevolutions(params.getPathParams().getRevolutions()); + ok &= params.setRadiusOffset(params.getPathParams().getRadiusOffset()); + ok &= params.setSkew(params.getPathParams().getSkew()); + if(!ok) + { + llwarns << "LLVolumeMessage::constrainVolumeParams() - " + << "forced to constrain incoming volume params." << llendl; + } + return ok; +} + +bool LLVolumeMessage::packVolumeParams(const LLVolumeParams* params, LLMessageSystem *mesgsys) +{ + // llinfos << "pack volume" << llendl; + if (params) + { + packPathParams(¶ms->getPathParams(), mesgsys); + packProfileParams(¶ms->getProfileParams(), mesgsys); + } + else + { + packPathParams(0, mesgsys); + packProfileParams(0, mesgsys); + } + return true; +} + +bool LLVolumeMessage::packVolumeParams(const LLVolumeParams* params, LLDataPacker &dp) +{ + // llinfos << "pack volume" << llendl; + if (params) + { + packPathParams(¶ms->getPathParams(), dp); + packProfileParams(¶ms->getProfileParams(), dp); + } + else + { + packPathParams(0, dp); + packProfileParams(0, dp); + } + return true; +} + +bool LLVolumeMessage::unpackVolumeParams( + LLVolumeParams* params, + LLMessageSystem* mesgsys, + char* block_name, + S32 block_num) +{ + bool ok = true; + ok &= unpackPathParams( + ¶ms->getPathParams(), + mesgsys, + block_name, + block_num); + ok &= unpackProfileParams( + ¶ms->getProfileParams(), + mesgsys, + block_name, + block_num); + ok &= constrainVolumeParams(*params); + + return ok; +} + +bool LLVolumeMessage::unpackVolumeParams( + LLVolumeParams* params, + LLDataPacker &dp) +{ + bool ok = true; + ok &= unpackPathParams(¶ms->getPathParams(), dp); + ok &= unpackProfileParams(¶ms->getProfileParams(), dp); + ok &= constrainVolumeParams(*params); + return ok; +} + +//============================================================================ diff --git a/indra/llprimitive/llvolumemessage.h b/indra/llprimitive/llvolumemessage.h new file mode 100644 index 0000000000..299d5813a0 --- /dev/null +++ b/indra/llprimitive/llvolumemessage.h @@ -0,0 +1,74 @@ +/** + * @file llvolumemessage.h + * @brief LLVolumeMessage base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLVOLUMEMESSAGE_H +#define LL_LLVOLUMEMESSAGE_H + +#include "llvolume.h" + +class LLMessageSystem; +class LLDataPacker; + +// wrapper class for some volume/message functions +class LLVolumeMessage +{ +protected: + // The profile and path params are protected since they do not do + // any kind of parameter validation or clamping. Use the public + // pack and unpack volume param methods below + + static bool packProfileParams( + const LLProfileParams* params, + LLMessageSystem* mesgsys); + static bool packProfileParams( + const LLProfileParams* params, + LLDataPacker& dp); + static bool unpackProfileParams( + LLProfileParams* params, + LLMessageSystem* mesgsys, + char* block_name, + S32 block_num = 0); + static bool unpackProfileParams(LLProfileParams* params, LLDataPacker& dp); + + static bool packPathParams( + const LLPathParams* params, + LLMessageSystem* mesgsys); + static bool packPathParams(const LLPathParams* params, LLDataPacker& dp); + static bool unpackPathParams( + LLPathParams* params, + LLMessageSystem* mesgsys, + char* block_name, + S32 block_num = 0); + static bool unpackPathParams(LLPathParams* params, LLDataPacker& dp); + +public: + /** + * @brief This method constrains any volume params to make them valid. + * + * @param[in,out] Possibly invalid params in, always valid out. + * @return Returns true if the in params were valid, and therefore + * unchanged. + */ + static bool constrainVolumeParams(LLVolumeParams& params); + + static bool packVolumeParams( + const LLVolumeParams* params, + LLMessageSystem* mesgsys); + static bool packVolumeParams( + const LLVolumeParams* params, + LLDataPacker& dp); + static bool unpackVolumeParams( + LLVolumeParams* params, + LLMessageSystem* mesgsys, + char* block_name, + S32 block_num = 0); + static bool unpackVolumeParams(LLVolumeParams* params, LLDataPacker &dp); +}; + +#endif // LL_LLVOLUMEMESSAGE_H + diff --git a/indra/llprimitive/llvolumexml.cpp b/indra/llprimitive/llvolumexml.cpp new file mode 100644 index 0000000000..3c9d4d3b39 --- /dev/null +++ b/indra/llprimitive/llvolumexml.cpp @@ -0,0 +1,57 @@ +/** + * @file llvolumexml.cpp + * @brief LLVolumeXml base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llvolumexml.h" + +//============================================================================ + +// LLVolumeXml is just a wrapper class; all members are static + +//============================================================================ + +LLXMLNode *LLVolumeXml::exportProfileParams(const LLProfileParams* params) +{ + LLXMLNode *ret = new LLXMLNode("profile", FALSE); + + ret->createChild("curve_type", TRUE)->setByteValue(1, ¶ms->getCurveType()); + ret->createChild("interval", FALSE)->setFloatValue(2, ¶ms->getBegin()); + ret->createChild("hollow", FALSE)->setFloatValue(1, ¶ms->getHollow()); + + return ret; +} + + +LLXMLNode *LLVolumeXml::exportPathParams(const LLPathParams* params) +{ + LLXMLNode *ret = new LLXMLNode("path", FALSE); + ret->createChild("curve_type", TRUE)->setByteValue(1, ¶ms->getCurveType()); + ret->createChild("interval", FALSE)->setFloatValue(2, ¶ms->getBegin()); + ret->createChild("scale", FALSE)->setFloatValue(2, params->getScale().mV); + ret->createChild("shear", FALSE)->setFloatValue(2, params->getShear().mV); + ret->createChild("twist_interval", FALSE)->setFloatValue(2, ¶ms->getTwistBegin()); + ret->createChild("radius_offset", FALSE)->setFloatValue(1, ¶ms->getRadiusOffset()); + ret->createChild("taper", FALSE)->setFloatValue(2, params->getTaper().mV); + ret->createChild("revolutions", FALSE)->setFloatValue(1, ¶ms->getRevolutions()); + ret->createChild("skew", FALSE)->setFloatValue(1, ¶ms->getSkew()); + + return ret; +} + + +LLXMLNode *LLVolumeXml::exportVolumeParams(const LLVolumeParams* params) +{ + LLXMLNode *ret = new LLXMLNode("shape", FALSE); + + exportPathParams(¶ms->getPathParams())->setParent(ret); + exportProfileParams(¶ms->getProfileParams())->setParent(ret); + + return ret; +} + diff --git a/indra/llprimitive/llvolumexml.h b/indra/llprimitive/llvolumexml.h new file mode 100644 index 0000000000..5d105f148a --- /dev/null +++ b/indra/llprimitive/llvolumexml.h @@ -0,0 +1,27 @@ +/** + * @file llvolumexml.h + * @brief LLVolumeXml base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLVOLUMEXML_H +#define LL_LLVOLUMEXML_H + +#include "llvolume.h" +#include "llxmlnode.h" + +// wrapper class for some volume/message functions +class LLVolumeXml +{ +public: + static LLXMLNode* exportProfileParams(const LLProfileParams* params); + + static LLXMLNode* exportPathParams(const LLPathParams* params); + + static LLXMLNode* exportVolumeParams(const LLVolumeParams* params); +}; + +#endif // LL_LLVOLUMEXML_H + diff --git a/indra/llprimitive/material_codes.h b/indra/llprimitive/material_codes.h new file mode 100644 index 0000000000..f9c05017c2 --- /dev/null +++ b/indra/llprimitive/material_codes.h @@ -0,0 +1,35 @@ +/** + * @file material_codes.h + * @brief Material_codes definitions + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_MATERIAL_CODES_H +#define LL_MATERIAL_CODES_H + +#include "lluuid.h" + + // material types +const U8 LL_MCODE_STONE = 0; +const U8 LL_MCODE_METAL = 1; +const U8 LL_MCODE_GLASS = 2; +const U8 LL_MCODE_WOOD = 3; +const U8 LL_MCODE_FLESH = 4; +const U8 LL_MCODE_PLASTIC = 5; +const U8 LL_MCODE_RUBBER = 6; +const U8 LL_MCODE_LIGHT = 7; +const U8 LL_MCODE_END = 8; +const U8 LL_MCODE_MASK = 0x0F; + +const LLUUID LL_DEFAULT_STONE_UUID("87c5765b-aa26-43eb-b8c6-c09a1ca6208e"); +const LLUUID LL_DEFAULT_METAL_UUID("6f3c53e9-ba60-4010-8f3e-30f51a762476"); +const LLUUID LL_DEFAULT_GLASS_UUID("b4ba225c-373f-446d-9f7e-6cb7b5cf9b3d"); +const LLUUID LL_DEFAULT_WOOD_UUID("89556747-24cb-43ed-920b-47caed15465f"); +const LLUUID LL_DEFAULT_FLESH_UUID("80736669-e4b9-450e-8890-d5169f988a50"); +const LLUUID LL_DEFAULT_PLASTIC_UUID("304fcb4e-7d33-4339-ba80-76d3d22dc11a"); +const LLUUID LL_DEFAULT_RUBBER_UUID("9fae0bc5-666d-477e-9f70-84e8556ec867"); +const LLUUID LL_DEFAULT_LIGHT_UUID("00000000-0000-0000-0000-000000000000"); + +#endif diff --git a/indra/llrender/llfontgl.cpp b/indra/llrender/llfontgl.cpp new file mode 100644 index 0000000000..547a593447 --- /dev/null +++ b/indra/llrender/llfontgl.cpp @@ -0,0 +1,1434 @@ +/** + * @file llfontgl.cpp + * @brief Wrapper around FreeType + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include + +#include "llfont.h" +#include "llfontgl.h" +#include "llgl.h" +#include "v4color.h" + +const S32 BOLD_OFFSET = 1; + +// static class members +F32 LLFontGL::sVertDPI = 96.f; +F32 LLFontGL::sHorizDPI = 96.f; +F32 LLFontGL::sScaleX = 1.f; +F32 LLFontGL::sScaleY = 1.f; +LLString LLFontGL::sAppDir; + +LLFontGL* LLFontGL::sMonospace = NULL; +LLFontGL* LLFontGL::sSansSerifSmall = NULL; +LLFontGL* LLFontGL::sSansSerif = NULL; +LLFontGL* LLFontGL::sSansSerifBig = NULL; +LLFontGL* LLFontGL::sSansSerifHuge = NULL; +LLFontGL* LLFontGL::sSansSerifBold = NULL; +LLFontList* LLFontGL::sSSFallback = NULL; +LLFontList* LLFontGL::sSSSmallFallback = NULL; +LLFontList* LLFontGL::sSSBigFallback = NULL; +LLFontList* LLFontGL::sSSHugeFallback = NULL; +LLFontList* LLFontGL::sSSBoldFallback = NULL; +LLColor4 LLFontGL::sShadowColor(0.f, 0.f, 0.f, 1.f); + +LLCoordFont LLFontGL::sCurOrigin; +std::vector LLFontGL::sOriginStack; + +LLFontGL*& gExtCharFont = LLFontGL::sSansSerif; + +const F32 EXT_X_BEARING = 1.f; +const F32 EXT_Y_BEARING = 0.f; +const F32 EXT_KERNING = 1.f; +const F32 PIXEL_BORDER_THRESHOLD = 0.0001f; +const F32 PIXEL_CORRECTION_DISTANCE = 0.01f; + +const F32 PAD_AMT = 0.5f; + +F32 llfont_round_x(F32 x) +{ + //return llfloor((x-LLFontGL::sCurOrigin.mX)/LLFontGL::sScaleX+0.5f)*LLFontGL::sScaleX+LLFontGL::sCurOrigin.mX; + //return llfloor(x/LLFontGL::sScaleX+0.5f)*LLFontGL::sScaleY; + return x; +} + +F32 llfont_round_y(F32 y) +{ + //return llfloor((y-LLFontGL::sCurOrigin.mY)/LLFontGL::sScaleY+0.5f)*LLFontGL::sScaleY+LLFontGL::sCurOrigin.mY; + //return llfloor(y+0.5f); + return y; +} + +// static +U8 LLFontGL::getStyleFromString(const LLString &style) +{ + S32 ret = 0; + if (style.find("NORMAL") != style.npos) + { + ret |= NORMAL; + } + if (style.find("BOLD") != style.npos) + { + ret |= BOLD; + } + if (style.find("ITALIC") != style.npos) + { + ret |= ITALIC; + } + if (style.find("UNDERLINE") != style.npos) + { + ret |= UNDERLINE; + } + return ret; +} + +LLFontGL::LLFontGL() + : LLFont() +{ + init(); + clearEmbeddedChars(); +} + +LLFontGL::LLFontGL(const LLFontGL &source) +{ + llerrs << "Not implemented!" << llendl; +} + +LLFontGL::~LLFontGL() +{ + mImageGLp = NULL; + mRawImageGLp = NULL; + clearEmbeddedChars(); +} + +void LLFontGL::init() +{ + if (mImageGLp.isNull()) + { + mImageGLp = new LLImageGL(FALSE); + //RN: use nearest mipmap filtering to obviate the need to do pixel-accurate positioning + mImageGLp->bind(); + mImageGLp->setMipFilterNearest(TRUE,TRUE); + } + if (mRawImageGLp.isNull()) + { + mRawImageGLp = new LLImageRaw; // Note LLFontGL owns the image, not LLFont. + } + setRawImage( mRawImageGLp ); +} + +void LLFontGL::reset() +{ + init(); + resetBitmap(); +} + +// static +LLString LLFontGL::getFontPathSystem() +{ + LLString system_path; + + // Try to figure out where the system's font files are stored. + char *system_root = NULL; +#if LL_WINDOWS + system_root = getenv("SystemRoot"); + if (!system_root) + { + llwarns << "SystemRoot not found, attempting to load fonts from default path." << llendl; + } +#endif + + if (system_root) + { + system_path = llformat("%s/fonts/", system_root); + } + else + { +#if LL_WINDOWS + // HACK for windows 98/Me + system_path = "/WINDOWS/FONTS/"; +#elif LL_DARWIN + // HACK for Mac OS X + system_path = "/System/Library/Fonts/"; +#endif + } + return system_path; +} + + +// static +LLString LLFontGL::getFontPathLocal() +{ + LLString local_path; + + // Backup files if we can't load from system fonts directory. + // We could store this in an end-user writable directory to allow + // end users to switch fonts. + if (LLFontGL::sAppDir.length()) + { + // use specified application dir to look for fonts + local_path = LLFontGL::sAppDir + "/fonts/"; + } + else + { + // assume working directory is executable directory + local_path = "./fonts/"; + } + return local_path; +} + +//static +bool LLFontGL::loadFaceFallback(LLFontList *fontlistp, const LLString& fontname, const F32 point_size) +{ + LLString local_path = getFontPathLocal(); + LLString sys_path = getFontPathSystem(); + + // The fontname string may contain multiple font file names separated by semicolons. + // Break it apart and try loading each one, in order. + typedef boost::tokenizer > tokenizer; + boost::char_separator sep(";"); + tokenizer tokens(fontname, sep); + tokenizer::iterator token_iter; + + for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + LLFont *fontp = new LLFont(); + LLString font_path = local_path + *token_iter; + if (!fontp->loadFace(font_path.c_str(), point_size, sVertDPI, sHorizDPI, 2, TRUE)) + { + font_path = sys_path + *token_iter; + if (!fontp->loadFace(font_path.c_str(), point_size, sVertDPI, sHorizDPI, 2, TRUE)) + { + llwarns << "Couldn't load font " << *token_iter << llendl; + delete fontp; + fontp = NULL; + } + } + + if(fontp) + { + fontlistp->addAtEnd(fontp); + } + } + + // We want to return true if at least one fallback font loaded correctly. + return (fontlistp->size() > 0); +} + +//static +bool LLFontGL::loadFace(LLFontGL *fontp, const LLString& fontname, const F32 point_size, LLFontList *fallback_fontp) +{ + LLString local_path = getFontPathLocal(); + LLString font_path = local_path + fontname; + if (!fontp->loadFace(font_path.c_str(), point_size, sVertDPI, sHorizDPI)) + { + LLString sys_path = getFontPathSystem(); + font_path = sys_path + fontname; + if (!fontp->loadFace(font_path.c_str(), point_size, sVertDPI, sHorizDPI)) + { + llwarns << "Couldn't load font " << fontname << llendl; + return false; + } + } + + fontp->setFallbackFont(fallback_fontp); + return true; +} + + +// static +BOOL LLFontGL::initDefaultFonts(F32 screen_dpi, F32 x_scale, F32 y_scale, + const LLString& monospace_file, F32 monospace_size, + const LLString& sansserif_file, + const LLString& sanserif_fallback_file, F32 ss_fallback_scale, + F32 small_size, F32 medium_size, F32 big_size, F32 huge_size, + const LLString& sansserif_bold_file, F32 bold_size, + const LLString& app_dir) +{ + BOOL failed = FALSE; + sVertDPI = (F32)llfloor(screen_dpi * y_scale); + sHorizDPI = (F32)llfloor(screen_dpi * x_scale); + sScaleX = x_scale; + sScaleY = y_scale; + sAppDir = app_dir; + + // + // Monospace font + // + + if (!sMonospace) + { + sMonospace = new LLFontGL(); + } + else + { + sMonospace->reset(); + } + + failed |= !loadFace(sMonospace, monospace_file, monospace_size, NULL); + + // + // Sans-serif fonts + // + if(!sSansSerifHuge) + { + sSansSerifHuge = new LLFontGL(); + } + else + { + sSansSerifHuge->reset(); + } + + if (!sSSHugeFallback) + { + sSSHugeFallback = new LLFontList(); + if (!loadFaceFallback(sSSHugeFallback, sanserif_fallback_file, huge_size*ss_fallback_scale)) + { + delete sSSHugeFallback; + sSSHugeFallback = NULL; + } + } + + failed |= !loadFace(sSansSerifHuge, sansserif_file, huge_size, sSSHugeFallback); + + + if(!sSansSerifBig) + { + sSansSerifBig = new LLFontGL(); + } + else + { + sSansSerifBig->reset(); + } + + if (!sSSBigFallback) + { + sSSBigFallback = new LLFontList(); + if (!loadFaceFallback(sSSBigFallback, sanserif_fallback_file, big_size*ss_fallback_scale)) + { + delete sSSBigFallback; + sSSBigFallback = NULL; + } + } + + failed |= !loadFace(sSansSerifBig, sansserif_file, big_size, sSSBigFallback); + + + if(!sSansSerif) + { + sSansSerif = new LLFontGL(); + } + else + { + sSansSerif->reset(); + } + + if (!sSSFallback) + { + sSSFallback = new LLFontList(); + if (!loadFaceFallback(sSSFallback, sanserif_fallback_file, medium_size*ss_fallback_scale)) + { + delete sSSFallback; + sSSFallback = NULL; + } + } + failed |= !loadFace(sSansSerif, sansserif_file, medium_size, sSSFallback); + + + if(!sSansSerifSmall) + { + sSansSerifSmall = new LLFontGL(); + } + else + { + sSansSerifSmall->reset(); + } + + if (!sSSSmallFallback) + { + sSSSmallFallback = new LLFontList(); + if (!loadFaceFallback(sSSSmallFallback, sanserif_fallback_file, small_size*ss_fallback_scale)) + { + delete sSSSmallFallback; + sSSSmallFallback = NULL; + } + } + failed |= !loadFace(sSansSerifSmall, sansserif_file, small_size, sSSSmallFallback); + + + // + // Sans-serif bold + // + if(!sSansSerifBold) + { + sSansSerifBold = new LLFontGL(); + } + else + { + sSansSerifBold->reset(); + } + + if (!sSSBoldFallback) + { + sSSBoldFallback = new LLFontList(); + if (!loadFaceFallback(sSSBoldFallback, sanserif_fallback_file, medium_size*ss_fallback_scale)) + { + delete sSSBoldFallback; + sSSBoldFallback = NULL; + } + } + failed |= !loadFace(sSansSerifBold, sansserif_bold_file, medium_size, sSSBoldFallback); + + return !failed; +} + + + +// static +void LLFontGL::destroyDefaultFonts() +{ + delete sMonospace; + sMonospace = NULL; + + delete sSansSerifHuge; + sSansSerifHuge = NULL; + + delete sSansSerifBig; + sSansSerifBig = NULL; + + delete sSansSerif; + sSansSerif = NULL; + + delete sSansSerifSmall; + sSansSerifSmall = NULL; + + delete sSansSerifBold; + sSansSerifBold = NULL; + + delete sSSHugeFallback; + sSSHugeFallback = NULL; + + delete sSSBigFallback; + sSSBigFallback = NULL; + + delete sSSFallback; + sSSFallback = NULL; + + delete sSSSmallFallback; + sSSSmallFallback = NULL; + + delete sSSBoldFallback; + sSSBoldFallback = NULL; +} + +//static +void LLFontGL::destroyGL() +{ + if (!sMonospace) + { + // Already all destroyed. + return; + } + sMonospace->mImageGLp->destroyGLTexture(); + sSansSerifHuge->mImageGLp->destroyGLTexture(); + sSansSerifSmall->mImageGLp->destroyGLTexture(); + sSansSerif->mImageGLp->destroyGLTexture(); + sSansSerifBig->mImageGLp->destroyGLTexture(); + sSansSerifBold->mImageGLp->destroyGLTexture(); +} + + + +LLFontGL &LLFontGL::operator=(const LLFontGL &source) +{ + llerrs << "Not implemented" << llendl; + return *this; +} + +BOOL LLFontGL::loadFace(const LLString& filename, const F32 point_size, const F32 vert_dpi, const F32 horz_dpi) +{ + if (!LLFont::loadFace(filename, point_size, vert_dpi, horz_dpi, 2, FALSE)) + { + return FALSE; + } + mImageGLp->createGLTexture(0, mRawImageGLp); + mImageGLp->bind(); + mImageGLp->setMipFilterNearest(TRUE, TRUE); + return TRUE; +} + +BOOL LLFontGL::addChar(const llwchar wch) +{ + if (!LLFont::addChar(wch)) + { + return FALSE; + } + + stop_glerror(); + mImageGLp->setSubImage(mRawImageGLp, 0, 0, mImageGLp->getWidth(), mImageGLp->getHeight()); + mImageGLp->bind(); + mImageGLp->setMipFilterNearest(TRUE, TRUE); + stop_glerror(); + return TRUE; +} + + +S32 LLFontGL::renderUTF8(const LLString &text, const S32 offset, + const F32 x, const F32 y, + const LLColor4 &color, + const HAlign halign, const VAlign valign, + U8 style, + const S32 max_chars, const S32 max_pixels, + F32* right_x, + BOOL use_ellipses) const +{ + LLWString wstr = utf8str_to_wstring(text); + return render(wstr, offset, x, y, color, halign, valign, style, max_chars, max_pixels, right_x, use_ellipses); +} + +S32 LLFontGL::render(const LLWString &wstr, + const S32 begin_offset, + const F32 x, const F32 y, + const LLColor4 &color, + const HAlign halign, const VAlign valign, + U8 style, + const S32 max_chars, S32 max_pixels, + F32* right_x, + BOOL use_embedded, + BOOL use_ellipses) const +{ + LLGLEnable texture_2d(GL_TEXTURE_2D); + + if (wstr.empty()) + { + return 0; + } + + if (style & DROP_SHADOW) + { + LLColor4 shadow_color = sShadowColor; + shadow_color[3] = color[3]; + render(wstr, begin_offset, + x + 1.f / sScaleX, + y - 1.f / sScaleY, + shadow_color, + halign, + valign, + style & (~DROP_SHADOW), + max_chars, + max_pixels, + right_x, + use_embedded, + use_ellipses); + } + + S32 scaled_max_pixels = max_pixels == S32_MAX ? S32_MAX : llceil((F32)max_pixels * sScaleX); + + BOOL render_bold = FALSE; + + // HACK for better bolding + if (style & BOLD) + { + if (this == LLFontGL::sSansSerif) + { + return LLFontGL::sSansSerifBold->render( + wstr, begin_offset, + x, y, + color, + halign, valign, + (style & ~BOLD), + max_chars, max_pixels, + right_x, use_embedded); + } + else + { + render_bold = TRUE; + } + } + + glPushMatrix(); + glLoadIdentity(); + glTranslatef(floorf(sCurOrigin.mX*sScaleX), floorf(sCurOrigin.mY*sScaleY), sCurOrigin.mZ); + //glScalef(sScaleX, sScaleY, 1.0f); + + // avoid half pixels + // RN: if we're going to this trouble, might as well snap to nearest pixel all the time + // but the plan is to get rid of this so that fonts "just work" + //F32 half_pixel_distance = llabs(fmodf(sCurOrigin.mX * sScaleX, 1.f) - 0.5f); + //if (half_pixel_distance < PIXEL_BORDER_THRESHOLD) + //{ + glTranslatef(PIXEL_CORRECTION_DISTANCE*sScaleX, 0.f, 0.f); + //} + + // this code would just snap to pixel grid, although it seems to introduce more jitter + //F32 pixel_offset_x = llround(sCurOrigin.mX * sScaleX) - (sCurOrigin.mX * sScaleX); + //F32 pixel_offset_y = llround(sCurOrigin.mY * sScaleY) - (sCurOrigin.mY * sScaleY); + //glTranslatef(-pixel_offset_x, -pixel_offset_y, 0.f); + + // scale back to native pixel size + //glScalef(1.f / sScaleX, 1.f / sScaleY, 1.f); + //glScaled(1.0 / (F64) sScaleX, 1.0 / (F64) sScaleY, 1.0f); + LLFastTimer t(LLFastTimer::FTM_RENDER_FONTS); + + glColor4fv( color.mV ); + + S32 chars_drawn = 0; + S32 i; + S32 length; + + if (-1 == max_chars) + { + length = (S32)wstr.length() - begin_offset; + } + else + { + length = llmin((S32)wstr.length() - begin_offset, max_chars ); + } + + F32 cur_x, cur_y, cur_render_x, cur_render_y; + F32 slant_offset; + + slant_offset = ((style & ITALIC) ? ( -mAscender * 0.25f) : 0.f); + + // Bind the font texture + + mImageGLp->bind(0); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Not guaranteed to be set correctly + + cur_x = ((F32)x * sScaleX); + cur_y = ((F32)y * sScaleY); + + // Offset y by vertical alignment. + switch (valign) + { + case TOP: + cur_y -= mAscender; + break; + case BOTTOM: + cur_y += mDescender; + break; + case VCENTER: + cur_y -= ((mAscender - mDescender)/2.f); + break; + case BASELINE: + // Baseline, do nothing. + break; + default: + break; + } + + switch (halign) + { + case LEFT: + break; + case RIGHT: + cur_x -= (F32)getWidth(wstr.c_str(), 0, length) * sScaleX; + break; + case HCENTER: + cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), 0, length) * sScaleX)) / 2; + break; + default: + break; + } + + // Round properly. + //cur_render_y = (F32)llfloor(cur_y/sScaleY + 0.5f)*sScaleY; + //cur_render_x = (F32)llfloor(cur_x/sScaleX + 0.5f)*sScaleX; + + cur_render_y = cur_y; + cur_render_x = cur_x; + + F32 start_x = cur_x; + + F32 inv_width = 1.f / mImageGLp->getWidth(); + F32 inv_height = 1.f / mImageGLp->getHeight(); + + const S32 LAST_CHARACTER = LLFont::LAST_CHAR_FULL; + + + BOOL draw_ellipses = FALSE; + if (use_ellipses) + { + // check for too long of a string + if (getWidthF32(wstr.c_str(), 0, max_chars) > scaled_max_pixels) + { + const LLWString dots(utf8str_to_wstring(LLString("..."))); + scaled_max_pixels = llmax(0, scaled_max_pixels - llround(getWidthF32(dots.c_str()))); + draw_ellipses = TRUE; + } + } + + + glBegin(GL_QUADS); + for (i = begin_offset; i < begin_offset + length; i++) + { + llwchar wch = wstr[i]; + + // Handle embedded characters first, if they're enabled. + // Embedded characters are a hack for notecards + const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; + if (ext_data) + { + LLImageGL* ext_image = ext_data->mImage; + const LLWString& label = ext_data->mLabel; + + F32 ext_height = (F32)ext_image->getHeight() * sScaleY; + + F32 ext_width = (F32)ext_image->getWidth() * sScaleX; + F32 ext_advance = (EXT_X_BEARING * sScaleX) + ext_width; + + if (!label.empty()) + { + ext_advance += (EXT_X_BEARING + gExtCharFont->getWidthF32( label.c_str() )) * sScaleX; + } + + if (start_x + scaled_max_pixels < cur_x + ext_advance) + { + // Not enough room for this character. + break; + } + + glEnd(); + + glColor3f(1.f, 1.f, 1.f); + + ext_image->bind(); + const F32 ext_x = cur_render_x + (EXT_X_BEARING * sScaleX); + const F32 ext_y = cur_render_y + (EXT_Y_BEARING * sScaleY + mAscender - mLineHeight); + + glBegin(GL_QUADS); + { + S32 num_passes = render_bold ? 2 : 1; + for (S32 pass = 0; pass < num_passes; pass++) + { + glTexCoord2f(1.f, 1.f); + glVertex2f(llfont_round_x(ext_x + ext_width + (F32)(pass * BOLD_OFFSET)), + llfont_round_y(ext_y + ext_height)); + + glTexCoord2f(0.f, 1.f); + glVertex2f(llfont_round_x(ext_x + (F32)(pass * BOLD_OFFSET)), + llfont_round_y(ext_y + ext_height)); + + glTexCoord2f(0.f, 0.f); + glVertex2f(llfont_round_x(ext_x + (F32)(pass * BOLD_OFFSET)), llfont_round_y(ext_y)); + + glTexCoord2f(1.f, 0.f); + glVertex2f(llfont_round_x(ext_x + ext_width + (F32)(pass * BOLD_OFFSET)), + llfont_round_y(ext_y)); + } + } + glEnd(); + + if (!label.empty()) + { + glPushMatrix(); + //glLoadIdentity(); + //glTranslatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f); + //glScalef(sScaleX, sScaleY, 1.f); + gExtCharFont->render(label, 0, + /*llfloor*/((ext_x + (F32)ext_image->getWidth() + EXT_X_BEARING) / sScaleX), + /*llfloor*/(cur_y / sScaleY), + color, + halign, BASELINE, NORMAL, S32_MAX, S32_MAX, NULL, + TRUE ); + glPopMatrix(); + } + + glColor4fv(color.mV); + + chars_drawn++; + cur_x += ext_advance; + if (((i + 1) < length) && wstr[i+1]) + { + cur_x += EXT_KERNING * sScaleX; + } + cur_render_x = cur_x; + + // Bind the font texture + mImageGLp->bind(); + glBegin(GL_QUADS); + } + else + { + if (!hasGlyph(wch)) + { + glEnd(); + (const_cast(this))->addChar(wch); + glBegin(GL_QUADS); + } + + const LLFontGlyphInfo* fgi= getGlyphInfo(wch); + if (!fgi) + { + llerrs << "Missing Glyph Info" << llendl; + } + if ((start_x + scaled_max_pixels) < (cur_x + fgi->mXBearing + fgi->mWidth)) + { + // Not enough room for this character. + break; + } + + // Draw the text at the appropriate location + //Specify vertices and texture coordinates + S32 num_passes = render_bold ? 2 : 1; + for (S32 pass = 0; pass < num_passes; pass++) + { + glTexCoord2f((fgi->mXBitmapOffset - PAD_AMT) * inv_width, + (fgi->mYBitmapOffset + fgi->mHeight + PAD_AMT) * inv_height); + glVertex2f(llfont_round_x(cur_render_x + (F32)fgi->mXBearing + (F32)(pass * BOLD_OFFSET) - PAD_AMT), + llfont_round_y(cur_render_y + (F32)fgi->mYBearing + PAD_AMT)); + glTexCoord2f((fgi->mXBitmapOffset - PAD_AMT) * inv_width, + (fgi->mYBitmapOffset - PAD_AMT) * inv_height); + glVertex2f(llfont_round_x(cur_render_x + (F32)fgi->mXBearing + slant_offset + (F32)(pass * BOLD_OFFSET) - PAD_AMT), + llfont_round_y(cur_render_y + (F32)fgi->mYBearing - (F32)fgi->mHeight - PAD_AMT)); + glTexCoord2f((fgi->mXBitmapOffset + fgi->mWidth + PAD_AMT) * inv_width, + (fgi->mYBitmapOffset - PAD_AMT) * inv_height); + glVertex2f(llfont_round_x(cur_render_x + (F32)fgi->mXBearing + slant_offset + (F32)fgi->mWidth + (F32)(pass * BOLD_OFFSET) + PAD_AMT), + llfont_round_y(cur_render_y + (F32)fgi->mYBearing - (F32)fgi->mHeight - PAD_AMT)); + glTexCoord2f((fgi->mXBitmapOffset + fgi->mWidth + PAD_AMT) * inv_width, + (fgi->mYBitmapOffset + fgi->mHeight + PAD_AMT) * inv_height); + glVertex2f(llfont_round_x(cur_render_x + (F32)fgi->mXBearing + (F32)fgi->mWidth + (F32)(pass * BOLD_OFFSET) + PAD_AMT), + llfont_round_y(cur_render_y + (F32)fgi->mYBearing + PAD_AMT)); + } + + chars_drawn++; + + cur_x += fgi->mXAdvance; + cur_y += fgi->mYAdvance; + llwchar next_char = wstr[i+1]; + if (next_char && (next_char < LAST_CHARACTER)) + { + // Kern this puppy. + if (!hasGlyph(next_char)) + { + glEnd(); + (const_cast(this))->addChar(next_char); + glBegin(GL_QUADS); + } + cur_x += getXKerning(wch, next_char); + } + + // Round after kerning. + // Must do this to cur_x, not just to cur_render_x, otherwise you + // will squish sub-pixel kerned characters too close together. + // For example, "CCCCC" looks bad. + cur_x = (F32)llfloor(cur_x + 0.5f); + //cur_y = (F32)llfloor(cur_y + 0.5f); + + cur_render_x = cur_x; + cur_render_y = cur_y; + } + } + + glEnd(); + + if (right_x) + { + *right_x = cur_x / sScaleX; + } + + if (style & UNDERLINE) + { + LLGLSNoTexture no_texture; + glBegin(GL_LINES); + glVertex2f(start_x, cur_y - (mDescender)); + glVertex2f(cur_x, cur_y - (mDescender)); + glEnd(); + } + + //FIXME: get this working in all alignment cases, etc. + if (draw_ellipses) + { + // recursively render ellipses at end of string + // we've already reserved enough room + glPushMatrix(); + //glLoadIdentity(); + //glTranslatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f); + //glScalef(sScaleX, sScaleY, 1.f); + renderUTF8("...", + 0, + cur_x / sScaleX, (F32)y, + color, + LEFT, valign, + style, + S32_MAX, max_pixels, + right_x, + FALSE); + glPopMatrix(); + } + + glPopMatrix(); + + return chars_drawn; +} + + +LLImageGL *LLFontGL::getImageGL() const +{ + return mImageGLp; +} + +S32 LLFontGL::getWidth(const LLString& utf8text) const +{ + LLWString wtext = utf8str_to_wstring(utf8text); + return getWidth(wtext.c_str(), 0, S32_MAX); +} + +S32 LLFontGL::getWidth(const llwchar* wchars) const +{ + return getWidth(wchars, 0, S32_MAX); +} + +S32 LLFontGL::getWidth(const LLString& utf8text, const S32 begin_offset, const S32 max_chars) const +{ + LLWString wtext = utf8str_to_wstring(utf8text); + return getWidth(wtext.c_str(), begin_offset, max_chars); +} + +S32 LLFontGL::getWidth(const llwchar* wchars, const S32 begin_offset, const S32 max_chars, BOOL use_embedded) const +{ + F32 width = getWidthF32(wchars, begin_offset, max_chars, use_embedded); + return llround(width); +} + +F32 LLFontGL::getWidthF32(const LLString& utf8text) const +{ + LLWString wtext = utf8str_to_wstring(utf8text); + return getWidthF32(wtext.c_str(), 0, S32_MAX); +} + +F32 LLFontGL::getWidthF32(const llwchar* wchars) const +{ + return getWidthF32(wchars, 0, S32_MAX); +} + +F32 LLFontGL::getWidthF32(const LLString& utf8text, const S32 begin_offset, const S32 max_chars ) const +{ + LLWString wtext = utf8str_to_wstring(utf8text); + return getWidthF32(wtext.c_str(), begin_offset, max_chars); +} + +F32 LLFontGL::getWidthF32(const llwchar* wchars, const S32 begin_offset, const S32 max_chars, BOOL use_embedded) const +{ + const S32 LAST_CHARACTER = LLFont::LAST_CHAR_FULL; + + F32 cur_x = 0; + const S32 max_index = begin_offset + max_chars; + for (S32 i = begin_offset; i < max_index; i++) + { + const llwchar wch = wchars[i]; + if (wch == 0) + { + break; // done + } + const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; + if (ext_data) + { + // Handle crappy embedded hack + cur_x += getEmbeddedCharAdvance(ext_data); + + if( ((i+1) < max_chars) && (i+1 < max_index)) + { + cur_x += EXT_KERNING * sScaleX; + } + } + else + { + cur_x += getXAdvance(wch); + llwchar next_char = wchars[i+1]; + + if (((i + 1) < max_chars) + && next_char + && (next_char < LAST_CHARACTER)) + { + // Kern this puppy. + cur_x += getXKerning(wch, next_char); + } + } + // Round after kerning. + cur_x = (F32)llfloor(cur_x + 0.5f); + } + + return cur_x / sScaleX; +} + + + +// Returns the max number of complete characters from text (up to max_chars) that can be drawn in max_pixels +S32 LLFontGL::maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_chars, + BOOL end_on_word_boundary, const BOOL use_embedded, + F32* drawn_pixels) const +{ + if (!wchars || !wchars[0] || max_chars == 0) + { + return 0; + } + + llassert(max_pixels >= 0.f); + llassert(max_chars >= 0); + + BOOL clip = FALSE; + F32 cur_x = 0; + F32 drawn_x = 0; + + S32 start_of_last_word = 0; + BOOL in_word = FALSE; + + F32 scaled_max_pixels = (F32)llceil(max_pixels * sScaleX); + + S32 i; + for (i=0; (i < max_chars); i++) + { + llwchar wch = wchars[i]; + + if(wch == 0) + { + // Null terminator. We're done. + break; + } + + const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; + if (ext_data) + { + if (in_word) + { + in_word = FALSE; + } + else + { + start_of_last_word = i; + } + cur_x += getEmbeddedCharAdvance(ext_data); + + if (scaled_max_pixels < cur_x) + { + clip = TRUE; + break; + } + + if (((i+1) < max_chars) && wchars[i+1]) + { + cur_x += EXT_KERNING * sScaleX; + } + + if( scaled_max_pixels < cur_x ) + { + clip = TRUE; + break; + } + } + else + { + if (in_word) + { + if (iswspace(wch)) + { + in_word = FALSE; + } + } + else + { + start_of_last_word = i; + if (!iswspace(wch)) + { + in_word = TRUE; + } + } + + cur_x += getXAdvance(wch); + + if (scaled_max_pixels < cur_x) + { + clip = TRUE; + break; + } + + if (((i+1) < max_chars) && wchars[i+1]) + { + // Kern this puppy. + cur_x += getXKerning(wch, wchars[i+1]); + } + } + // Round after kerning. + cur_x = (F32)llfloor(cur_x + 0.5f); + drawn_x = cur_x; + } + + if( clip && end_on_word_boundary && (start_of_last_word != 0) ) + { + i = start_of_last_word; + } + if (drawn_pixels) + { + *drawn_pixels = drawn_x; + } + return i; +} + + +S32 LLFontGL::firstDrawableChar(const llwchar* wchars, F32 max_pixels, S32 text_len, S32 start_pos, S32 max_chars) const +{ + if (!wchars || !wchars[0] || max_chars == 0) + { + return 0; + } + + F32 total_width = 0.0; + S32 drawable_chars = 0; + + F32 scaled_max_pixels = max_pixels * sScaleX; + + S32 start = llmin(start_pos, text_len - 1); + for (S32 i = start; i >= 0; i--) + { + llwchar wch = wchars[i]; + + const embedded_data_t* ext_data = getEmbeddedCharData(wch); + if (ext_data) + { + F32 char_width = getEmbeddedCharAdvance(ext_data); + + if( scaled_max_pixels < (total_width + char_width) ) + { + break; + } + + total_width += char_width; + + drawable_chars++; + if( max_chars >= 0 && drawable_chars >= max_chars ) + { + break; + } + + if ( i > 0 ) + { + total_width += EXT_KERNING * sScaleX; + } + + // Round after kerning. + total_width = (F32)llfloor(total_width + 0.5f); + } + else + { + F32 char_width = getXAdvance(wch); + if( scaled_max_pixels < (total_width + char_width) ) + { + break; + } + + total_width += char_width; + + drawable_chars++; + if( max_chars >= 0 && drawable_chars >= max_chars ) + { + break; + } + + if ( i > 0 ) + { + // Kerning + total_width += getXKerning(wchars[i-1], wch); + } + + // Round after kerning. + total_width = (F32)llfloor(total_width + 0.5f); + } + } + + return text_len - drawable_chars; +} + + +S32 LLFontGL::charFromPixelOffset(const llwchar* wchars, const S32 begin_offset, F32 target_x, F32 max_pixels, S32 max_chars, BOOL round, BOOL use_embedded) const +{ + if (!wchars || !wchars[0] || max_chars == 0) + { + return 0; + } + + F32 cur_x = 0; + S32 pos = 0; + + target_x *= sScaleX; + + // max_chars is S32_MAX by default, so make sure we don't get overflow + const S32 max_index = begin_offset + llmin(S32_MAX - begin_offset, max_chars); + + F32 scaled_max_pixels = max_pixels * sScaleX; + + for (S32 i = begin_offset; (i < max_index); i++) + { + llwchar wch = wchars[i]; + if (!wch) + { + break; // done + } + const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; + if (ext_data) + { + F32 ext_advance = getEmbeddedCharAdvance(ext_data); + + if (round) + { + // Note: if the mouse is on the left half of the character, the pick is to the character's left + // If it's on the right half, the pick is to the right. + if (target_x < cur_x + ext_advance/2) + { + break; + } + } + else + { + if (target_x < cur_x + ext_advance) + { + break; + } + } + + if (scaled_max_pixels < cur_x + ext_advance) + { + break; + } + + pos++; + cur_x += ext_advance; + + if (((i + 1) < max_index) + && (wchars[(i + 1)])) + { + cur_x += EXT_KERNING * sScaleX; + } + // Round after kerning. + cur_x = (F32)llfloor(cur_x + 0.5f); + } + else + { + F32 char_width = getXAdvance(wch); + + if (round) + { + // Note: if the mouse is on the left half of the character, the pick is to the character's left + // If it's on the right half, the pick is to the right. + if (target_x < cur_x + char_width*0.5f) + { + break; + } + } + else if (target_x < cur_x + char_width) + { + break; + } + + if (scaled_max_pixels < cur_x + char_width) + { + break; + } + + pos++; + cur_x += char_width; + + if (((i + 1) < max_index) + && (wchars[(i + 1)])) + { + llwchar next_char = wchars[i + 1]; + // Kern this puppy. + cur_x += getXKerning(wch, next_char); + } + + // Round after kerning. + cur_x = (F32)llfloor(cur_x + 0.5f); + } + } + + return pos; +} + + +const LLFontGL::embedded_data_t* LLFontGL::getEmbeddedCharData(const llwchar wch) const +{ + // Handle crappy embedded hack + embedded_map_t::const_iterator iter = mEmbeddedChars.find(wch); + if (iter != mEmbeddedChars.end()) + { + return iter->second; + } + return NULL; +} + + +F32 LLFontGL::getEmbeddedCharAdvance(const embedded_data_t* ext_data) const +{ + const LLWString& label = ext_data->mLabel; + LLImageGL* ext_image = ext_data->mImage; + + F32 ext_width = (F32)ext_image->getWidth(); + if( !label.empty() ) + { + ext_width += (EXT_X_BEARING + gExtCharFont->getWidthF32(label.c_str())) * sScaleX; + } + + return (EXT_X_BEARING * sScaleX) + ext_width; +} + + +void LLFontGL::clearEmbeddedChars() +{ + for_each(mEmbeddedChars.begin(), mEmbeddedChars.end(), DeletePairedPointer()); + mEmbeddedChars.clear(); +} + +void LLFontGL::addEmbeddedChar( llwchar wc, LLImageGL* image, const LLString& label ) +{ + LLWString wlabel = utf8str_to_wstring(label); + addEmbeddedChar(wc, image, wlabel); +} + +void LLFontGL::addEmbeddedChar( llwchar wc, LLImageGL* image, const LLWString& wlabel ) +{ + embedded_data_t* ext_data = new embedded_data_t(image, wlabel); + mEmbeddedChars[wc] = ext_data; +} + +void LLFontGL::removeEmbeddedChar( llwchar wc ) +{ + embedded_map_t::iterator iter = mEmbeddedChars.find(wc); + if (iter != mEmbeddedChars.end()) + { + delete iter->second; + mEmbeddedChars.erase(wc); + } +} + +// static +LLString LLFontGL::nameFromFont(const LLFontGL* fontp) +{ + if (fontp == sSansSerifHuge) + { + return LLString("SansSerifHude"); + } + else if (fontp == sSansSerifSmall) + { + return LLString("SansSerifSmall"); + } + else if (fontp == sSansSerif) + { + return LLString("SansSerif"); + } + else if (fontp == sSansSerifBig) + { + return LLString("SansSerifBig"); + } + else if (fontp == sSansSerifBold) + { + return LLString("SansSerifBold"); + } + else if (fontp == sMonospace) + { + return LLString("Monospace"); + } + else + { + return LLString(); + } +} + +// static +LLFontGL* LLFontGL::fontFromName(const LLString& font_name) +{ + LLFontGL* gl_font = NULL; + if (font_name == "SansSerifHuge") + { + gl_font = LLFontGL::sSansSerifHuge; + } + else if (font_name == "SansSerifSmall") + { + gl_font = LLFontGL::sSansSerifSmall; + } + else if (font_name == "SansSerif") + { + gl_font = LLFontGL::sSansSerif; + } + else if (font_name == "SansSerifBig") + { + gl_font = LLFontGL::sSansSerifBig; + } + else if (font_name == "SansSerifBold") + { + gl_font = LLFontGL::sSansSerifBold; + } + else if (font_name == "Monospace") + { + gl_font = LLFontGL::sMonospace; + } + return gl_font; +} + +// static +LLString LLFontGL::nameFromHAlign(LLFontGL::HAlign align) +{ + if (align == LEFT) return LLString("left"); + else if (align == RIGHT) return LLString("right"); + else if (align == HCENTER) return LLString("center"); + else return LLString(); +} + +// static +LLFontGL::HAlign LLFontGL::hAlignFromName(const LLString& name) +{ + LLFontGL::HAlign gl_hfont_align = LLFontGL::LEFT; + if (name == "left") + { + gl_hfont_align = LLFontGL::LEFT; + } + else if (name == "right") + { + gl_hfont_align = LLFontGL::RIGHT; + } + else if (name == "center") + { + gl_hfont_align = LLFontGL::HCENTER; + } + //else leave left + return gl_hfont_align; +} + +// static +LLString LLFontGL::nameFromVAlign(LLFontGL::VAlign align) +{ + if (align == TOP) return LLString("top"); + else if (align == VCENTER) return LLString("center"); + else if (align == BASELINE) return LLString("baseline"); + else if (align == BOTTOM) return LLString("bottom"); + else return LLString(); +} + +// static +LLFontGL::VAlign LLFontGL::vAlignFromName(const LLString& name) +{ + LLFontGL::VAlign gl_vfont_align = LLFontGL::BASELINE; + if (name == "top") + { + gl_vfont_align = LLFontGL::TOP; + } + else if (name == "center") + { + gl_vfont_align = LLFontGL::VCENTER; + } + else if (name == "baseline") + { + gl_vfont_align = LLFontGL::BASELINE; + } + else if (name == "bottom") + { + gl_vfont_align = LLFontGL::BOTTOM; + } + //else leave baseline + return gl_vfont_align; +} diff --git a/indra/llrender/llfontgl.h b/indra/llrender/llfontgl.h new file mode 100644 index 0000000000..789879a5ca --- /dev/null +++ b/indra/llrender/llfontgl.h @@ -0,0 +1,233 @@ +/** + * @file llfontgl.h + * @author Doug Soo + * @brief Wrapper around FreeType + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLFONTGL_H +#define LL_LLFONTGL_H + +#include "llfont.h" +#include "llimagegl.h" +#include "v2math.h" +#include "llcoord.h" + +class LLColor4; + +class LLFontGL : public LLFont +{ +public: + enum HAlign + { + // Horizontal location of x,y coord to render. + LEFT = 0, // Left align + RIGHT = 1, // Right align + HCENTER = 2, // Center + }; + + enum VAlign + { + // Vertical location of x,y coord to render. + TOP = 3, // Top align + VCENTER = 4, // Center + BASELINE = 5, // Baseline + BOTTOM = 6 // Bottom + }; + + enum StyleFlags + { + // text style to render. May be combined (these are bit flags) + NORMAL = 0, + BOLD = 1, + ITALIC = 2, + UNDERLINE = 4, + DROP_SHADOW = 8 + }; + + // Takes a string with potentially several flags, i.e. "NORMAL|BOLD|ITALIC" + static U8 getStyleFromString(const LLString &style); + + LLFontGL(); + LLFontGL(const LLFontGL &source); + ~LLFontGL(); + + void init(); // Internal init, or reinitialization + void reset(); // Reset a font after GL cleanup. ONLY works on an already loaded font. + + LLFontGL &operator=(const LLFontGL &source); + + static BOOL initDefaultFonts(F32 screen_dpi, F32 x_scale, F32 y_scale, + const LLString& monospace_file, F32 monospace_size, + const LLString& sansserif_file, + const LLString& sansserif_fallback_file, F32 ss_fallback_scale, + F32 small_size, F32 medium_size, F32 large_size, F32 huge_size, + const LLString& sansserif_bold_file, F32 bold_size, + const LLString& app_dir = LLString::null); + + static void destroyDefaultFonts(); + static void destroyGL(); + + static bool loadFaceFallback(LLFontList *fontp, const LLString& fontname, const F32 point_size); + static bool loadFace(LLFontGL *fontp, const LLString& fontname, const F32 point_size, LLFontList *fallback_fontp); + BOOL loadFace(const LLString& filename, const F32 point_size, const F32 vert_dpi, const F32 horz_dpi); + + + S32 renderUTF8(const LLString &text, const S32 begin_offset, + S32 x, S32 y, + const LLColor4 &color) const + { + return renderUTF8(text, begin_offset, (F32)x, (F32)y, color, + LEFT, BASELINE, NORMAL, + S32_MAX, S32_MAX, NULL, FALSE); + } + + S32 renderUTF8(const LLString &text, const S32 begin_offset, + S32 x, S32 y, + const LLColor4 &color, + HAlign halign, VAlign valign, U8 style = NORMAL) const + { + return renderUTF8(text, begin_offset, (F32)x, (F32)y, color, + halign, valign, style, + S32_MAX, S32_MAX, NULL, FALSE); + } + + // renderUTF8 does a conversion, so is slower! + S32 renderUTF8(const LLString &text, + S32 begin_offset, + F32 x, F32 y, + const LLColor4 &color, + HAlign halign, + VAlign valign, + U8 style, + S32 max_chars, + S32 max_pixels, + F32* right_x, + BOOL use_ellipses) const; + + S32 render(const LLWString &text, const S32 begin_offset, + F32 x, F32 y, + const LLColor4 &color) const + { + return render(text, begin_offset, x, y, color, + LEFT, BASELINE, NORMAL, + S32_MAX, S32_MAX, NULL, FALSE, FALSE); + } + + + S32 render(const LLWString &text, + S32 begin_offset, + F32 x, F32 y, + const LLColor4 &color, + HAlign halign = LEFT, + VAlign valign = BASELINE, + U8 style = NORMAL, + S32 max_chars = S32_MAX, + S32 max_pixels = S32_MAX, + F32* right_x=NULL, + BOOL use_embedded = FALSE, + BOOL use_ellipses = FALSE) const; + + // font metrics - override for LLFont that returns units of virtual pixels + /*virtual*/ F32 getLineHeight() const { return (F32)llround(mLineHeight / sScaleY); } + /*virtual*/ F32 getAscenderHeight() const { return (F32)llround(mAscender / sScaleY); } + /*virtual*/ F32 getDescenderHeight() const { return (F32)llround(mDescender / sScaleY); } + + virtual S32 getWidth(const LLString& utf8text) const; + virtual S32 getWidth(const llwchar* wchars) const; + virtual S32 getWidth(const LLString& utf8text, const S32 offset, const S32 max_chars ) const; + virtual S32 getWidth(const llwchar* wchars, const S32 offset, const S32 max_chars, BOOL use_embedded = FALSE) const; + + virtual F32 getWidthF32(const LLString& utf8text) const; + virtual F32 getWidthF32(const llwchar* wchars) const; + virtual F32 getWidthF32(const LLString& text, const S32 offset, const S32 max_chars ) const; + virtual F32 getWidthF32(const llwchar* wchars, const S32 offset, const S32 max_chars, BOOL use_embedded = FALSE ) const; + + // The following are called often, frequently with large buffers, so do not use a string interface + + // Returns the max number of complete characters from text (up to max_chars) that can be drawn in max_pixels + virtual S32 maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_chars = S32_MAX, + BOOL end_on_word_boundary = FALSE, const BOOL use_embedded = FALSE, + F32* drawn_pixels = NULL) const; + + // Returns the index of the first complete characters from text that can be drawn in max_pixels + // starting on the right side (at character start_pos). + virtual S32 firstDrawableChar(const llwchar* wchars, F32 max_pixels, S32 text_len, S32 start_pos=S32_MAX, S32 max_chars = S32_MAX) const; + + // Returns the index of the character closest to pixel position x (ignoring text to the right of max_pixels and max_chars) + virtual S32 charFromPixelOffset(const llwchar* wchars, const S32 char_offset, + F32 x, F32 max_pixels=F32_MAX, S32 max_chars = S32_MAX, + BOOL round = TRUE, BOOL use_embedded = FALSE) const; + + + LLImageGL *getImageGL() const; + + void addEmbeddedChar( llwchar wc, LLImageGL* image, const LLString& label); + void addEmbeddedChar( llwchar wc, LLImageGL* image, const LLWString& label); + void removeEmbeddedChar( llwchar wc ); + + static LLString nameFromFont(const LLFontGL* fontp); + static LLFontGL* fontFromName(const LLString& name); + + static LLString nameFromHAlign(LLFontGL::HAlign align); + static LLFontGL::HAlign hAlignFromName(const LLString& name); + + static LLString nameFromVAlign(LLFontGL::VAlign align); + static LLFontGL::VAlign vAlignFromName(const LLString& name); + +protected: + struct embedded_data_t + { + embedded_data_t(LLImageGL* image, const LLWString& label) : mImage(image), mLabel(label) {} + LLPointer mImage; + LLWString mLabel; + }; + const embedded_data_t* getEmbeddedCharData(const llwchar wch) const; + F32 getEmbeddedCharAdvance(const embedded_data_t* ext_data) const; + void clearEmbeddedChars(); + +public: + static F32 sVertDPI; + static F32 sHorizDPI; + static F32 sScaleX; + static F32 sScaleY; + static LLString sAppDir; // For loading fonts + + static LLFontGL* sMonospace; // medium + + static LLFontGL* sSansSerifSmall; // small + static LLFontList* sSSSmallFallback; + static LLFontGL* sSansSerif; // medium + static LLFontList* sSSFallback; + static LLFontGL* sSansSerifBig; // large + static LLFontList* sSSBigFallback; + static LLFontGL* sSansSerifHuge; // very large + static LLFontList* sSSHugeFallback; + + static LLFontGL* sSansSerifBold; // medium, bolded + static LLFontList* sSSBoldFallback; + + static LLColor4 sShadowColor; + + friend class LLTextBillboard; + friend class LLHUDText; + +protected: + /*virtual*/ BOOL addChar(const llwchar wch); + static LLString getFontPathLocal(); + static LLString getFontPathSystem(); + +protected: + LLPointer mRawImageGLp; + LLPointer mImageGLp; + typedef std::map embedded_map_t; + embedded_map_t mEmbeddedChars; + +public: + static LLCoordFont sCurOrigin; + static std::vector sOriginStack; +}; + +#endif diff --git a/indra/llrender/llgldbg.cpp b/indra/llrender/llgldbg.cpp new file mode 100644 index 0000000000..2c61ebb851 --- /dev/null +++ b/indra/llrender/llgldbg.cpp @@ -0,0 +1,204 @@ +/** + * @file llgldbg.cpp + * @brief Definitions for OpenGL debugging support + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// This file sets some global GL parameters, and implements some +// useful functions for GL operations. + +#include "linden_common.h" + +#include "llglheaders.h" + +#include "llgl.h" + + +//------------------------------------------------------------------------ +// cmstr() +//------------------------------------------------------------------------ +char *cmstr(int i) +{ + switch( i ) + { + case GL_EMISSION: return "GL_EMISSION"; + case GL_AMBIENT: return "GL_AMBIENT"; + case GL_DIFFUSE: return "GL_DIFFUSE"; + case GL_SPECULAR: return "GL_SPECULAR"; + case GL_AMBIENT_AND_DIFFUSE: return "GL_AMBIENT_AND_DIFFUSE"; + } + return "UNKNOWN"; +} + +//------------------------------------------------------------------------ +// facestr() +//------------------------------------------------------------------------ +char *facestr(int i) +{ + switch( i ) + { + case GL_FRONT: return "GL_FRONT"; + case GL_BACK: return "GL_BACK"; + case GL_FRONT_AND_BACK: return "GL_FRONT_AND_BACK"; + } + return "UNKNOWN"; +} + +//------------------------------------------------------------------------ +// boolstr() +//------------------------------------------------------------------------ +const char *boolstr(int b) +{ + return b ? "GL_TRUE" : "GL_FALSE"; +} + +//------------------------------------------------------------------------ +// fv4() +//------------------------------------------------------------------------ +char *fv4(F32 *f) +{ + static char str[128]; + sprintf(str, "%8.3f %8.3f %8.3f %8.3f", f[0], f[1], f[2], f[3]); + return str; +} + +//------------------------------------------------------------------------ +// fv3() +//------------------------------------------------------------------------ +char *fv3(F32 *f) +{ + static char str[128]; + sprintf(str, "%8.3f, %8.3f, %8.3f", f[0], f[1], f[2]); + return str; +} + +//------------------------------------------------------------------------ +// fv1() +//------------------------------------------------------------------------ +char *fv1(F32 *f) +{ + static char str[128]; + sprintf(str, "%8.3f", f[0]); + return str; +} + +//------------------------------------------------------------------------ +// llgl_dump() +//------------------------------------------------------------------------ +void llgl_dump() +{ + int i; + F32 fv[16]; + GLboolean b; + + llinfos << "==========================" << llendl; + llinfos << "OpenGL State" << llendl; + llinfos << "==========================" << llendl; + + llinfos << "-----------------------------------" << llendl; + llinfos << "Current Values" << llendl; + llinfos << "-----------------------------------" << llendl; + + glGetFloatv(GL_CURRENT_COLOR, fv); + llinfos << "GL_CURRENT_COLOR : " << fv4(fv) << llendl; + + glGetFloatv(GL_CURRENT_NORMAL, fv); + llinfos << "GL_CURRENT_NORMAL : " << fv3(fv) << llendl; + + llinfos << "-----------------------------------" << llendl; + llinfos << "Lighting" << llendl; + llinfos << "-----------------------------------" << llendl; + + llinfos << "GL_LIGHTING : " << boolstr(glIsEnabled(GL_LIGHTING)) << llendl; + + llinfos << "GL_COLOR_MATERIAL : " << boolstr(glIsEnabled(GL_COLOR_MATERIAL)) << llendl; + + glGetIntegerv(GL_COLOR_MATERIAL_PARAMETER, (GLint*)&i); + llinfos << "GL_COLOR_MATERIAL_PARAMETER: " << cmstr(i) << llendl; + + glGetIntegerv(GL_COLOR_MATERIAL_FACE, (GLint*)&i); + llinfos << "GL_COLOR_MATERIAL_FACE : " << facestr(i) << llendl; + + fv[0] = fv[1] = fv[2] = fv[3] = 12345.6789f; + glGetMaterialfv(GL_FRONT, GL_AMBIENT, fv); + llinfos << "GL_AMBIENT material : " << fv4(fv) << llendl; + + fv[0] = fv[1] = fv[2] = fv[3] = 12345.6789f; + glGetMaterialfv(GL_FRONT, GL_DIFFUSE, fv); + llinfos << "GL_DIFFUSE material : " << fv4(fv) << llendl; + + fv[0] = fv[1] = fv[2] = fv[3] = 12345.6789f; + glGetMaterialfv(GL_FRONT, GL_SPECULAR, fv); + llinfos << "GL_SPECULAR material : " << fv4(fv) << llendl; + + fv[0] = fv[1] = fv[2] = fv[3] = 12345.6789f; + glGetMaterialfv(GL_FRONT, GL_EMISSION, fv); + llinfos << "GL_EMISSION material : " << fv4(fv) << llendl; + + fv[0] = fv[1] = fv[2] = fv[3] = 12345.6789f; + glGetMaterialfv(GL_FRONT, GL_SHININESS, fv); + llinfos << "GL_SHININESS material : " << fv1(fv) << llendl; + + fv[0] = fv[1] = fv[2] = fv[3] = 12345.6789f; + glGetFloatv(GL_LIGHT_MODEL_AMBIENT, fv); + llinfos << "GL_LIGHT_MODEL_AMBIENT : " << fv4(fv) << llendl; + + glGetBooleanv(GL_LIGHT_MODEL_LOCAL_VIEWER, &b); + llinfos << "GL_LIGHT_MODEL_LOCAL_VIEWER: " << boolstr(b) << llendl; + + glGetBooleanv(GL_LIGHT_MODEL_TWO_SIDE, &b); + llinfos << "GL_LIGHT_MODEL_TWO_SIDE : " << boolstr(b) << llendl; + + for (int l=0; l<8; l++) + { + b = glIsEnabled(GL_LIGHT0+l); + llinfos << "GL_LIGHT" << l << " : " << boolstr(b) << llendl; + + if (!b) + continue; + + glGetLightfv(GL_LIGHT0+l, GL_AMBIENT, fv); + llinfos << " GL_AMBIENT light : " << fv4(fv) << llendl; + + glGetLightfv(GL_LIGHT0+l, GL_DIFFUSE, fv); + llinfos << " GL_DIFFUSE light : " << fv4(fv) << llendl; + + glGetLightfv(GL_LIGHT0+l, GL_SPECULAR, fv); + llinfos << " GL_SPECULAR light : " << fv4(fv) << llendl; + + glGetLightfv(GL_LIGHT0+l, GL_POSITION, fv); + llinfos << " GL_POSITION light : " << fv4(fv) << llendl; + + glGetLightfv(GL_LIGHT0+l, GL_CONSTANT_ATTENUATION, fv); + llinfos << " GL_CONSTANT_ATTENUATION : " << fv1(fv) << llendl; + + glGetLightfv(GL_LIGHT0+l, GL_QUADRATIC_ATTENUATION, fv); + llinfos << " GL_QUADRATIC_ATTENUATION : " << fv1(fv) << llendl; + + glGetLightfv(GL_LIGHT0+l, GL_SPOT_DIRECTION, fv); + llinfos << " GL_SPOT_DIRECTION : " << fv4(fv) << llendl; + + glGetLightfv(GL_LIGHT0+l, GL_SPOT_EXPONENT, fv); + llinfos << " GL_SPOT_EXPONENT : " << fv1(fv) << llendl; + + glGetLightfv(GL_LIGHT0+l, GL_SPOT_CUTOFF, fv); + llinfos << " GL_SPOT_CUTOFF : " << fv1(fv) << llendl; + } + + llinfos << "-----------------------------------" << llendl; + llinfos << "Pixel Operations" << llendl; + llinfos << "-----------------------------------" << llendl; + + llinfos << "GL_ALPHA_TEST : " << boolstr(glIsEnabled(GL_ALPHA_TEST)) << llendl; + llinfos << "GL_DEPTH_TEST : " << boolstr(glIsEnabled(GL_DEPTH_TEST)) << llendl; + + glGetBooleanv(GL_DEPTH_WRITEMASK, &b); + llinfos << "GL_DEPTH_WRITEMASK : " << boolstr(b) << llendl; + + llinfos << "GL_BLEND : " << boolstr(glIsEnabled(GL_BLEND)) << llendl; + llinfos << "GL_DITHER : " << boolstr(glIsEnabled(GL_DITHER)) << llendl; +} + +// End diff --git a/indra/llrender/llgldbg.h b/indra/llrender/llgldbg.h new file mode 100644 index 0000000000..9cb15dc316 --- /dev/null +++ b/indra/llrender/llgldbg.h @@ -0,0 +1,16 @@ +/** + * @file llgldbg.h + * @brief Definitions for OpenGL debugging support + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLGLDBG_H +#define LL_LLGLDBG_H + +// Dumps the current OpenGL state to the console. +void llgl_dump(); + + +#endif // LL_LLGLDBG_H diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp new file mode 100644 index 0000000000..f26223e32b --- /dev/null +++ b/indra/llrender/llimagegl.cpp @@ -0,0 +1,1205 @@ +/** + * @file llimagegl.cpp + * @brief Generic GL image handler + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + + +// TODO: create 2 classes for images w/ and w/o discard levels? + +#include "linden_common.h" + +#include "llimagegl.h" + +#include "llerror.h" +#include "llimage.h" + +#include "llmath.h" +#include "llgl.h" + +//---------------------------------------------------------------------------- + +const F32 MIN_TEXTURE_LIFETIME = 10.f; + +//statics +LLGLuint LLImageGL::sCurrentBoundTextures[MAX_GL_TEXTURE_UNITS] = { 0 }; + +S32 LLImageGL::sGlobalTextureMemory = 0; +S32 LLImageGL::sBoundTextureMemory = 0; +S32 LLImageGL::sCurBoundTextureMemory = 0; +S32 LLImageGL::sCount = 0; + +BOOL LLImageGL::sGlobalUseAnisotropic = FALSE; +F32 LLImageGL::sLastFrameTime = 0.f; + +std::set LLImageGL::sImageList; + +//---------------------------------------------------------------------------- + +//static +S32 LLImageGL::dataFormatBits(S32 dataformat) +{ + switch (dataformat) + { + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: return 4; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: return 8; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return 8; + case GL_LUMINANCE: return 8; + case GL_ALPHA: return 8; + case GL_COLOR_INDEX: return 8; + case GL_LUMINANCE_ALPHA: return 16; + case GL_RGB: return 24; + case GL_RGB8: return 24; + case GL_RGBA: return 32; + case GL_BGRA: return 32; // Used for QuickTime media textures on the Mac + default: + llerrs << "LLImageGL::Unknown format: " << dataformat << llendl; + return 0; + } +} + +//static +S32 LLImageGL::dataFormatBytes(S32 dataformat, S32 width, S32 height) +{ + if (dataformat >= GL_COMPRESSED_RGB_S3TC_DXT1_EXT && + dataformat <= GL_COMPRESSED_RGBA_S3TC_DXT5_EXT) + { + if (width < 4) width = 4; + if (height < 4) height = 4; + } + S32 bytes ((width*height*dataFormatBits(dataformat)+7)>>3); + S32 aligned = (bytes+3)&~3; + return aligned; +} + +//static +S32 LLImageGL::dataFormatComponents(S32 dataformat) +{ + switch (dataformat) + { + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: return 3; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: return 4; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return 4; + case GL_LUMINANCE: return 1; + case GL_ALPHA: return 1; + case GL_COLOR_INDEX: return 1; + case GL_LUMINANCE_ALPHA: return 2; + case GL_RGB: return 3; + case GL_RGBA: return 4; + case GL_BGRA: return 4; // Used for QuickTime media textures on the Mac + default: + llerrs << "LLImageGL::Unknown format: " << dataformat << llendl; + return 0; + } +} + +//---------------------------------------------------------------------------- + +// static +void LLImageGL::bindExternalTexture(LLGLuint gl_name, S32 stage, LLGLenum bind_target ) +{ + glActiveTextureARB(GL_TEXTURE0_ARB + stage); + glClientActiveTextureARB(GL_TEXTURE0_ARB + stage); + glBindTexture(bind_target, gl_name); + sCurrentBoundTextures[stage] = gl_name; +} + +// static +void LLImageGL::unbindTexture(S32 stage, LLGLenum bind_target) +{ + glActiveTextureARB(GL_TEXTURE0_ARB + stage); + glClientActiveTextureARB(GL_TEXTURE0_ARB + stage); + glBindTexture(bind_target, 0); + sCurrentBoundTextures[stage] = 0; +} + +// static +void LLImageGL::updateStats(F32 current_time) +{ + sLastFrameTime = current_time; + sBoundTextureMemory = sCurBoundTextureMemory; + sCurBoundTextureMemory = 0; +} + +//static +S32 LLImageGL::updateBoundTexMem(const S32 delta) +{ + LLImageGL::sCurBoundTextureMemory += delta; + return LLImageGL::sCurBoundTextureMemory; +} + +//---------------------------------------------------------------------------- + +//static +void LLImageGL::destroyGL(BOOL save_state) +{ + for (S32 stage = 0; stage < gGLManager.mNumTextureUnits; stage++) + { + LLImageGL::unbindTexture(stage, GL_TEXTURE_2D); + } + for (std::set::iterator iter = sImageList.begin(); + iter != sImageList.end(); iter++) + { + LLImageGL* glimage = *iter; + if (glimage->mTexName && glimage->mComponents) + { + if (save_state) + { + glimage->mSaveData = new LLImageRaw; + glimage->readBackRaw(glimage->mCurrentDiscardLevel, glimage->mSaveData); + } + glimage->destroyGLTexture(); + stop_glerror(); + } + } +} + +//static +void LLImageGL::restoreGL() +{ + for (std::set::iterator iter = sImageList.begin(); + iter != sImageList.end(); iter++) + { + LLImageGL* glimage = *iter; + if (glimage->mSaveData.notNull() && glimage->mSaveData->getComponents()) + { + if (glimage->getComponents()) + { + glimage->createGLTexture(glimage->mCurrentDiscardLevel, glimage->mSaveData); + stop_glerror(); + } + glimage->mSaveData = NULL; // deletes data + } + } +} + +//---------------------------------------------------------------------------- + +//static +BOOL LLImageGL::create(LLPointer& dest, BOOL usemipmaps) +{ + dest = new LLImageGL(usemipmaps); + return TRUE; +} + +BOOL LLImageGL::create(LLPointer& dest, U32 width, U32 height, U8 components, BOOL usemipmaps) +{ + dest = new LLImageGL(width, height, components, usemipmaps); + return TRUE; +} + +BOOL LLImageGL::create(LLPointer& dest, const LLImageRaw* imageraw, BOOL usemipmaps) +{ + dest = new LLImageGL(imageraw, usemipmaps); + return TRUE; +} + +//---------------------------------------------------------------------------- + +LLImageGL::LLImageGL(BOOL usemipmaps) + : mSaveData(0) +{ + init(usemipmaps); + setSize(0, 0, 0); + sImageList.insert(this); + sCount++; +} + +LLImageGL::LLImageGL(U32 width, U32 height, U8 components, BOOL usemipmaps) + : mSaveData(0) +{ + llassert( components <= 4 ); + init(usemipmaps); + setSize(width, height, components); + sImageList.insert(this); + sCount++; +} + +LLImageGL::LLImageGL(const LLImageRaw* imageraw, BOOL usemipmaps) + : mSaveData(0) +{ + init(usemipmaps); + setSize(0, 0, 0); + sImageList.insert(this); + sCount++; + createGLTexture(0, imageraw); +} + +LLImageGL::~LLImageGL() +{ + LLImageGL::cleanup(); + sImageList.erase(this); + sCount--; +} + +void LLImageGL::init(BOOL usemipmaps) +{ +#ifdef DEBUG_MISS + mMissed = FALSE; +#endif + + mTextureMemory = 0; + mLastBindTime = 0.f; + + mTarget = GL_TEXTURE_2D; + mBindTarget = GL_TEXTURE_2D; + mUseMipMaps = usemipmaps; + mHasMipMaps = FALSE; + mAutoGenMips = FALSE; + mTexName = 0; + mIsResident = 0; + mClampS = FALSE; + mClampT = FALSE; + mMipFilterNearest = FALSE; + mWidth = 0; + mHeight = 0; + mComponents = 0; + + mMaxDiscardLevel = MAX_DISCARD_LEVEL; + mCurrentDiscardLevel = -1; + mDontDiscard = FALSE; + + mFormatInternal = -1; + mFormatPrimary = (LLGLenum) 0; + mFormatType = GL_UNSIGNED_BYTE; + mFormatSwapBytes = FALSE; + mHasExplicitFormat = FALSE; +} + +void LLImageGL::cleanup() +{ + if (!gGLManager.mIsDisabled) + { + destroyGLTexture(); + } + mSaveData = NULL; // deletes data +} + +//---------------------------------------------------------------------------- + +static bool check_power_of_two(S32 dim) +{ + while(dim > 1) + { + if (dim & 1) + { + return false; + } + dim >>= 1; + } + return true; +} + +//static +bool LLImageGL::checkSize(S32 width, S32 height) +{ + return check_power_of_two(width) && check_power_of_two(height); +} + +void LLImageGL::setSize(S32 width, S32 height, S32 ncomponents) +{ + if (width != mWidth || height != mHeight || ncomponents != mComponents) + { + // Check if dimensions are a power of two! + if (!checkSize(width,height)) + { + llerrs << llformat("Texture has non power of two dimention: %dx%d",width,height) << llendl; + } + + if (mTexName) + { +// llwarns << "Setting Size of LLImageGL with existing mTexName = " << mTexName << llendl; + destroyGLTexture(); + } + + mWidth = width; + mHeight = height; + mComponents = ncomponents; + if (ncomponents > 0) + { + mMaxDiscardLevel = 0; + while (width > 1 && height > 1 && mMaxDiscardLevel < MAX_DISCARD_LEVEL) + { + mMaxDiscardLevel++; + width >>= 1; + height >>= 1; + } + } + else + { + mMaxDiscardLevel = MAX_DISCARD_LEVEL; + } + } +} + +//---------------------------------------------------------------------------- + +// virtual +void LLImageGL::dump() +{ + llinfos << "mMaxDiscardLevel " << S32(mMaxDiscardLevel) + << " mLastBindTime " << mLastBindTime + << " mTarget " << S32(mTarget) + << " mBindTarget " << S32(mBindTarget) + << " mUseMipMaps " << S32(mUseMipMaps) + << " mHasMipMaps " << S32(mHasMipMaps) + << " mCurrentDiscardLevel " << S32(mCurrentDiscardLevel) + << " mFormatInternal " << S32(mFormatInternal) + << " mFormatPrimary " << S32(mFormatPrimary) + << " mFormatType " << S32(mFormatType) + << " mFormatSwapBytes " << S32(mFormatSwapBytes) + << " mHasExplicitFormat " << S32(mHasExplicitFormat) +#if DEBUG_MISS + << " mMissed " << mMissed +#endif + << llendl; + + llinfos << " mTextureMemory " << mTextureMemory + << " mTexNames " << mTexName + << " mIsResident " << S32(mIsResident) + << llendl; +} + +//---------------------------------------------------------------------------- + +BOOL LLImageGL::bindTextureInternal(const S32 stage) const +{ + if (gGLManager.mIsDisabled) + { + llwarns << "Trying to bind a texture while GL is disabled!" << llendl; + } + + stop_glerror(); + + glActiveTextureARB(GL_TEXTURE0_ARB + stage); + //glClientActiveTextureARB(GL_TEXTURE0_ARB + stage); + + stop_glerror(); + + if (sCurrentBoundTextures[stage] && sCurrentBoundTextures[stage] == mTexName) + { + // already set! + return TRUE; + } + + if (mTexName != 0) + { +#ifdef DEBUG_MISS + mMissed = ! getIsResident(TRUE); +#endif + + glBindTexture(mBindTarget, mTexName); + sCurrentBoundTextures[stage] = mTexName; + stop_glerror(); + + if (mLastBindTime != sLastFrameTime) + { + // we haven't accounted for this texture yet this frame + updateBoundTexMem(mTextureMemory); + mLastBindTime = sLastFrameTime; + } + + return TRUE; + } + else + { + glBindTexture(mBindTarget, 0); + sCurrentBoundTextures[stage] = 0; + return FALSE; + } +} + +//virtual +BOOL LLImageGL::bind(const S32 stage) const +{ + if (stage == -1) + { + return FALSE; + } + BOOL res = bindTextureInternal(stage); + //llassert(res); + return res; +} + +void LLImageGL::setExplicitFormat( LLGLint internal_format, LLGLenum primary_format, LLGLenum type_format, BOOL swap_bytes ) +{ + // Note: must be called before createTexture() + // Note: it's up to the caller to ensure that the format matches the number of components. + mHasExplicitFormat = TRUE; + mFormatInternal = internal_format; + mFormatPrimary = primary_format; + if(type_format == 0) + mFormatType = GL_UNSIGNED_BYTE; + else + mFormatType = type_format; + mFormatSwapBytes = swap_bytes; +} + +//---------------------------------------------------------------------------- + +void LLImageGL::setImage(const LLImageRaw* imageraw) +{ + llassert((imageraw->getWidth() == getWidth(mCurrentDiscardLevel)) && + (imageraw->getHeight() == getHeight(mCurrentDiscardLevel)) && + (imageraw->getComponents() == getComponents())); + const U8* rawdata = imageraw->getData(); + setImage(rawdata, FALSE); +} + +void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips) +{ +// LLFastTimer t1(LLFastTimer::FTM_TEMP1); + + bool is_compressed = false; + if (mFormatPrimary >= GL_COMPRESSED_RGBA_S3TC_DXT1_EXT && mFormatPrimary <= GL_COMPRESSED_RGBA_S3TC_DXT5_EXT) + { + is_compressed = true; + } + + { +// LLFastTimer t2(LLFastTimer::FTM_TEMP2); + llverify(bindTextureInternal(0)); + } + + if (mUseMipMaps) + { +// LLFastTimer t2(LLFastTimer::FTM_TEMP3); + if (data_hasmips) + { + // NOTE: data_in points to largest image; smaller images + // are stored BEFORE the largest image + for (S32 d=mCurrentDiscardLevel; d<=mMaxDiscardLevel; d++) + { + S32 w = getWidth(d); + S32 h = getHeight(d); + S32 gl_level = d-mCurrentDiscardLevel; + if (d > mCurrentDiscardLevel) + { + data_in -= dataFormatBytes(mFormatPrimary, w, h); // see above comment + } + if (is_compressed) + { +// LLFastTimer t2(LLFastTimer::FTM_TEMP4); + S32 tex_size = dataFormatBytes(mFormatPrimary, w, h); + glCompressedTexImage2DARB(mTarget, gl_level, mFormatPrimary, w, h, 0, tex_size, (GLvoid *)data_in); + stop_glerror(); + } + else + { +// LLFastTimer t2(LLFastTimer::FTM_TEMP4); + + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 1); + stop_glerror(); + } + + glTexImage2D(mTarget, gl_level, mFormatInternal, w, h, 0, mFormatPrimary, GL_UNSIGNED_BYTE, (GLvoid*)data_in); + + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 0); + stop_glerror(); + } + + stop_glerror(); + } + stop_glerror(); + } + } + else if (!is_compressed) + { + if (mAutoGenMips) + { + glTexParameteri(mBindTarget, GL_GENERATE_MIPMAP_SGIS, TRUE); + stop_glerror(); + { +// LLFastTimer t2(LLFastTimer::FTM_TEMP4); + + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 1); + stop_glerror(); + } + + glTexImage2D(mTarget, 0, mFormatInternal, + getWidth(mCurrentDiscardLevel), getHeight(mCurrentDiscardLevel), 0, + mFormatPrimary, mFormatType, + data_in); + stop_glerror(); + + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 0); + stop_glerror(); + } + } + } + else + { + // Create mips by hand + // about 30% faster than autogen on ATI 9800, 50% slower on nVidia 4800 + // ~4x faster than gluBuild2DMipmaps + S32 width = getWidth(mCurrentDiscardLevel); + S32 height = getHeight(mCurrentDiscardLevel); + S32 nummips = mMaxDiscardLevel - mCurrentDiscardLevel + 1; + S32 w = width, h = height; + const U8* prev_mip_data = 0; + const U8* cur_mip_data = 0; + for (int m=0; m 0 && h > 0 && cur_mip_data); + { +// LLFastTimer t1(LLFastTimer::FTM_TEMP4); + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 1); + stop_glerror(); + } + + glTexImage2D(mTarget, m, mFormatInternal, w, h, 0, mFormatPrimary, mFormatType, cur_mip_data); + stop_glerror(); + + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 0); + stop_glerror(); + } + } + if (prev_mip_data && prev_mip_data != data_in) + { + delete[] prev_mip_data; + } + prev_mip_data = cur_mip_data; + w >>= 1; + h >>= 1; + } + if (prev_mip_data && prev_mip_data != data_in) + { + delete[] prev_mip_data; + } + } + } + else + { + llerrs << "Compressed Image has mipmaps but data does not (can not auto generate compressed mips)" << llendl; + } + mHasMipMaps = TRUE; + } + else + { +// LLFastTimer t2(LLFastTimer::FTM_TEMP5); + S32 w = getWidth(); + S32 h = getHeight(); + if (is_compressed) + { + S32 tex_size = dataFormatBytes(mFormatPrimary, w, h); + glCompressedTexImage2DARB(mTarget, 0, mFormatPrimary, w, h, 0, tex_size, (GLvoid *)data_in); + stop_glerror(); + } + else + { + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 1); + stop_glerror(); + } + + glTexImage2D(mTarget, 0, mFormatInternal, w, h, 0, + mFormatPrimary, mFormatType, (GLvoid *)data_in); + stop_glerror(); + + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 0); + stop_glerror(); + } + + } + mHasMipMaps = FALSE; + } + stop_glerror(); +} + +BOOL LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height) +{ + if (!width || !height) + { + return TRUE; + } + if (mTexName == 0) + { + llwarns << "Setting subimage on image without GL texture" << llendl; + return FALSE; + } + + if (x_pos == 0 && y_pos == 0 && width == getWidth() && height == getHeight()) + { + setImage(datap, FALSE); + } + else + { + if (mUseMipMaps) + { + dump(); + llerrs << "setSubImage called with mipmapped image (not supported)" << llendl; + } + llassert(mCurrentDiscardLevel == 0); + if (((x_pos + width) > getWidth()) || + (y_pos + height) > getHeight()) + { + dump(); + llerrs << "Subimage not wholly in target image!" + << " x_pos " << x_pos + << " y_pos " << y_pos + << " width " << width + << " height " << height + << " getWidth() " << getWidth() + << " getHeight() " << getHeight() + << llendl; + } + + if ((x_pos + width) > data_width || + (y_pos + height) > data_height) + { + dump(); + llerrs << "Subimage not wholly in source image!" + << " x_pos " << x_pos + << " y_pos " << y_pos + << " width " << width + << " height " << height + << " source_width " << data_width + << " source_height " << data_height + << llendl; + } + + + glPixelStorei(GL_UNPACK_ROW_LENGTH, data_width); + stop_glerror(); + + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 1); + stop_glerror(); + } + + datap += (y_pos * data_width + x_pos) * getComponents(); + // Update the GL texture + llverify(bindTextureInternal(0)); + stop_glerror(); + + glTexSubImage2D(mTarget, 0, x_pos, y_pos, + width, height, mFormatPrimary, mFormatType, datap); + stop_glerror(); + + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 0); + stop_glerror(); + } + + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + stop_glerror(); + } + + return TRUE; +} + +BOOL LLImageGL::setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height) +{ + return setSubImage(imageraw->getData(), imageraw->getWidth(), imageraw->getHeight(), x_pos, y_pos, width, height); +} + +// Copy sub image from frame buffer +BOOL LLImageGL::setSubImageFromFrameBuffer(S32 fb_x, S32 fb_y, S32 x_pos, S32 y_pos, S32 width, S32 height) +{ + if (bindTextureInternal(0)) + { + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, fb_x, fb_y, x_pos, y_pos, width, height); + stop_glerror(); + return TRUE; + } + else + { + return FALSE; + } +} + +BOOL LLImageGL::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename/*=0*/) +{ + if (gGLManager.mIsDisabled) + { + llwarns << "Trying to create a texture while GL is disabled!" << llendl; + return FALSE; + } + llassert(gGLManager.mInited || gNoRender); + stop_glerror(); + + if (discard_level < 0) + { + llassert(mCurrentDiscardLevel >= 0); + discard_level = mCurrentDiscardLevel; + } + discard_level = llclamp(discard_level, 0, (S32)mMaxDiscardLevel); + + // Actual image width/height = raw image width/height * 2^discard_level + S32 w = imageraw->getWidth() << discard_level; + S32 h = imageraw->getHeight() << discard_level; + + // setSize may call destroyGLTexture if the size does not match + setSize(w, h, imageraw->getComponents()); + + if( !mHasExplicitFormat ) + { + switch (mComponents) + { + case 1: + // Use luminance alpha (for fonts) + mFormatInternal = GL_LUMINANCE8; + mFormatPrimary = GL_LUMINANCE; + mFormatType = GL_UNSIGNED_BYTE; + break; + case 2: + // Use luminance alpha (for fonts) + mFormatInternal = GL_LUMINANCE8_ALPHA8; + mFormatPrimary = GL_LUMINANCE_ALPHA; + mFormatType = GL_UNSIGNED_BYTE; + break; + case 3: + mFormatInternal = GL_RGB8; + mFormatPrimary = GL_RGB; + mFormatType = GL_UNSIGNED_BYTE; + break; + case 4: + mFormatInternal = GL_RGBA8; + mFormatPrimary = GL_RGBA; + mFormatType = GL_UNSIGNED_BYTE; + break; + default: + llerrs << "Bad number of components for texture: " << (U32)getComponents() << llendl; + } + } + + const U8* rawdata = imageraw->getData(); + return createGLTexture(discard_level, rawdata, FALSE, usename); +} + +BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_hasmips, S32 usename) +{ + llassert(data_in); + + if (discard_level < 0) + { + llassert(mCurrentDiscardLevel >= 0); + discard_level = mCurrentDiscardLevel; + } + discard_level = llclamp(discard_level, 0, (S32)mMaxDiscardLevel); + + if (mTexName != 0 && discard_level == mCurrentDiscardLevel) + { + // This will only be true if the size has not changed + setImage(data_in, data_hasmips); + return TRUE; + } + + GLuint old_name = mTexName; +// S32 old_discard = mCurrentDiscardLevel; + + if (usename != 0) + { + mTexName = usename; + } + else + { + glGenTextures(1, (GLuint*)&mTexName); + stop_glerror(); + { +// LLFastTimer t1(LLFastTimer::FTM_TEMP6); + llverify(bindTextureInternal(0)); + glTexParameteri(mBindTarget, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(mBindTarget, GL_TEXTURE_MAX_LEVEL, mMaxDiscardLevel-discard_level); + } + } + if (!mTexName) + { + llerrs << "LLImageGL::createGLTexture failed to make texture" << llendl; + } + + if (mUseMipMaps) + { + mAutoGenMips = gGLManager.mHasMipMapGeneration; +#if LL_DARWIN + // On the Mac GF2 and GF4MX drivers, auto mipmap generation doesn't work right with alpha-only textures. + if(gGLManager.mIsGF2or4MX && (mFormatInternal == GL_ALPHA8) && (mFormatPrimary == GL_ALPHA)) + { + mAutoGenMips = FALSE; + } +#endif + } + + mCurrentDiscardLevel = discard_level; + + setImage(data_in, data_hasmips); + + setClamp(mClampS, mClampT); + setMipFilterNearest(mMipFilterNearest); + + // things will break if we don't unbind after creation + unbindTexture(0, mBindTarget); + stop_glerror(); + + if (old_name != 0) + { + sGlobalTextureMemory -= mTextureMemory; + glDeleteTextures(1, &old_name); + stop_glerror(); + } + + mTextureMemory = getMipBytes(discard_level); + sGlobalTextureMemory += mTextureMemory; + + // mark this as bound at this point, so we don't throw it out immediately + mLastBindTime = sLastFrameTime; + + return TRUE; +} + +BOOL LLImageGL::setDiscardLevel(S32 discard_level) +{ + llassert(discard_level >= 0); + llassert(mCurrentDiscardLevel >= 0); + + discard_level = llclamp(discard_level, 0, (S32)mMaxDiscardLevel); + + if (mDontDiscard) + { + // don't discard! + return FALSE; + } + else if (discard_level == mCurrentDiscardLevel) + { + // nothing to do + return FALSE; + } + else if (discard_level < mCurrentDiscardLevel) + { + // larger image + dump(); + llerrs << "LLImageGL::setDiscardLevel() called with larger discard level; use createGLTexture()" << llendl; + return FALSE; + } + else if (mUseMipMaps) + { + LLPointer imageraw = new LLImageRaw; + while(discard_level > mCurrentDiscardLevel) + { + if (readBackRaw(discard_level, imageraw)) + { + break; + } + discard_level--; + } + if (discard_level == mCurrentDiscardLevel) + { + // unable to increase the discard level + return FALSE; + } + return createGLTexture(discard_level, imageraw); + } + else + { +#ifndef LL_LINUX // FIXME: This should not be skipped for the linux client. + llerrs << "LLImageGL::setDiscardLevel() called on image without mipmaps" << llendl; +#endif + return FALSE; + } +} + +BOOL LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw) +{ + if (discard_level < 0) + { + discard_level = mCurrentDiscardLevel; + } + + if (mTexName == 0 || discard_level < mCurrentDiscardLevel) + { + return FALSE; + } + + S32 gl_discard = discard_level - mCurrentDiscardLevel; + + llverify(bindTextureInternal(0)); + + LLGLint glwidth = 0; + glGetTexLevelParameteriv(mTarget, gl_discard, GL_TEXTURE_WIDTH, (GLint*)&glwidth); + if (glwidth == 0) + { + // No mip data smaller than current discard level + return FALSE; + } + + S32 width = getWidth(discard_level); + S32 height = getHeight(discard_level); + S32 ncomponents = getComponents(); + if (ncomponents == 0) + { + return FALSE; + } + + if (width <= 0 || width > 2048 || height <= 0 || height > 2048 || ncomponents < 1 || ncomponents > 4) + { + llerrs << llformat("LLImageGL::readBackRaw: bogus params: %d x %d x %d",width,height,ncomponents) << llendl; + } + + LLGLint is_compressed = 0; + glGetTexLevelParameteriv(mTarget, is_compressed, GL_TEXTURE_COMPRESSED, (GLint*)&is_compressed); + if (is_compressed) + { + LLGLint glbytes; + glGetTexLevelParameteriv(mTarget, gl_discard, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, (GLint*)&glbytes); + imageraw->allocateDataSize(width, height, ncomponents, glbytes); + glGetCompressedTexImageARB(mTarget, gl_discard, (GLvoid*)(imageraw->getData())); + stop_glerror(); + } + else + { + imageraw->allocateDataSize(width, height, ncomponents); + glGetTexImage(GL_TEXTURE_2D, gl_discard, mFormatPrimary, mFormatType, (GLvoid*)(imageraw->getData())); + stop_glerror(); + } + + return TRUE; +} + +void LLImageGL::destroyGLTexture() +{ + stop_glerror(); + + if (mTexName != 0) + { + for (int i = 0; i < gGLManager.mNumTextureUnits; i++) + { + if (sCurrentBoundTextures[i] == mTexName) + { + unbindTexture(i, GL_TEXTURE_2D); + stop_glerror(); + } + } + + sGlobalTextureMemory -= mTextureMemory; + mTextureMemory = 0; + + glDeleteTextures(1, (GLuint*)&mTexName); + mTexName = 0; + + stop_glerror(); + } +} + +//---------------------------------------------------------------------------- + +void LLImageGL::setClamp(BOOL clamps, BOOL clampt) +{ + mClampS = clamps; + mClampT = clampt; + if (mTexName != 0) + { + glTexParameteri(mBindTarget, GL_TEXTURE_WRAP_S, clamps ? GL_CLAMP_TO_EDGE : GL_REPEAT); + glTexParameteri(mBindTarget, GL_TEXTURE_WRAP_T, clampt ? GL_CLAMP_TO_EDGE : GL_REPEAT); + } + stop_glerror(); +} + +void LLImageGL::setMipFilterNearest(BOOL nearest, BOOL min_nearest) +{ + mMipFilterNearest = nearest; + + if (mTexName != 0) + { + if (min_nearest) + { + glTexParameteri(mBindTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + else if (mHasMipMaps) + { + glTexParameteri(mBindTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } + else + { + glTexParameteri(mBindTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + if (mMipFilterNearest) + { + glTexParameteri(mBindTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + else + { + glTexParameteri(mBindTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + if (gGLManager.mHasAnisotropic) + { + if (sGlobalUseAnisotropic && !mMipFilterNearest) + { + F32 largest_anisotropy; + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &largest_anisotropy); + glTexParameterf(mBindTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, largest_anisotropy); + } + else + { + glTexParameterf(mBindTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.f); + } + } + } + + stop_glerror(); +} + +BOOL LLImageGL::getIsResident(BOOL test_now) +{ + if (test_now) + { + if (mTexName != 0) + { + glAreTexturesResident(1, (GLuint*)&mTexName, &mIsResident); + } + else + { + mIsResident = FALSE; + } + } + + return mIsResident; +} + +S32 LLImageGL::getHeight(S32 discard_level) const +{ + if (discard_level < 0) + { + discard_level = mCurrentDiscardLevel; + } + S32 height = mHeight >> discard_level; + if (height < 1) height = 1; + return height; +} + +S32 LLImageGL::getWidth(S32 discard_level) const +{ + if (discard_level < 0) + { + discard_level = mCurrentDiscardLevel; + } + S32 width = mWidth >> discard_level; + if (width < 1) width = 1; + return width; +} + +S32 LLImageGL::getBytes(S32 discard_level) const +{ + if (discard_level < 0) + { + discard_level = mCurrentDiscardLevel; + } + S32 w = mWidth>>discard_level; + S32 h = mHeight>>discard_level; + if (w == 0) w = 1; + if (h == 0) h = 1; + return dataFormatBytes(mFormatPrimary, w, h); +} + +S32 LLImageGL::getMipBytes(S32 discard_level) const +{ + if (discard_level < 0) + { + discard_level = mCurrentDiscardLevel; + } + S32 w = mWidth>>discard_level; + S32 h = mHeight>>discard_level; + S32 res = dataFormatBytes(mFormatPrimary, w, h); + if (mUseMipMaps) + { + while (w > 1 && h > 1) + { + w >>= 1; if (w == 0) w = 1; + h >>= 1; if (h == 0) h = 1; + res += dataFormatBytes(mFormatPrimary, w, h); + } + } + return res; +} + +BOOL LLImageGL::getBoundRecently() const +{ + return (BOOL)(sLastFrameTime - mLastBindTime < MIN_TEXTURE_LIFETIME); +} + +void LLImageGL::setTarget(const LLGLenum target, const LLGLenum bind_target) +{ + mTarget = target; + mBindTarget = bind_target; +} + +//---------------------------------------------------------------------------- + +// Manual Mip Generation +/* + S32 width = getWidth(discard_level); + S32 height = getHeight(discard_level); + S32 w = width, h = height; + S32 nummips = 1; + while (w > 4 && h > 4) + { + w >>= 1; h >>= 1; + nummips++; + } + stop_glerror(); + w = width, h = height; + const U8* prev_mip_data = 0; + const U8* cur_mip_data = 0; + for (int m=0; m 0 && h > 0 && cur_mip_data); + U8 test = cur_mip_data[w*h*mComponents-1]; + { + glTexImage2D(mTarget, m, mFormatInternal, w, h, 0, mFormatPrimary, mFormatType, cur_mip_data); + stop_glerror(); + } + if (prev_mip_data && prev_mip_data != rawdata) + { + delete prev_mip_data; + } + prev_mip_data = cur_mip_data; + w >>= 1; + h >>= 1; + } + if (prev_mip_data && prev_mip_data != rawdata) + { + delete prev_mip_data; + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, nummips); +*/ diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h new file mode 100644 index 0000000000..f8c6a008eb --- /dev/null +++ b/indra/llrender/llimagegl.h @@ -0,0 +1,185 @@ +/** + * @file llimagegl.h + * @brief Object for managing images and their textures + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + + +#ifndef LL_LLIMAGEGL_H +#define LL_LLIMAGEGL_H + +#include "llimage.h" + +#include "llgltypes.h" +#include "llmemory.h" + +//============================================================================ + +class LLImageGL : public LLThreadSafeRefCount +{ +public: + // Size calculation + static S32 dataFormatBits(S32 dataformat); + static S32 dataFormatBytes(S32 dataformat, S32 width, S32 height); + static S32 dataFormatComponents(S32 dataformat); + + // Wrapper for glBindTexture that keeps LLImageGL in sync. + // Usually you want stage = 0 and bind_target = GL_TEXTURE_2D + static void bindExternalTexture( LLGLuint gl_name, S32 stage, LLGLenum bind_target); + static void unbindTexture(S32 stage, LLGLenum target); + + // needs to be called every frame + static void updateStats(F32 current_time); + + // Save off / restore GL textures + static void destroyGL(BOOL save_state = TRUE); + static void restoreGL(); + + // Sometimes called externally for textures not using LLImageGL (should go away...) + static S32 updateBoundTexMem(const S32 delta); + + static bool checkSize(S32 width, S32 height); + + // Not currently necessary for LLImageGL, but required in some derived classes, + // so include for compatability + static BOOL create(LLPointer& dest, BOOL usemipmaps = TRUE); + static BOOL create(LLPointer& dest, U32 width, U32 height, U8 components, BOOL usemipmaps = TRUE); + static BOOL create(LLPointer& dest, const LLImageRaw* imageraw, BOOL usemipmaps = TRUE); + +public: + LLImageGL(BOOL usemipmaps = TRUE); + LLImageGL(U32 width, U32 height, U8 components, BOOL usemipmaps = TRUE); + LLImageGL(const LLImageRaw* imageraw, BOOL usemipmaps = TRUE); + +protected: + virtual ~LLImageGL(); + BOOL bindTextureInternal(const S32 stage = 0) const; + +public: + virtual void dump(); // debugging info to llinfos + virtual BOOL bind(const S32 stage = 0) const; + + void setSize(S32 width, S32 height, S32 ncomponents); + + BOOL createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename = 0); + BOOL createGLTexture(S32 discard_level, const U8* data, BOOL data_hasmips = FALSE, S32 usename = 0); + void setImage(const LLImageRaw* imageraw); + void setImage(const U8* data_in, BOOL data_hasmips = FALSE); + BOOL setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height); + BOOL setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height); + BOOL setSubImageFromFrameBuffer(S32 fb_x, S32 fb_y, S32 x_pos, S32 y_pos, S32 width, S32 height); + BOOL setDiscardLevel(S32 discard_level); + BOOL readBackRaw(S32 discard_level, LLImageRaw* imageraw); // Read back a raw image for this discard level, if it exists + void destroyGLTexture(); + + void setClamp(BOOL clamps, BOOL clampt); + void setMipFilterNearest(BOOL nearest, BOOL min_nearest = FALSE); + void setExplicitFormat(LLGLint internal_format, LLGLenum primary_format, LLGLenum type_format = 0, BOOL swap_bytes = FALSE); + void dontDiscard() { mDontDiscard = 1; } + + S32 getDiscardLevel() const { return mCurrentDiscardLevel; } + S32 getMaxDiscardLevel() const { return mMaxDiscardLevel; } + + S32 getWidth(S32 discard_level = -1) const; + S32 getHeight(S32 discard_level = -1) const; + U8 getComponents() const { return mComponents; } + S32 getBytes(S32 discard_level = -1) const; + S32 getMipBytes(S32 discard_level = -1) const; + BOOL getBoundRecently() const; + LLGLenum getPrimaryFormat() const { return mFormatPrimary; } + + BOOL getClampS() const { return mClampS; } + BOOL getClampT() const { return mClampT; } + BOOL getMipFilterNearest() const { return mMipFilterNearest; } + + BOOL getHasGLTexture() const { return mTexName != 0; } + LLGLuint getTexName() const { return mTexName; } + + BOOL getIsResident(BOOL test_now = FALSE); // not const + + void setTarget(const LLGLenum target, const LLGLenum bind_target); + + BOOL getUseMipMaps() const { return mUseMipMaps; } + void setUseMipMaps(BOOL usemips) { mUseMipMaps = usemips; } + BOOL getUseDiscard() const { return mUseMipMaps && !mDontDiscard; } + BOOL getDontDiscard() const { return mDontDiscard; } + +protected: + void init(BOOL usemipmaps); + virtual void cleanup(); // Clean up the LLImageGL so it can be reinitialized. Be careful when using this in derived class destructors + +public: + // Various GL/Rendering options + S32 mTextureMemory; + mutable F32 mLastBindTime; // last time this was bound, by discard level + mutable F32 mLastBindAttempt; // last time bindTexture was called on this texture + +private: + LLPointer mSaveData; // used for destroyGL/restoreGL + S8 mUseMipMaps; + S8 mHasMipMaps; + S8 mHasExplicitFormat; // If false (default), GL format is f(mComponents) + S8 mAutoGenMips; + +protected: + LLGLenum mTarget; // Normally GL_TEXTURE2D, sometimes something else (ex. cube maps) + LLGLenum mBindTarget; // NOrmally GL_TEXTURE2D, sometimes something else (ex. cube maps) + + LLGLuint mTexName; + + LLGLboolean mIsResident; + + U16 mWidth; + U16 mHeight; + + S8 mComponents; + S8 mMaxDiscardLevel; + S8 mCurrentDiscardLevel; + S8 mDontDiscard; // Keep full res version of this image (for UI, etc) + + S8 mClampS; // Need to save clamp state + S8 mClampT; + S8 mMipFilterNearest; // if TRUE, set magfilter to GL_NEAREST + + LLGLint mFormatInternal; // = GL internalformat + LLGLenum mFormatPrimary; // = GL format (pixel data format) + LLGLenum mFormatType; + BOOL mFormatSwapBytes;// if true, use glPixelStorei(GL_UNPACK_SWAP_BYTES, 1) + + // STATICS +public: + static std::set LLImageGL::sImageList; + static S32 sCount; + + static F32 sLastFrameTime; + + static LLGLuint sCurrentBoundTextures[MAX_GL_TEXTURE_UNITS]; // Currently bound texture ID + + // Global memory statistics + static S32 sGlobalTextureMemory; // Tracks main memory texmem + static S32 sBoundTextureMemory; // Tracks bound texmem for last completed frame + static S32 sCurBoundTextureMemory; // Tracks bound texmem for current frame + + static BOOL sGlobalUseAnisotropic; + +#if DEBUG_MISS + BOOL mMissed; // Missed on last bind? + BOOL getMissed() const { return mMissed; }; +#else + BOOL getMissed() const { return FALSE; }; +#endif +}; + +//RN: maybe this needs to moved elsewhere? +class LLImageProviderInterface +{ +public: + LLImageProviderInterface() {}; + virtual ~LLImageProviderInterface() {}; + + virtual LLImageGL* getUIImageByID(const LLUUID& id, BOOL clamped = TRUE) = 0; +}; + +#endif // LL_LLIMAGEGL_H diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp new file mode 100644 index 0000000000..c02be6bb8d --- /dev/null +++ b/indra/llui/llbutton.cpp @@ -0,0 +1,1012 @@ +/** + * @file llbutton.cpp + * @brief LLButton base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llbutton.h" + +// Linden library includes +#include "v4color.h" +#include "llstring.h" + +// Project includes +#include "llkeyboard.h" +#include "llgl.h" +#include "llui.h" +#include "lluiconstants.h" +//#include "llcallbacklist.h" +#include "llresmgr.h" +#include "llcriticaldamp.h" +#include "llglheaders.h" +#include "llfocusmgr.h" +#include "llwindow.h" + +// globals loaded from settings.xml +S32 LLBUTTON_ORIG_H_PAD = 6; // Pre-zoomable UI +S32 LLBUTTON_H_PAD = 0; +S32 LLBUTTON_V_PAD = 0; +S32 BTN_HEIGHT_SMALL= 0; +S32 BTN_HEIGHT = 0; + +S32 BTN_GRID = 12; +S32 BORDER_SIZE = 1; + +// static +LLFrameTimer LLButton::sFlashingTimer; + +LLButton::LLButton( const LLString& name, const LLRect& rect, const LLString& control_name, void (*click_callback)(void*), void *callback_data) +: LLUICtrl(name, rect, TRUE, NULL, NULL), + mClickedCallback( click_callback ), + mMouseDownCallback( NULL ), + mMouseUpCallback( NULL ), + mHeldDownCallback( NULL ), + mGLFont( NULL ), + mHeldDownDelay( 0.5f ), // seconds until held-down callback is called + mImageUnselected( NULL ), + mImageSelected( NULL ), + mImageHoverSelected( NULL ), + mImageHoverUnselected( NULL ), + mImageDisabled( NULL ), + mImageDisabledSelected( NULL ), + mToggleState( FALSE ), + mScaleImage( TRUE ), + mDropShadowedText( TRUE ), + mBorderEnabled( FALSE ), + mFlashing( FALSE ), + mHAlign( LLFontGL::HCENTER ), + mLeftHPad( LLBUTTON_H_PAD ), + mRightHPad( LLBUTTON_H_PAD ), + mFixedWidth( 16 ), + mFixedHeight( 16 ), + mHoverGlowStrength(0.15f), + mCurGlowStrength(0.f), + mNeedsHighlight(FALSE), + mCommitOnReturn(TRUE), + mImagep( NULL ) +{ + mUnselectedLabel = name; + mSelectedLabel = name; + + setImageUnselected("button_enabled_32x128.tga"); + setImageSelected("button_enabled_selected_32x128.tga"); + setImageDisabled("button_disabled_32x128.tga"); + setImageDisabledSelected("button_disabled_32x128.tga"); + + mImageColor = LLUI::sColorsGroup->getColor( "ButtonImageColor" ); + mDisabledImageColor = LLUI::sColorsGroup->getColor( "ButtonImageColor" ); + + init(click_callback, callback_data, NULL, control_name); +} + + +LLButton::LLButton(const LLString& name, const LLRect& rect, + const LLString &unselected_image_name, + const LLString &selected_image_name, + const LLString& control_name, + void (*click_callback)(void*), + void *callback_data, + const LLFontGL *font, + const LLString& unselected_label, + const LLString& selected_label ) +: LLUICtrl(name, rect, TRUE, NULL, NULL), + mClickedCallback( click_callback ), + mMouseDownCallback( NULL ), + mMouseUpCallback( NULL ), + mHeldDownCallback( NULL ), + mGLFont( NULL ), + mHeldDownDelay( 0.5f ), // seconds until held-down callback is called + mImageUnselected( NULL ), + mImageSelected( NULL ), + mImageHoverSelected( NULL ), + mImageHoverUnselected( NULL ), + mImageDisabled( NULL ), + mImageDisabledSelected( NULL ), + mToggleState( FALSE ), + mScaleImage( TRUE ), + mDropShadowedText( TRUE ), + mBorderEnabled( FALSE ), + mFlashing( FALSE ), + mHAlign( LLFontGL::HCENTER ), + mLeftHPad( LLBUTTON_H_PAD ), + mRightHPad( LLBUTTON_H_PAD ), + mFixedWidth( 16 ), + mFixedHeight( 16 ), + mHoverGlowStrength(0.25f), + mCurGlowStrength(0.f), + mNeedsHighlight(FALSE), + mCommitOnReturn(TRUE), + mImagep( NULL ) +{ + mUnselectedLabel = unselected_label; + mSelectedLabel = selected_label; + + // by default, disabled color is same as enabled + mImageColor = LLUI::sColorsGroup->getColor( "ButtonImageColor" ); + mDisabledImageColor = LLUI::sColorsGroup->getColor( "ButtonImageColor" ); + + if( unselected_image_name != "" ) + { + setImageUnselected(unselected_image_name); + setImageDisabled(unselected_image_name); + + mDisabledImageColor.mV[VALPHA] = 0.5f; + mImageDisabled = mImageUnselected; + mDisabledImageColor.mV[VALPHA] = 0.5f; + // user-specified image - don't use fixed borders unless requested + mFixedWidth = 0; + mFixedHeight = 0; + mScaleImage = FALSE; + } + else + { + setImageUnselected("button_enabled_32x128.tga"); + setImageDisabled("button_disabled_32x128.tga"); + } + + if( selected_image_name != "" ) + { + setImageSelected(selected_image_name); + setImageDisabledSelected(selected_image_name); + + mDisabledImageColor.mV[VALPHA] = 0.5f; + // user-specified image - don't use fixed borders unless requested + mFixedWidth = 0; + mFixedHeight = 0; + mScaleImage = FALSE; + } + else + { + setImageSelected("button_enabled_selected_32x128.tga"); + setImageDisabledSelected("button_disabled_32x128.tga"); + } + + init(click_callback, callback_data, font, control_name); +} + +void LLButton::init(void (*click_callback)(void*), void *callback_data, const LLFontGL* font, const LLString& control_name) +{ + mGLFont = ( font ? font : LLFontGL::sSansSerif); + + // Hack to make sure there is space for at least one character + if (mRect.getWidth() - (mRightHPad + mLeftHPad) < mGLFont->getWidth(" ")) + { + // Use old defaults + mLeftHPad = LLBUTTON_ORIG_H_PAD; + mRightHPad = LLBUTTON_ORIG_H_PAD; + } + + mCallbackUserData = callback_data; + mMouseDownTimer.stop(); + + setControlName(control_name, NULL); + + mUnselectedLabelColor = ( LLUI::sColorsGroup->getColor( "ButtonLabelColor" ) ); + mSelectedLabelColor = ( LLUI::sColorsGroup->getColor( "ButtonLabelSelectedColor" ) ); + mDisabledLabelColor = ( LLUI::sColorsGroup->getColor( "ButtonLabelDisabledColor" ) ); + mDisabledSelectedLabelColor = ( LLUI::sColorsGroup->getColor( "ButtonLabelSelectedDisabledColor" ) ); + mHighlightColor = ( LLUI::sColorsGroup->getColor( "ButtonUnselectedFgColor" ) ); + mUnselectedBgColor = ( LLUI::sColorsGroup->getColor( "ButtonUnselectedBgColor" ) ); + mSelectedBgColor = ( LLUI::sColorsGroup->getColor( "ButtonSelectedBgColor" ) ); +} + +LLButton::~LLButton() +{ + if( this == gFocusMgr.getMouseCapture() ) + { + gFocusMgr.setMouseCapture( NULL, NULL ); + } +} + +// virtual +EWidgetType LLButton::getWidgetType() const +{ + return WIDGET_TYPE_BUTTON; +} + +// virtual +LLString LLButton::getWidgetTag() const +{ + return LL_BUTTON_TAG; +} + +// HACK: Committing a button is the same as instantly clicking it. +// virtual +void LLButton::onCommit() +{ + // WARNING: Sometimes clicking a button destroys the floater or + // panel containing it. Therefore we need to call mClickedCallback + // LAST, otherwise this becomes deleted memory. + LLUICtrl::onCommit(); + + if (mMouseDownCallback) + { + (*mMouseDownCallback)(mCallbackUserData); + } + + if (mMouseUpCallback) + { + (*mMouseUpCallback)(mCallbackUserData); + } + + if (mSoundFlags & MOUSE_DOWN) + { + make_ui_sound("UISndClick"); + } + + if (mSoundFlags & MOUSE_UP) + { + make_ui_sound("UISndClickRelease"); + } + + if (mClickedCallback) + { + (*mClickedCallback)( mCallbackUserData ); + } +} + +BOOL LLButton::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent) +{ + BOOL handled = FALSE; + if( getVisible() && mEnabled && !called_from_parent && ' ' == uni_char && !gKeyboard->getKeyRepeated(' ')) + { + if (mClickedCallback) + { + (*mClickedCallback)( mCallbackUserData ); + } + handled = TRUE; + } + return handled; +} + +BOOL LLButton::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent ) +{ + BOOL handled = FALSE; + if( getVisible() && mEnabled && !called_from_parent ) + { + if( mCommitOnReturn && KEY_RETURN == key && mask == MASK_NONE && !gKeyboard->getKeyRepeated(key)) + { + if (mClickedCallback) + { + (*mClickedCallback)( mCallbackUserData ); + } + handled = TRUE; + } + } + return handled; +} + + +BOOL LLButton::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // Route future Mouse messages here preemptively. (Release on mouse up.) + gFocusMgr.setMouseCapture( this, &LLButton::onMouseCaptureLost ); + + if (hasTabStop() && !getIsChrome()) + { + setFocus(TRUE); + } + + if (mMouseDownCallback) + { + (*mMouseDownCallback)(mCallbackUserData); + } + + mMouseDownTimer.start(); + + if (mSoundFlags & MOUSE_DOWN) + { + make_ui_sound("UISndClick"); + } + + return TRUE; +} + + +BOOL LLButton::handleMouseUp(S32 x, S32 y, MASK mask) +{ + // We only handle the click if the click both started and ended within us + if( this == gFocusMgr.getMouseCapture() ) + { + // Regardless of where mouseup occurs, handle callback + if (mMouseUpCallback) + { + (*mMouseUpCallback)(mCallbackUserData); + } + + mMouseDownTimer.stop(); + + // Always release the mouse + gFocusMgr.setMouseCapture( NULL, NULL ); + + // DO THIS AT THE VERY END to allow the button to be destroyed as a result of being clicked. + // If mouseup in the widget, it's been clicked + if (pointInView(x, y)) + { + if (mSoundFlags & MOUSE_UP) + { + make_ui_sound("UISndClickRelease"); + } + + if (mClickedCallback) + { + (*mClickedCallback)( mCallbackUserData ); + } + } + } + + return TRUE; +} + + +BOOL LLButton::handleHover(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + LLMouseHandler* other_captor = gFocusMgr.getMouseCapture(); + mNeedsHighlight = other_captor == NULL || + other_captor == this || + // this following bit is to support modal dialogs + (other_captor->isView() && hasAncestor((LLView*)other_captor)); + + if (mMouseDownTimer.getStarted() && NULL != mHeldDownCallback) + { + F32 elapsed = mMouseDownTimer.getElapsedTimeF32(); + if( mHeldDownDelay < elapsed ) + { + mHeldDownCallback( mCallbackUserData ); + } + } + + // We only handle the click if the click both started and ended within us + if( this == gFocusMgr.getMouseCapture() ) + { + handled = TRUE; + } + else if( getVisible() ) + { + // Opaque + handled = TRUE; + } + + if( handled ) + { + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; + } + + return handled; +} + + +// virtual +void LLButton::draw() +{ + if( getVisible() ) + { + BOOL flash = FALSE; + if( mFlashing ) + { + F32 elapsed = LLButton::sFlashingTimer.getElapsedTimeF32(); + flash = S32(elapsed * 2) & 1; + } + + BOOL pressed_by_keyboard = FALSE; + if (hasFocus()) + { + pressed_by_keyboard = gKeyboard->getKeyDown(' ') || (mCommitOnReturn && gKeyboard->getKeyDown(KEY_RETURN)); + } + + // Unselected image assignments + S32 local_mouse_x; + S32 local_mouse_y; + LLCoordWindow cursor_pos_window; + getWindow()->getCursorPosition(&cursor_pos_window); + LLCoordGL cursor_pos_gl; + getWindow()->convertCoords(cursor_pos_window, &cursor_pos_gl); + cursor_pos_gl.mX = llround((F32)cursor_pos_gl.mX / LLUI::sGLScaleFactor.mV[VX]); + cursor_pos_gl.mY = llround((F32)cursor_pos_gl.mY / LLUI::sGLScaleFactor.mV[VY]); + screenPointToLocal(cursor_pos_gl.mX, cursor_pos_gl.mY, &local_mouse_x, &local_mouse_y); + + BOOL pressed = pressed_by_keyboard || (this == gFocusMgr.getMouseCapture() && pointInView(local_mouse_x, local_mouse_y)); + + BOOL display_state = FALSE; + if( pressed ) + { + mImagep = mImageSelected; + // show the resulting state after releasing the mouse button while it is down + display_state = mToggleState ? FALSE : TRUE; + } + else + { + display_state = mToggleState || flash; + } + + BOOL use_glow_effect = FALSE; + if ( mNeedsHighlight ) + { + if (display_state) + { + if (mImageHoverSelected) + { + mImagep = mImageHoverSelected; + } + else + { + mImagep = mImageSelected; + use_glow_effect = TRUE; + } + } + else + { + if (mImageHoverUnselected) + { + mImagep = mImageHoverUnselected; + } + else + { + mImagep = mImageUnselected; + use_glow_effect = TRUE; + } + } + } + else if ( display_state ) + { + mImagep = mImageSelected; + } + else + { + mImagep = mImageUnselected; + } + + // Override if more data is available + // HACK: Use gray checked state to mean either: + // enabled and tentative + // or + // disabled but checked + if (!mImageDisabledSelected.isNull() && ( (mEnabled && mTentative) || (!mEnabled && display_state ) ) ) + { + mImagep = mImageDisabledSelected; + } + else if (!mImageDisabled.isNull() && !mEnabled && !display_state) + { + mImagep = mImageDisabled; + } + + if (mNeedsHighlight && !mImagep) + { + use_glow_effect = TRUE; + } + + // Figure out appropriate color for the text + LLColor4 label_color; + + if ( mEnabled ) + { + if ( !display_state ) + { + label_color = mUnselectedLabelColor; + } + else + { + label_color = mSelectedLabelColor; + } + } + else + { + if ( !display_state ) + { + label_color = mDisabledLabelColor; + } + else + { + label_color = mDisabledSelectedLabelColor; + } + } + + // Unselected label assignments + LLWString label; + + if( display_state ) + { + if( mEnabled || mDisabledSelectedLabel.empty() ) + { + label = mSelectedLabel; + } + else + { + label = mDisabledSelectedLabel; + } + } + else + { + if( mEnabled || mDisabledLabel.empty() ) + { + label = mUnselectedLabel; + } + else + { + label = mDisabledLabel; + } + } + + // draw default button border + if (mEnabled && mBorderEnabled && gFocusMgr.getAppHasFocus()) // because we're the default button in a panel + { + drawBorder(LLUI::sColorsGroup->getColor( "ButtonBorderColor" ), BORDER_SIZE); + } + + // overlay with keyboard focus border + if (hasFocus()) + { + F32 lerp_amt = gFocusMgr.getFocusFlashAmt(); + drawBorder(gFocusMgr.getFocusColor(), llround(lerp(1.f, 3.f, lerp_amt))); + } + + if (use_glow_effect) + { + mCurGlowStrength = lerp(mCurGlowStrength, mHoverGlowStrength, LLCriticalDamp::getInterpolant(0.05f)); + } + else + { + mCurGlowStrength = lerp(mCurGlowStrength, 0.f, LLCriticalDamp::getInterpolant(0.05f)); + } + + // Draw button image, if available. + // Otherwise draw basic rectangular button. + if( mImagep.notNull() && !mScaleImage) + { + gl_draw_image( 0, 0, mImagep, mEnabled ? mImageColor : mDisabledImageColor ); + if (mCurGlowStrength > 0.01f) + { + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + gl_draw_scaled_image_with_border(0, 0, 0, 0, mImagep->getWidth(), mImagep->getHeight(), mImagep, LLColor4(1.f, 1.f, 1.f, mCurGlowStrength), TRUE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + } + else + if ( mImagep.notNull() && mScaleImage) + { + gl_draw_scaled_image_with_border(0, 0, mFixedWidth, mFixedHeight, mRect.getWidth(), mRect.getHeight(), + mImagep, mEnabled ? mImageColor : mDisabledImageColor ); + if (mCurGlowStrength > 0.01f) + { + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + gl_draw_scaled_image_with_border(0, 0, mFixedWidth, mFixedHeight, mRect.getWidth(), mRect.getHeight(), + mImagep, LLColor4(1.f, 1.f, 1.f, mCurGlowStrength), TRUE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + } + else + { + // no image + llalerts << "No image for button " << mName << llendl; + // draw it in pink so we can find it + gl_rect_2d(0, mRect.getHeight(), mRect.getWidth(), 0, LLColor4::pink1, FALSE); + } + + // Draw label + if( !label.empty() ) + { + S32 drawable_width = mRect.getWidth() - mLeftHPad - mRightHPad; + + LLWString::trim(label); + + S32 x; + switch( mHAlign ) + { + case LLFontGL::RIGHT: + x = mRect.getWidth() - mRightHPad; + break; + case LLFontGL::HCENTER: + x = mRect.getWidth() / 2; + break; + case LLFontGL::LEFT: + default: + x = mLeftHPad; + break; + } + + S32 y_offset = 2 + (mRect.getHeight() - 20)/2; + + if (pressed || display_state) + { + y_offset--; + x++; + } + + mGLFont->render(label, 0, (F32)x, (F32)(LLBUTTON_V_PAD + y_offset), + label_color, + mHAlign, LLFontGL::BOTTOM, + mDropShadowedText ? LLFontGL::DROP_SHADOW : LLFontGL::NORMAL, + U32_MAX, drawable_width, + NULL, FALSE, FALSE); + } + + if (sDebugRects + || (LLView::sEditingUI && this == LLView::sEditingUIView)) + { + drawDebugRect(); + } + } + // reset hover status for next frame + mNeedsHighlight = FALSE; +} + +void LLButton::drawBorder(const LLColor4& color, S32 size) +{ + S32 left = -size; + S32 top = mRect.getHeight() + size; + S32 right = mRect.getWidth() + size; + S32 bottom = -size; + + if (mImagep.isNull()) + { + gl_rect_2d(left, top, right, bottom, color, FALSE); + return; + } + + if (mScaleImage) + { + gl_draw_scaled_image_with_border(left, bottom, mFixedWidth, mFixedHeight, right-left, top-bottom, + mImagep, color, TRUE ); + } + else + { + gl_draw_scaled_image_with_border(left, bottom, 0, 0, mImagep->getWidth() + size * 2, + mImagep->getHeight() + size * 2, mImagep, color, TRUE ); + } +} + +void LLButton::setClickedCallback(void (*cb)(void*), void* userdata) +{ + mClickedCallback = cb; + if (userdata) + { + mCallbackUserData = userdata; + } +} + + +void LLButton::setToggleState(BOOL b) +{ + if( b != mToggleState ) + { + mToggleState = b; + LLValueChangedEvent *evt = new LLValueChangedEvent(this, mToggleState); + fireEvent(evt, ""); + } +} + +void LLButton::setValue(const LLSD& value ) +{ + mToggleState = value.asBoolean(); +} + +LLSD LLButton::getValue() const +{ + return mToggleState; +} + +void LLButton::setLabel( const LLString& label ) +{ + setLabelUnselected(label); + setLabelSelected(label); +} + +//virtual +BOOL LLButton::setLabelArg( const LLString& key, const LLString& text ) +{ + mUnselectedLabel.setArg(key, text); + mSelectedLabel.setArg(key, text); + return TRUE; +} + +void LLButton::setLabelUnselected( const LLString& label ) +{ + mUnselectedLabel = label; +} + +void LLButton::setLabelSelected( const LLString& label ) +{ + mSelectedLabel = label; +} + +void LLButton::setDisabledLabel( const LLString& label ) +{ + mDisabledLabel = label; +} + +void LLButton::setDisabledSelectedLabel( const LLString& label ) +{ + mDisabledSelectedLabel = label; +} + +void LLButton::setImageUnselectedID( const LLUUID &image_id ) +{ + mImageUnselectedName = ""; + mImageUnselected = LLUI::sImageProvider->getUIImageByID(image_id); +} + +void LLButton::setImages( const LLString &image_name, const LLString &selected_name ) +{ + setImageUnselected(image_name); + setImageSelected(selected_name); + +} + +void LLButton::setImageSelectedID( const LLUUID &image_id ) +{ + mImageSelectedName = ""; + mImageSelected = LLUI::sImageProvider->getUIImageByID(image_id); +} + +void LLButton::setImageColor(const LLColor4& c) +{ + mImageColor = c; +} + + +void LLButton::setImageDisabledID( const LLUUID &image_id ) +{ + mImageDisabledName = ""; + mImageDisabled = LLUI::sImageProvider->getUIImageByID(image_id); + mDisabledImageColor = mImageColor; + mDisabledImageColor.mV[VALPHA] *= 0.5f; +} + +void LLButton::setImageDisabledSelectedID( const LLUUID &image_id ) +{ + mImageDisabledSelectedName = ""; + mImageDisabledSelected = LLUI::sImageProvider->getUIImageByID(image_id); + mDisabledImageColor = mImageColor; + mDisabledImageColor.mV[VALPHA] *= 0.5f; +} + +void LLButton::setDisabledImages( const LLString &image_name, const LLString &selected_name, const LLColor4& c ) +{ + setImageDisabled(image_name); + setImageDisabledSelected(selected_name); + mDisabledImageColor = c; +} + + +void LLButton::setImageHoverSelectedID( const LLUUID& image_id ) +{ + mImageHoverSelectedName = ""; + mImageHoverSelected = LLUI::sImageProvider->getUIImageByID(image_id); +} + +void LLButton::setDisabledImages( const LLString &image_name, const LLString &selected_name) +{ + LLColor4 clr = mImageColor; + clr.mV[VALPHA] *= .5f; + setDisabledImages( image_name, selected_name, clr ); +} + +void LLButton::setImageHoverUnselectedID( const LLUUID& image_id ) +{ + mImageHoverUnselectedName = ""; + mImageHoverUnselected = LLUI::sImageProvider->getUIImageByID(image_id); +} + +void LLButton::setHoverImages( const LLString& image_name, const LLString& selected_name ) +{ + setImageHoverUnselected(image_name); + setImageHoverSelected(selected_name); +} + +// static +void LLButton::onMouseCaptureLost( LLMouseHandler* old_captor ) +{ + LLButton* self = (LLButton*) old_captor; + self->mMouseDownTimer.stop(); +} + +//------------------------------------------------------------------------- +// LLSquareButton +//------------------------------------------------------------------------- +LLSquareButton::LLSquareButton(const LLString& name, const LLRect& rect, + const LLString& label, + const LLFontGL *font, + const LLString& control_name, + void (*click_callback)(void*), + void *callback_data, + const LLString& selected_label ) +: LLButton(name, rect, "","", + control_name, + click_callback, callback_data, + font, + label, + (selected_label.empty() ? label : selected_label) ) +{ + setImageUnselected("square_btn_32x128.tga"); + // mImageUnselected = LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("square_btn_32x128.tga"))); + setImageSelected("square_btn_selected_32x128.tga"); + // mImageSelectedImage = LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("square_btn_selected_32x128.tga"))); + setImageDisabled("square_btn_32x128.tga"); + //mDisabledImage = LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("square_btn_32x128.tga"))); + setImageDisabledSelected("square_btn_selected_32x128.tga"); + //mDisabledSelectedImage = LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("square_btn_selected_32x128.tga"))); + mImageColor = LLUI::sColorsGroup->getColor("ButtonColor"); +} + +//------------------------------------------------------------------------- +// Utilities +//------------------------------------------------------------------------- +S32 round_up(S32 grid, S32 value) +{ + S32 mod = value % grid; + + if (mod > 0) + { + // not even multiple + return value + (grid - mod); + } + else + { + return value; + } +} + +void LLButton::setImageUnselected(const LLString &image_name) +{ + setImageUnselectedID(LLUI::findAssetUUIDByName(image_name)); + mImageUnselectedName = image_name; +} + +void LLButton::setImageSelected(const LLString &image_name) +{ + setImageSelectedID(LLUI::findAssetUUIDByName(image_name)); + mImageSelectedName = image_name; +} + +void LLButton::setImageHoverSelected(const LLString &image_name) +{ + setImageHoverSelectedID(LLUI::findAssetUUIDByName(image_name)); + mImageHoverSelectedName = image_name; +} + +void LLButton::setImageHoverUnselected(const LLString &image_name) +{ + setImageHoverUnselectedID(LLUI::findAssetUUIDByName(image_name)); + mImageHoverUnselectedName = image_name; +} + +void LLButton::setImageDisabled(const LLString &image_name) +{ + setImageDisabledID(LLUI::findAssetUUIDByName(image_name)); + mImageDisabledName = image_name; +} + +void LLButton::setImageDisabledSelected(const LLString &image_name) +{ + setImageDisabledSelectedID(LLUI::findAssetUUIDByName(image_name)); + mImageDisabledSelectedName = image_name; +} + +void LLButton::addImageAttributeToXML(LLXMLNodePtr node, + const LLString& image_name, + const LLUUID& image_id, + const LLString& xml_tag_name) const +{ + if( !image_name.empty() ) + { + node->createChild(xml_tag_name, TRUE)->setStringValue(image_name); + } + else if( image_id != LLUUID::null ) + { + node->createChild(xml_tag_name + "_id", TRUE)->setUUIDValue(image_id); + } +} + +// virtual +LLXMLNodePtr LLButton::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + node->createChild("label", TRUE)->setStringValue(getLabelUnselected()); + node->createChild("label_selected", TRUE)->setStringValue(getLabelSelected()); + node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mGLFont)); + node->createChild("halign", TRUE)->setStringValue(LLFontGL::nameFromHAlign(mHAlign)); + node->createChild("border_width", TRUE)->setIntValue(mFixedWidth); + node->createChild("border_height", TRUE)->setIntValue(mFixedHeight); + + addImageAttributeToXML(node,mImageUnselectedName,mImageUnselectedID,"image_unselected"); + addImageAttributeToXML(node,mImageSelectedName,mImageSelectedID,"image_selected"); + addImageAttributeToXML(node,mImageHoverSelectedName,mImageHoverSelectedID,"image_hover_selected"); + addImageAttributeToXML(node,mImageHoverUnselectedName,mImageHoverUnselectedID,"image_hover_unselected"); + addImageAttributeToXML(node,mImageDisabledName,mImageDisabledID,"image_disabled"); + addImageAttributeToXML(node,mImageDisabledSelectedName,mImageDisabledSelectedID,"image_disabled_selected"); + + node->createChild("scale_image", TRUE)->setBoolValue(mScaleImage); + + return node; +} + +// static +LLView* LLButton::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("button"); + node->getAttributeString("name", name); + + LLString label = name; + node->getAttributeString("label", label); + + LLString label_selected = label; + node->getAttributeString("label_selected", label_selected); + + LLFontGL* font = selectFont(node); + + LLString image_unselected; + if (node->hasAttribute("image_unselected")) node->getAttributeString("image_unselected",image_unselected); + + LLString image_selected; + if (node->hasAttribute("image_selected")) node->getAttributeString("image_selected",image_selected); + + LLString image_hover_selected; + if (node->hasAttribute("image_hover_selected")) node->getAttributeString("image_hover_selected",image_hover_selected); + + LLString image_hover_unselected; + if (node->hasAttribute("image_hover_unselected")) node->getAttributeString("image_hover_unselected",image_hover_unselected); + + LLString image_disabled_selected; + if (node->hasAttribute("image_disabled_selected")) node->getAttributeString("image_disabled_selected",image_disabled_selected); + + LLString image_disabled; + if (node->hasAttribute("image_disabled")) node->getAttributeString("image_disabled",image_disabled); + + LLButton *button = new LLButton(name, + LLRect(), + image_unselected, + image_selected, + "", + NULL, + parent, + font, + label, + label_selected); + + node->getAttributeS32("border_width", button->mFixedWidth); + node->getAttributeS32("border_height", button->mFixedHeight); + + if(image_hover_selected != LLString::null) button->setImageHoverSelected(image_hover_selected); + + if(image_hover_unselected != LLString::null) button->setImageHoverUnselected(image_hover_unselected); + + if(image_disabled_selected != LLString::null) button->setImageDisabledSelected(image_disabled_selected ); + + if(image_disabled != LLString::null) button->setImageDisabled(image_disabled); + + + if (node->hasAttribute("halign")) + { + LLFontGL::HAlign halign = selectFontHAlign(node); + button->setHAlign(halign); + } + + if (node->hasAttribute("scale_image")) + { + BOOL needsScale = FALSE; + node->getAttributeBOOL("scale_image",needsScale); + button->setScaleImage( needsScale ); + } + + if(label.empty()) + { + button->setLabelUnselected(node->getTextContents()); + } + if (label_selected.empty()) + { + button->setLabelSelected(node->getTextContents()); + } + + button->initFromXML(node, parent); + + return button; +} + diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h new file mode 100644 index 0000000000..0a4e41b017 --- /dev/null +++ b/indra/llui/llbutton.h @@ -0,0 +1,261 @@ +/** + * @file llbutton.h + * @brief Header for buttons + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLBUTTON_H +#define LL_LLBUTTON_H + +#include "lluuid.h" +#include "llcontrol.h" +#include "lluictrl.h" +#include "v4color.h" +#include "llframetimer.h" +#include "llfontgl.h" +#include "llimage.h" +#include "lluistring.h" + +// +// Constants +// + +// PLEASE please use these "constants" when building your own buttons. +// They are loaded from settings.xml at run time. +extern S32 LLBUTTON_H_PAD; +extern S32 LLBUTTON_V_PAD; +extern S32 BTN_HEIGHT_SMALL; +extern S32 BTN_HEIGHT; + + +// All button widths should be rounded up to this size +extern S32 BTN_GRID; + +// +// Classes +// + +class LLButton +: public LLUICtrl +{ +public: + // simple button with text label + LLButton(const LLString& name, const LLRect &rect, const LLString& control_name = "", + void (*on_click)(void*) = NULL, void *data = NULL); + + LLButton(const LLString& name, const LLRect& rect, + const LLString &unselected_image, + const LLString &selected_image, + const LLString& control_name, + void (*click_callback)(void*), + void *callback_data = NULL, + const LLFontGL* mGLFont = NULL, + const LLString& unselected_label = LLString::null, + const LLString& selected_label = LLString::null ); + + virtual ~LLButton(); + void init(void (*click_callback)(void*), void *callback_data, const LLFontGL* font, const LLString& control_name); + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + void addImageAttributeToXML(LLXMLNodePtr node, const LLString& imageName, + const LLUUID& imageID,const LLString& xmlTagName) const; + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + virtual BOOL handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent); + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual void draw(); + + // HACK: "committing" a button is the same as clicking on it. + virtual void onCommit(); + + void setUnselectedLabelColor( const LLColor4& c ) { mUnselectedLabelColor = c; } + void setSelectedLabelColor( const LLColor4& c ) { mSelectedLabelColor = c; } + + void setClickedCallback( void (*cb)(void *data), void* data = NULL ); // mouse down and up within button + void setMouseDownCallback( void (*cb)(void *data) ) { mMouseDownCallback = cb; } // mouse down within button + void setMouseUpCallback( void (*cb)(void *data) ) { mMouseUpCallback = cb; } // mouse up, EVEN IF NOT IN BUTTON + void setHeldDownCallback( void (*cb)(void *data) ) { mHeldDownCallback = cb; } // Mouse button held down and in button + void setHeldDownDelay( F32 seconds) { mHeldDownDelay = seconds; } + + F32 getHeldDownTime() const { return mMouseDownTimer.getElapsedTimeF32(); } + + BOOL toggleState() { setToggleState( !mToggleState ); return mToggleState; } + BOOL getToggleState() const { return mToggleState; } + void setToggleState(BOOL b); + + void setFlashing( BOOL b ) { mFlashing = b; } + BOOL getFlashing() const { return mFlashing; } + + void setHAlign( LLFontGL::HAlign align ) { mHAlign = align; } + void setLeftHPad( S32 pad ) { mLeftHPad = pad; } + void setRightHPad( S32 pad ) { mRightHPad = pad; } + + const LLString getLabelUnselected() const { return wstring_to_utf8str(mUnselectedLabel); } + const LLString getLabelSelected() const { return wstring_to_utf8str(mSelectedLabel); } + + + // HACK to allow images to be freed when the caller knows he's done with it. + LLImageGL* getImageUnselected() const { return mImageUnselected; } + + void setImageColor(const LLString& color_control); + void setImages(const LLString &image_name, const LLString &selected_name); + void setImageColor(const LLColor4& c); + + void setDisabledImages(const LLString &image_name, const LLString &selected_name); + void setDisabledImages(const LLString &image_name, const LLString &selected_name, const LLColor4& c); + + void setHoverImages(const LLString &image_name, const LLString &selected_name); + + void setDisabledImageColor(const LLColor4& c) { mDisabledImageColor = c; } + + void setDisabledSelectedLabelColor( const LLColor4& c ) { mDisabledSelectedLabelColor = c; } + + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + + void setLabel( const LLString& label); + virtual BOOL setLabelArg( const LLString& key, const LLString& text ); + void setLabelUnselected(const LLString& label); + void setLabelSelected(const LLString& label); + void setDisabledLabel(const LLString& disabled_label); + void setDisabledSelectedLabel(const LLString& disabled_label); + void setDisabledLabelColor( const LLColor4& c ) { mDisabledLabelColor = c; } + + void setFont(const LLFontGL *font) + { mGLFont = ( font ? font : LLFontGL::sSansSerif); } + void setScaleImage(BOOL scale) { mScaleImage = scale; } + + void setDropShadowedText(BOOL b) { mDropShadowedText = b; } + + void setBorderEnabled(BOOL b) { mBorderEnabled = b; } + + static void onHeldDown(void *userdata); // to be called by gIdleCallbacks + static void onMouseCaptureLost(LLMouseHandler* old_captor); + + void setFixedBorder(S32 width, S32 height) { mFixedWidth = width; mFixedHeight = height; } + void setHoverGlowStrength(F32 strength) { mHoverGlowStrength = strength; } + +private: + void setImageUnselectedID(const LLUUID &image_id); + void setImageSelectedID(const LLUUID &image_id); + void setImageHoverSelectedID(const LLUUID &image_id); + void setImageHoverUnselectedID(const LLUUID &image_id); + void setImageDisabledID(const LLUUID &image_id); + void setImageDisabledSelectedID(const LLUUID &image_id); +public: + void setImageUnselected(const LLString &image_name); + void setImageSelected(const LLString &image_name); + void setImageHoverSelected(const LLString &image_name); + void setImageHoverUnselected(const LLString &image_name); + void setImageDisabled(const LLString &image_name); + void setImageDisabledSelected(const LLString &image_name); + void setCommitOnReturn(BOOL commit) { mCommitOnReturn = commit; } + BOOL getCommitOnReturn() { return mCommitOnReturn; } + +protected: + virtual void drawBorder(const LLColor4& color, S32 size); + +protected: + + void (*mClickedCallback)(void* data ); + void (*mMouseDownCallback)(void *data); + void (*mMouseUpCallback)(void *data); + void (*mHeldDownCallback)(void *data); + + const LLFontGL *mGLFont; + + LLFrameTimer mMouseDownTimer; + F32 mHeldDownDelay; // seconds, after which held-down callbacks get called + + LLPointer mImageUnselected; + LLUIString mUnselectedLabel; + LLColor4 mUnselectedLabelColor; + + LLPointer mImageSelected; + LLUIString mSelectedLabel; + LLColor4 mSelectedLabelColor; + + LLPointer mImageHoverSelected; + + LLPointer mImageHoverUnselected; + + LLPointer mImageDisabled; + LLUIString mDisabledLabel; + LLColor4 mDisabledLabelColor; + + LLPointer mImageDisabledSelected; + LLUIString mDisabledSelectedLabel; + LLColor4 mDisabledSelectedLabelColor; + + + LLUUID mImageUnselectedID; + LLUUID mImageSelectedID; + LLUUID mImageHoverSelectedID; + LLUUID mImageHoverUnselectedID; + LLUUID mImageDisabledID; + LLUUID mImageDisabledSelectedID; + LLString mImageUnselectedName; + LLString mImageSelectedName; + LLString mImageHoverSelectedName; + LLString mImageHoverUnselectedName; + LLString mImageDisabledName; + LLString mImageDisabledSelectedName; + + LLColor4 mHighlightColor; + LLColor4 mUnselectedBgColor; + LLColor4 mSelectedBgColor; + + LLColor4 mImageColor; + LLColor4 mDisabledImageColor; + + BOOL mToggleState; + BOOL mScaleImage; + + BOOL mDropShadowedText; + + BOOL mBorderEnabled; + + BOOL mFlashing; + + LLFontGL::HAlign mHAlign; + S32 mLeftHPad; + S32 mRightHPad; + + S32 mFixedWidth; + S32 mFixedHeight; + + F32 mHoverGlowStrength; + F32 mCurGlowStrength; + + BOOL mNeedsHighlight; + BOOL mCommitOnReturn; + + LLPointer mImagep; + + static LLFrameTimer sFlashingTimer; +}; + +class LLSquareButton +: public LLButton +{ +public: + LLSquareButton(const LLString& name, const LLRect& rect, + const LLString& label, + const LLFontGL *font = NULL, + const LLString& control_name = "", + void (*click_callback)(void*) = NULL, + void *callback_data = NULL, + const LLString& selected_label = LLString::null ); +}; + +// Helpful functions +S32 round_up(S32 grid, S32 value); + +#endif // LL_LLBUTTON_H diff --git a/indra/llui/llcallbackmap.h b/indra/llui/llcallbackmap.h new file mode 100644 index 0000000000..dfc965aa08 --- /dev/null +++ b/indra/llui/llcallbackmap.h @@ -0,0 +1,36 @@ +/** + * @file llcallbackmap.h + * @brief LLCallbackMap base class + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// llcallbackmap.h +// +// Copyright 2006, Linden Research, Inc. + +#ifndef LL_CALLBACK_MAP_H +#define LL_CALLBACK_MAP_H + +#include +#include "llstring.h" + +class LLCallbackMap +{ +public: + // callback definition. + typedef void* (*callback_t)(void* data); + + typedef std::map map_t; + typedef map_t::iterator map_iter_t; + typedef map_t::const_iterator map_const_iter_t; + + LLCallbackMap() : mCallback(NULL), mData(NULL) { } + LLCallbackMap(callback_t callback, void* data) : mCallback(callback), mData(data) { } + + callback_t mCallback; + void* mData; +}; + +#endif // LL_CALLBACK_MAP_H diff --git a/indra/llui/llcheckboxctrl.cpp b/indra/llui/llcheckboxctrl.cpp new file mode 100644 index 0000000000..3b054d2fec --- /dev/null +++ b/indra/llui/llcheckboxctrl.cpp @@ -0,0 +1,315 @@ +/** + * @file llcheckboxctrl.cpp + * @brief LLCheckBoxCtrl base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// The mutants are coming! + +#include "linden_common.h" + +#include "llcheckboxctrl.h" + +#include "llgl.h" +#include "llui.h" +#include "lluiconstants.h" +#include "lluictrlfactory.h" +#include "llcontrol.h" + +#include "llstring.h" +#include "llfontgl.h" +#include "lltextbox.h" +#include "llkeyboard.h" +#include "llviewborder.h" + +const U32 MAX_STRING_LENGTH = 10; + + +LLCheckBoxCtrl::LLCheckBoxCtrl(const LLString& name, const LLRect& rect, + const LLString& label, + const LLFontGL* font, + void (*commit_callback)(LLUICtrl* ctrl, void* userdata), + void* callback_user_data, + BOOL initial_value, + BOOL use_radio_style, + const LLString& control_which) +: LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data, FOLLOWS_LEFT | FOLLOWS_TOP), + mTextEnabledColor( LLUI::sColorsGroup->getColor( "LabelTextColor" ) ), + mTextDisabledColor( LLUI::sColorsGroup->getColor( "LabelDisabledColor" ) ), + mRadioStyle( use_radio_style ), + mInitialValue( initial_value ) +{ + if (font) + { + mFont = font; + } + else + { + mFont = LLFontGL::sSansSerifSmall; + } + + // must be big enough to hold all children + setSpanChildren(TRUE); + + mKeyboardFocusOnClick = TRUE; + + // Label (add a little space to make sure text actually renders) + const S32 FUDGE = 10; + S32 text_width = mFont->getWidth( label ) + FUDGE; + S32 text_height = llround(mFont->getLineHeight()); + LLRect label_rect; + label_rect.setOriginAndSize( + LLCHECKBOXCTRL_HPAD + LLCHECKBOXCTRL_BTN_SIZE + LLCHECKBOXCTRL_SPACING, + LLCHECKBOXCTRL_VPAD + 1, // padding to get better alignment + text_width + LLCHECKBOXCTRL_HPAD, + text_height ); + mLabel = new LLTextBox( "CheckboxCtrl Label", label_rect, label.c_str(), mFont ); + mLabel->setFollowsLeft(); + mLabel->setFollowsBottom(); + addChild(mLabel); + + // Button + // Note: button cover the label by extending all the way to the right. + LLRect btn_rect; + btn_rect.setOriginAndSize( + LLCHECKBOXCTRL_HPAD, + LLCHECKBOXCTRL_VPAD, + LLCHECKBOXCTRL_BTN_SIZE + LLCHECKBOXCTRL_SPACING + text_width + LLCHECKBOXCTRL_HPAD, + llmax( text_height, LLCHECKBOXCTRL_BTN_SIZE ) + LLCHECKBOXCTRL_VPAD); + LLString active_true_id, active_false_id; + LLString inactive_true_id, inactive_false_id; + if (mRadioStyle) + { + active_true_id = "UIImgRadioActiveSelectedUUID"; + active_false_id = "UIImgRadioActiveUUID"; + inactive_true_id = "UIImgRadioInactiveSelectedUUID"; + inactive_false_id = "UIImgRadioInactiveUUID"; + mButton = new LLButton( + "Radio control button", btn_rect, + active_false_id, active_true_id, control_which, + &LLCheckBoxCtrl::onButtonPress, this, LLFontGL::sSansSerif ); + mButton->setDisabledImages( inactive_false_id, inactive_true_id ); + mButton->setHoverGlowStrength(0.35f); + } + else + { + active_false_id = "UIImgCheckboxActiveUUID"; + active_true_id = "UIImgCheckboxActiveSelectedUUID"; + inactive_true_id = "UIImgCheckboxInactiveSelectedUUID"; + inactive_false_id = "UIImgCheckboxInactiveUUID"; + mButton = new LLButton( + "Checkbox control button", btn_rect, + active_false_id, active_true_id, control_which, + &LLCheckBoxCtrl::onButtonPress, this, LLFontGL::sSansSerif ); + mButton->setDisabledImages( inactive_false_id, inactive_true_id ); + mButton->setHoverGlowStrength(0.35f); + } + mButton->setToggleState( initial_value ); + mButton->setFollowsLeft(); + mButton->setFollowsBottom(); + mButton->setCommitOnReturn(FALSE); + addChild(mButton); +} + +LLCheckBoxCtrl::~LLCheckBoxCtrl() +{ + // Children all cleaned up by default view destructor. +} + + +// static +void LLCheckBoxCtrl::onButtonPress( void *userdata ) +{ + LLCheckBoxCtrl* self = (LLCheckBoxCtrl*) userdata; + + if (self->mRadioStyle) + { + if (!self->getValue()) + { + self->setValue(TRUE); + } + } + else + { + self->toggle(); + } + self->setControlValue(self->getValue()); + self->onCommit(); + + if (self->mKeyboardFocusOnClick) + { + self->setFocus( TRUE ); + self->onFocusReceived(); + } +} + +void LLCheckBoxCtrl::onCommit() +{ + if( getEnabled() ) + { + setTentative(FALSE); + LLUICtrl::onCommit(); + } +} + +void LLCheckBoxCtrl::setEnabled(BOOL b) +{ + LLUICtrl::setEnabled(b); + mButton->setEnabled(b); +} + +void LLCheckBoxCtrl::clear() +{ + setValue( FALSE ); +} + +void LLCheckBoxCtrl::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + //stretch or shrink bounding rectangle of label when rebuilding UI at new scale + const S32 FUDGE = 10; + S32 text_width = mFont->getWidth( mLabel->getText() ) + FUDGE; + S32 text_height = llround(mFont->getLineHeight()); + LLRect label_rect; + label_rect.setOriginAndSize( + LLCHECKBOXCTRL_HPAD + LLCHECKBOXCTRL_BTN_SIZE + LLCHECKBOXCTRL_SPACING, + LLCHECKBOXCTRL_VPAD, + text_width, + text_height ); + mLabel->setRect(label_rect); + + LLRect btn_rect; + btn_rect.setOriginAndSize( + LLCHECKBOXCTRL_HPAD, + LLCHECKBOXCTRL_VPAD, + LLCHECKBOXCTRL_BTN_SIZE + LLCHECKBOXCTRL_SPACING + text_width, + llmax( text_height, LLCHECKBOXCTRL_BTN_SIZE ) ); + mButton->setRect( btn_rect ); + + LLUICtrl::reshape(width, height, called_from_parent); +} + +void LLCheckBoxCtrl::draw() +{ + if (mEnabled) + { + mLabel->setColor( mTextEnabledColor ); + } + else + { + mLabel->setColor( mTextDisabledColor ); + } + + // Draw children + LLUICtrl::draw(); +} + +//virtual +void LLCheckBoxCtrl::setValue(const LLSD& value ) +{ + mButton->setToggleState( value.asBoolean() ); +} + +//virtual +LLSD LLCheckBoxCtrl::getValue() const +{ + return mButton->getToggleState(); +} + +void LLCheckBoxCtrl::setLabel( const LLString& label ) +{ + mLabel->setText( label ); + reshape(getRect().getWidth(), getRect().getHeight(), FALSE); +} + +LLString LLCheckBoxCtrl::getLabel() const +{ + return mLabel->getText(); +} + +BOOL LLCheckBoxCtrl::setLabelArg( const LLString& key, const LLString& text ) +{ + BOOL res = mLabel->setTextArg(key, text); + reshape(getRect().getWidth(), getRect().getHeight(), FALSE); + return res; +} + +//virtual +LLString LLCheckBoxCtrl::getControlName() const +{ + return mButton->getControlName(); +} + +// virtual +void LLCheckBoxCtrl::setControlName(const LLString& control_name, LLView* context) +{ + mButton->setControlName(control_name, context); +} + +// virtual +LLXMLNodePtr LLCheckBoxCtrl::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + node->createChild("label", TRUE)->setStringValue(mLabel->getText()); + + LLString control_name = mButton->getControlName(); + + node->createChild("initial_value", TRUE)->setBoolValue(mInitialValue); + + node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mFont)); + + node->createChild("radio_style", TRUE)->setBoolValue(mRadioStyle); + + return node; +} + +// static +LLView* LLCheckBoxCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("checkbox"); + node->getAttributeString("name", name); + + LLString label(""); + node->getAttributeString("label", label); + + BOOL initial_value = FALSE; + + LLFontGL* font = LLView::selectFont(node); + + BOOL radio_style = FALSE; + node->getAttributeBOOL("radio_style", radio_style); + + LLUICtrlCallback callback = NULL; + + if (label.empty()) + { + label.assign(node->getTextContents()); + } + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + LLCheckBoxCtrl* checkbox = new LLCheckboxCtrl(name, + rect, + label, + font, + callback, + NULL, + initial_value, + radio_style); // if true, draw radio button style icons + + LLColor4 color; + color = LLUI::sColorsGroup->getColor( "LabelTextColor" ); + LLUICtrlFactory::getAttributeColor(node,"text_enabled_color", color); + checkbox->setEnabledColor(color); + + color = LLUI::sColorsGroup->getColor( "LabelDisabledColor" ); + LLUICtrlFactory::getAttributeColor(node,"text_disabled_color", color); + checkbox->setDisabledColor(color); + + checkbox->initFromXML(node, parent); + + return checkbox; +} diff --git a/indra/llui/llcheckboxctrl.h b/indra/llui/llcheckboxctrl.h new file mode 100644 index 0000000000..b2f9c95974 --- /dev/null +++ b/indra/llui/llcheckboxctrl.h @@ -0,0 +1,112 @@ +/** + * @file llcheckboxctrl.h + * @brief LLCheckBoxCtrl base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLCHECKBOXCTRL_H +#define LL_LLCHECKBOXCTRL_H + + +#include "stdtypes.h" +#include "lluictrl.h" +#include "llbutton.h" +#include "v4color.h" +#include "llrect.h" + +// +// Constants +// +const S32 LLCHECKBOXCTRL_BTN_SIZE = 13; +const S32 LLCHECKBOXCTRL_VPAD = 2; +const S32 LLCHECKBOXCTRL_HPAD = 2; +const S32 LLCHECKBOXCTRL_SPACING = 5; +const S32 LLCHECKBOXCTRL_HEIGHT = 16; + +// Deprecated, don't use. +#define CHECKBOXCTRL_HEIGHT LLCHECKBOXCTRL_HEIGHT + +const BOOL RADIO_STYLE = TRUE; +const BOOL CHECK_STYLE = FALSE; + +// +// Classes +// +class LLFontGL; +class LLTextBox; +class LLViewBorder; + +class LLCheckBoxCtrl +: public LLUICtrl +{ +public: + LLCheckBoxCtrl(const LLString& name, const LLRect& rect, const LLString& label, + const LLFontGL* font = NULL, + void (*commit_callback)(LLUICtrl*, void*) = NULL, + void* callback_userdata = NULL, + BOOL initial_value = FALSE, + BOOL use_radio_style = FALSE, // if true, draw radio button style icons + const LLString& control_which = LLString::null); + virtual ~LLCheckBoxCtrl(); + + // LLView interface + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_CHECKBOX; } + virtual LLString getWidgetTag() const { return LL_CHECK_BOX_CTRL_TAG; } + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + virtual void setEnabled( BOOL b ); + + virtual void draw(); + virtual void reshape(S32 width, S32 height, BOOL called_from_parent); + + // LLUICtrl interface + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + BOOL get() { return (BOOL)getValue().asBoolean(); } + void set(BOOL value) { setValue(value); } + + virtual void setTentative(BOOL b) { mButton->setTentative(b); } + virtual BOOL getTentative() const { return mButton->getTentative(); } + + virtual BOOL setLabelArg( const LLString& key, const LLString& text ); + + virtual void clear(); + virtual void onCommit(); + + // LLCheckBoxCtrl interface + virtual BOOL toggle() { return mButton->toggleState(); } // returns new state + + void setEnabledColor( const LLColor4 &color ) { mTextEnabledColor = color; } + void setDisabledColor( const LLColor4 &color ) { mTextDisabledColor = color; } + + void setLabel( const LLString& label ); + LLString getLabel() const; + + virtual void setControlName(const LLString& control_name, LLView* context); + virtual LLString getControlName() const; + + static void onButtonPress(void *userdata); + +protected: + // note: value is stored in toggle state of button + LLButton* mButton; + LLTextBox* mLabel; + const LLFontGL* mFont; + LLColor4 mTextEnabledColor; + LLColor4 mTextDisabledColor; + BOOL mRadioStyle; + BOOL mInitialValue; + BOOL mKeyboardFocusOnClick; + LLViewBorder* mBorder; +}; + + +// HACK: fix old capitalization problem +//typedef LLCheckBoxCtrl LLCheckboxCtrl; +#define LLCheckboxCtrl LLCheckBoxCtrl + + +#endif // LL_LLCHECKBOXCTRL_H diff --git a/indra/llui/llclipboard.cpp b/indra/llui/llclipboard.cpp new file mode 100644 index 0000000000..f2b546ec28 --- /dev/null +++ b/indra/llui/llclipboard.cpp @@ -0,0 +1,71 @@ +/** + * @file llclipboard.cpp + * @brief LLClipboard base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llclipboard.h" + +#include "llerror.h" +#include "llmath.h" +#include "llstring.h" +#include "llview.h" +#include "llwindow.h" + +// Global singleton +LLClipboard gClipboard; + + +LLClipboard::LLClipboard() +{ +} + + +LLClipboard::~LLClipboard() +{ +} + + +void LLClipboard::copyFromSubstring(const LLWString &src, S32 pos, S32 len, const LLUUID& source_id ) +{ + mSourceID = source_id; + mString = src.substr(pos, len); + LLView::getWindow()->copyTextToClipboard( mString ); +} + + +LLWString LLClipboard::getPasteWString( LLUUID* source_id ) +{ + if( mSourceID.notNull() ) + { + LLWString temp_string; + LLView::getWindow()->pasteTextFromClipboard(temp_string); + + if( temp_string != mString ) + { + mSourceID.setNull(); + mString = temp_string; + } + } + else + { + LLView::getWindow()->pasteTextFromClipboard(mString); + } + + if( source_id ) + { + *source_id = mSourceID; + } + + return mString; +} + + +BOOL LLClipboard::canPasteString() +{ + return LLView::getWindow()->isClipboardTextAvailable(); +} diff --git a/indra/llui/llclipboard.h b/indra/llui/llclipboard.h new file mode 100644 index 0000000000..a5bb4fc790 --- /dev/null +++ b/indra/llui/llclipboard.h @@ -0,0 +1,41 @@ +/** + * @file llclipboard.h + * @brief LLClipboard base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLCLIPBOARD_H +#define LL_LLCLIPBOARD_H + + +#include "llstring.h" +#include "lluuid.h" + +// +// Classes +// +class LLClipboard +{ +protected: + LLUUID mSourceID; + LLWString mString; + +public: + LLClipboard(); + ~LLClipboard(); + + void copyFromSubstring(const LLWString ©_from, S32 pos, S32 len, const LLUUID& source_id = LLUUID::null ); + + + BOOL canPasteString(); + LLWString getPasteWString(LLUUID* source_id = NULL); +}; + + +// Global singleton +extern LLClipboard gClipboard; + + +#endif // LL_LLCLIPBOARD_H diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp new file mode 100644 index 0000000000..84c5d354be --- /dev/null +++ b/indra/llui/llcombobox.cpp @@ -0,0 +1,1133 @@ +/** + * @file llcombobox.cpp + * @brief LLComboBox base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// A control that displays the name of the chosen item, which when +// clicked shows a scrolling box of options. + +#include "linden_common.h" + +// file includes +#include "llcombobox.h" + +// common includes +#include "llstring.h" + +// newview includes +#include "llbutton.h" +#include "llkeyboard.h" +#include "llscrolllistctrl.h" +#include "llwindow.h" +#include "llfloater.h" +#include "llscrollbar.h" +#include "llcontrol.h" +#include "llfocusmgr.h" +#include "lllineeditor.h" +#include "v2math.h" + +// Globals +S32 LLCOMBOBOX_HEIGHT = 0; +S32 LLCOMBOBOX_WIDTH = 0; + +LLComboBox::LLComboBox( const LLString& name, const LLRect &rect, const LLString& label, + void (*commit_callback)(LLUICtrl*,void*), + void *callback_userdata, + S32 list_width + ) +: LLUICtrl(name, rect, TRUE, commit_callback, callback_userdata, + FOLLOWS_LEFT | FOLLOWS_TOP), + mDrawButton(TRUE), + mTextEntry(NULL), + mArrowImage(NULL), + mAllowTextEntry(FALSE), + mMaxChars(20), + mTextEntryTentative(TRUE), + mPrearrangeCallback( NULL ), + mTextEntryCallback( NULL ), + mListWidth(list_width) +{ + // For now, all comboboxes don't take keyboard focus when clicked. + // This might change if it is part of a modal dialog. + // mKeyboardFocusOnClick = FALSE; + + // Revert to standard behavior. When this control's parent is hidden, it needs to + // hide this ctrl--which won't just happen automatically since when LLComboBox is + // showing its list, it's also set to TopView. When keyboard focus is cleared all + // controls (including this one) know that they are no longer editing. + mKeyboardFocusOnClick = TRUE; + + LLRect r; + r.setOriginAndSize(0, 0, rect.getWidth(), rect.getHeight()); + + // Always use text box + // Text label button + mButton = new LLSquareButton("comboxbox button", + r, label, NULL, LLString::null, + &LLComboBox::onButtonClick, this); + mButton->setFont(LLFontGL::sSansSerifSmall); + mButton->setFollows(FOLLOWS_LEFT | FOLLOWS_BOTTOM | FOLLOWS_RIGHT); + mButton->setHAlign( LLFontGL::LEFT ); + + const S32 ARROW_WIDTH = 16; + mButton->setRightHPad( ARROW_WIDTH ); + addChild(mButton); + + // Default size, will be set by arrange() call in button callback. + if (list_width == 0) + { + list_width = mRect.getWidth() + SCROLLBAR_SIZE; + } + r.setOriginAndSize(0, 16, list_width, 220); + + // disallow multiple selection + mList = new LLScrollListCtrl( + "ComboBox", r, + &LLComboBox::onItemSelected, this, FALSE); + mList->setVisible(FALSE); + mList->setBgWriteableColor( LLColor4(1,1,1,1) ); + mList->setCommitOnKeyboardMovement(FALSE); + addChild(mList); + + LLRect border_rect(0, mRect.getHeight(), mRect.getWidth(), 0); + mBorder = new LLViewBorder( "combo border", border_rect ); + addChild( mBorder ); + mBorder->setFollows(FOLLOWS_LEFT|FOLLOWS_RIGHT|FOLLOWS_TOP|FOLLOWS_BOTTOM); + + LLUUID arrow_image_id( LLUI::sAssetsGroup->getString("combobox_arrow.tga") ); + mArrowImage = LLUI::sImageProvider->getUIImageByID(arrow_image_id); +} + + +LLComboBox::~LLComboBox() +{ + // children automatically deleted, including mMenu, mButton +} + +// virtual +LLXMLNodePtr LLComboBox::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + // Attributes + + node->createChild("allow_text_entry", TRUE)->setBoolValue(mAllowTextEntry); + + node->createChild("max_chars", TRUE)->setIntValue(mMaxChars); + + // Contents + + std::vector data_list = mList->getAllData(); + std::vector::iterator data_itor; + for (data_itor = data_list.begin(); data_itor != data_list.end(); ++data_itor) + { + LLScrollListItem* item = *data_itor; + LLScrollListCell* cell = item->getColumn(0); + if (cell) + { + LLXMLNodePtr item_node = node->createChild("combo_item", FALSE); + LLSD value = item->getValue(); + item_node->createChild("value", TRUE)->setStringValue(value.asString()); + item_node->createChild("enabled", TRUE)->setBoolValue(item->getEnabled()); + item_node->setStringValue(cell->getText()); + } + } + + return node; +} + +// static +LLView* LLComboBox::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("combo_box"); + node->getAttributeString("name", name); + + LLString label(""); + node->getAttributeString("label", label); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + BOOL allow_text_entry = FALSE; + node->getAttributeBOOL("allow_text_entry", allow_text_entry); + + S32 max_chars = 20; + node->getAttributeS32("max_chars", max_chars); + + LLUICtrlCallback callback = NULL; + + LLComboBox* combo_box = new LLComboBox(name, + rect, + label, + callback, + NULL); + combo_box->setAllowTextEntry(allow_text_entry, max_chars); + + combo_box->initFromXML(node, parent); + + const LLString& contents = node->getValue(); + + if (contents.find_first_not_of(" \n\t") != contents.npos) + { + llerrs << "Legacy combo box item format used! Please convert to tags!" << llendl; + } + else + { + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + if (child->hasName("combo_item")) + { + LLString label = child->getTextContents(); + + LLString value = label; + child->getAttributeString("value", value); + + combo_box->add(label, LLSD(value) ); + } + } + } + + combo_box->selectFirstItem(); + + return combo_box; +} + +void LLComboBox::setEnabled(BOOL enabled) +{ + LLUICtrl::setEnabled(enabled); + mButton->setEnabled(enabled); +} + +//FIXME: these are all hacks to support the fact that the combobox has mouse capture +// so we can hide the list when we don't handle the mouse up event +BOOL LLComboBox::handleHover(S32 x, S32 y, MASK mask) +{ + if (mList->getVisible()) + { + S32 local_x, local_y; + LLView::localPointToOtherView(x, y, &local_x, &local_y, mList); + if (mList->pointInView(local_x, local_y)) + { + return mList->handleHover(local_x, local_y, mask); + } + } + return LLUICtrl::handleHover(x, y, mask); +} + +BOOL LLComboBox::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (mList->getVisible()) + { + S32 local_x, local_y; + LLView::localPointToOtherView(x, y, &local_x, &local_y, mList); + if (mList->pointInView(local_x, local_y)) + { + return mList->handleMouseDown(local_x, local_y, mask); + } + } + BOOL has_focus_now = hasFocus(); + BOOL handled = LLUICtrl::handleMouseDown(x, y, mask); + if (handled && !has_focus_now) + { + onFocusReceived(); + } + + return handled; +} + +BOOL LLComboBox::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if (mList->getVisible()) + { + S32 local_x, local_y; + LLView::localPointToOtherView(x, y, &local_x, &local_y, mList); + if (mList->pointInView(local_x, local_y)) + { + return mList->handleRightMouseDown(local_x, local_y, mask); + } + } + return LLUICtrl::handleRightMouseDown(x, y, mask); +} + +BOOL LLComboBox::handleRightMouseUp(S32 x, S32 y, MASK mask) +{ + if (mList->getVisible()) + { + S32 local_x, local_y; + LLView::localPointToOtherView(x, y, &local_x, &local_y, mList); + if (mList->pointInView(local_x, local_y)) + { + return mList->handleRightMouseUp(local_x, local_y, mask); + } + } + return LLUICtrl::handleRightMouseUp(x, y, mask); +} + +BOOL LLComboBox::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if (mList->getVisible()) + { + S32 local_x, local_y; + LLView::localPointToOtherView(x, y, &local_x, &local_y, mList); + if (mList->pointInView(local_x, local_y)) + { + return mList->handleDoubleClick(local_x, local_y, mask); + } + } + return LLUICtrl::handleDoubleClick(x, y, mask); +} + +BOOL LLComboBox::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = childrenHandleMouseUp(x, y, mask) != NULL; + + if (!handled && mList->getVisible()) + { + S32 local_x, local_y; + LLView::localPointToOtherView(x, y, &local_x, &local_y, mList); + if (mList->pointInView(local_x, local_y)) + { + handled = mList->handleMouseUp(local_x, local_y, mask); + } + } + + if( !handled && gFocusMgr.getMouseCapture() == this ) + { + // Mouse events that we didn't handle cause the list to be hidden. + // Eat mouse event, regardless of where on the screen it happens. + hideList(); + handled = TRUE; + } + + return handled; +} + +void LLComboBox::clear() +{ + if (mTextEntry) + { + mTextEntry->setText(""); + } + mButton->setLabelSelected(""); + mButton->setLabelUnselected(""); + mButton->setDisabledLabel(""); + mButton->setDisabledSelectedLabel(""); + mList->deselectAllItems(); +} + +void LLComboBox::onCommit() +{ + if (mAllowTextEntry && getCurrentIndex() != -1) + { + // we have selected an existing item, blitz the manual text entry with + // the properly capitalized item + mTextEntry->setValue(getSimple()); + mTextEntry->setTentative(FALSE); + } + LLUICtrl::onCommit(); +} + +// add item "name" to menu +void LLComboBox::add(const LLString& name, EAddPosition pos, BOOL enabled) +{ + mList->addSimpleItem(name, pos, enabled); + mList->selectFirstItem(); +} + +// add item "name" with a unique id to menu +void LLComboBox::add(const LLString& name, const LLUUID& id, EAddPosition pos, BOOL enabled ) +{ + mList->addSimpleItem(name, LLSD(id), pos, enabled); + mList->selectFirstItem(); +} + +// add item "name" with attached userdata +void LLComboBox::add(const LLString& name, void* userdata, EAddPosition pos, BOOL enabled ) +{ + LLScrollListItem* item = mList->addSimpleItem(name, pos, enabled); + item->setUserdata( userdata ); + mList->selectFirstItem(); +} + +// add item "name" with attached generic data +void LLComboBox::add(const LLString& name, LLSD value, EAddPosition pos, BOOL enabled ) +{ + mList->addSimpleItem(name, value, pos, enabled); + mList->selectFirstItem(); +} + + +void LLComboBox::sortByName() +{ + mList->sortByColumn(0, TRUE); +} + + +// Choose an item with a given name in the menu. +// Returns TRUE if the item was found. +BOOL LLComboBox::setSimple(const LLString& name) +{ + BOOL found = mList->selectSimpleItem(name, FALSE); + + if (found) + { + setLabel(name); + } + + return found; +} + +// virtual +void LLComboBox::setValue(const LLSD& value) +{ + BOOL found = mList->selectByValue(value); + if (found) + { + LLScrollListItem* item = mList->getFirstSelected(); + if (item) + { + setLabel( mList->getSimpleSelectedItem() ); + } + } +} + +const LLString& LLComboBox::getSimple() const +{ + const LLString& res = mList->getSimpleSelectedItem(); + if (res.empty() && mAllowTextEntry) + { + return mTextEntry->getText(); + } + else + { + return res; + } +} + +const LLString& LLComboBox::getSimpleSelectedItem(S32 column) const +{ + return mList->getSimpleSelectedItem(column); +} + +// virtual +LLSD LLComboBox::getValue() const +{ + LLScrollListItem* item = mList->getFirstSelected(); + if( item ) + { + return item->getValue(); + } + else if (mAllowTextEntry) + { + return mTextEntry->getValue(); + } + else + { + return LLSD(); + } +} + +void LLComboBox::setLabel(const LLString& name) +{ + if ( mAllowTextEntry ) + { + mTextEntry->setText(name); + if (mList->selectSimpleItem(name, FALSE)) + { + mTextEntry->setTentative(FALSE); + } + else + { + mTextEntry->setTentative(mTextEntryTentative); + } + } + else + { + mButton->setLabelUnselected(name); + mButton->setLabelSelected(name); + mButton->setDisabledLabel(name); + mButton->setDisabledSelectedLabel(name); + } +} + + +BOOL LLComboBox::remove(const LLString& name) +{ + BOOL found = mList->selectSimpleItem(name); + + if (found) + { + LLScrollListItem* item = mList->getFirstSelected(); + if (item) + { + mList->deleteSingleItem(mList->getItemIndex(item)); + } + } + + return found; +} + +BOOL LLComboBox::remove(S32 index) +{ + if (index < mList->getItemCount()) + { + mList->deleteSingleItem(index); + return TRUE; + } + return FALSE; +} + +// Keyboard focus lost. +void LLComboBox::onFocusLost() +{ + hideList(); + // if valid selection + if (mAllowTextEntry && getCurrentIndex() != -1) + { + mTextEntry->selectAll(); + } +} + +void LLComboBox::setButtonVisible(BOOL visible) +{ + mButton->setVisible(visible); + mDrawButton = visible; + if (mTextEntry) + { + LLRect text_entry_rect(0, mRect.getHeight(), mRect.getWidth(), 0); + if (visible) + { + text_entry_rect.mRight -= mArrowImage->getWidth() + 2 * LLUI::sConfigGroup->getS32("DropShadowButton"); + } + //mTextEntry->setRect(text_entry_rect); + mTextEntry->reshape(text_entry_rect.getWidth(), text_entry_rect.getHeight(), TRUE); + } +} + +void LLComboBox::draw() +{ + if( getVisible() ) + { + mBorder->setKeyboardFocusHighlight(hasFocus()); + + mButton->setEnabled(mEnabled /*&& !mList->isEmpty()*/); + + // Draw children + LLUICtrl::draw(); + + if (mDrawButton) + { + // Paste the graphic on the right edge + if (!mArrowImage.isNull()) + { + S32 left = mRect.getWidth() - mArrowImage->getWidth() - LLUI::sConfigGroup->getS32("DropShadowButton"); + + gl_draw_image( left, 0, mArrowImage, + LLColor4::white); + } + } + } +} + +BOOL LLComboBox::setCurrentByIndex( S32 index ) +{ + BOOL found = mList->selectNthItem( index ); + if (found) + { + setLabel(mList->getSimpleSelectedItem()); + } + return found; +} + +S32 LLComboBox::getCurrentIndex() const +{ + LLScrollListItem* item = mList->getFirstSelected(); + if( item ) + { + return mList->getItemIndex( item ); + } + return -1; +} + + +void* LLComboBox::getCurrentUserdata() +{ + LLScrollListItem* item = mList->getFirstSelected(); + if( item ) + { + return item->getUserdata(); + } + return NULL; +} + + +void LLComboBox::showList() +{ + // Make sure we don't go off top of screen. + LLCoordWindow window_size; + getWindow()->getSize(&window_size); + //HACK: shouldn't have to know about scale here + mList->arrange( 192, llfloor((F32)window_size.mY / LLUI::sGLScaleFactor.mV[VY]) - 50 ); + + // Move rect so it hangs off the bottom of this view + LLRect rect = mList->getRect(); + + rect.setLeftTopAndSize(0, 0, rect.getWidth(), rect.getHeight() ); + mList->setRect(rect); + + // Make sure that we can see the whole list + LLRect floater_area_screen; + LLRect floater_area_local; + gFloaterView->getParent()->localRectToScreen( gFloaterView->getRect(), &floater_area_screen ); + screenRectToLocal( floater_area_screen, &floater_area_local ); + mList->translateIntoRect( floater_area_local, FALSE ); + + // Make sure we didn't go off bottom of screen + S32 x, y; + mList->localPointToScreen(0, 0, &x, &y); + + if (y < 0) + { + mList->translate(0, -y); + } + + gFocusMgr.setMouseCapture( this, LLComboBox::onMouseCaptureLost ); + // NB: this call will trigger the focuslost callback which will hide the list, so do it first + // before finally showing the list + + if (!mList->getFirstSelected()) + { + // if nothing is selected, select the first item + // so that the callback is not immediately triggered on setFocus() + mList->selectFirstItem(); + } + gFocusMgr.setKeyboardFocus(mList, onListFocusLost); + + // Show the list and push the button down + mButton->setToggleState(TRUE); + mList->setVisible(TRUE); + + gFocusMgr.setTopView(mList, LLComboBox::onTopViewLost ); + +} + +void LLComboBox::hideList() +{ + mButton->setToggleState(FALSE); + mList->setVisible(FALSE); + mList->highlightNthItem(-1); + + if( gFocusMgr.getTopView() == mList ) + { + gFocusMgr.setTopView(NULL, NULL); + } + + if( gFocusMgr.getMouseCapture() == this ) + { + gFocusMgr.setMouseCapture( NULL, NULL ); + } + + if( gFocusMgr.getKeyboardFocus() == mList ) + { + if (mAllowTextEntry) + { + mTextEntry->setFocus(TRUE); + } + else + { + setFocus(TRUE); + } + } +} + + + +//------------------------------------------------------------------ +// static functions +//------------------------------------------------------------------ + +// static +void LLComboBox::onButtonClick(void *userdata) +{ + LLComboBox *self = (LLComboBox *)userdata; + + if (!self->mList->getVisible()) + { + LLScrollListItem* last_selected_item = self->mList->getLastSelectedItem(); + if (last_selected_item) + { + // highlight the original selection before potentially selecting a new item + self->mList->highlightNthItem(self->mList->getItemIndex(last_selected_item)); + } + + if( self->mPrearrangeCallback ) + { + self->mPrearrangeCallback( self, self->mCallbackUserData ); + } + + if (self->mList->getItemCount() != 0) + { + self->showList(); + } + + if (self->mKeyboardFocusOnClick && !self->hasFocus()) + { + self->setFocus( TRUE ); + } + } + else + { + // hide and release keyboard focus + self->hideList(); + + self->onCommit(); + } +} + + + +// static +void LLComboBox::onItemSelected(LLUICtrl* item, void *userdata) +{ + // Note: item is the LLScrollListCtrl + LLComboBox *self = (LLComboBox *) userdata; + + const LLString& name = self->mList->getSimpleSelectedItem(); + + self->hideList(); + + S32 cur_id = self->getCurrentIndex(); + if (cur_id != -1) + { + self->setLabel(self->mList->getSimpleSelectedItem()); + + if (self->mAllowTextEntry) + { + self->mTextEntry->setText(name); + self->mTextEntry->setTentative(FALSE); + gFocusMgr.setKeyboardFocus(self->mTextEntry, NULL); + self->mTextEntry->selectAll(); + } + else + { + self->mButton->setLabelUnselected( name ); + self->mButton->setLabelSelected( name ); + self->mButton->setDisabledLabel( name ); + self->mButton->setDisabledSelectedLabel( name ); + } + } + self->onCommit(); +} + +// static +void LLComboBox::onTopViewLost(LLView* old_focus) +{ + LLComboBox *self = (LLComboBox *) old_focus->getParent(); + self->hideList(); +} + + +// static +void LLComboBox::onMouseCaptureLost(LLMouseHandler*) +{ + // Can't hide the list here. If the list scrolls off the screen, + // and you click in the arrow buttons of the scroll bar, they must capture + // the mouse to handle scrolling-while-mouse-down. +} + +BOOL LLComboBox::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen) +{ + + LLString tool_tip; + + if (LLUI::sShowXUINames) + { + tool_tip = mName; + } + else + { + tool_tip = mToolTipMsg; + } + + if( getVisible() && pointInView( x, y ) ) + { + if( !tool_tip.empty() ) + { + msg = tool_tip; + + // Convert rect local to screen coordinates + localPointToScreen( + 0, 0, + &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); + localPointToScreen( + mRect.getWidth(), mRect.getHeight(), + &(sticky_rect_screen->mRight), &(sticky_rect_screen->mTop) ); + } + return TRUE; + } + return FALSE; +} + +BOOL LLComboBox::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + BOOL result = FALSE; + if (gFocusMgr.childHasKeyboardFocus(this)) + { + //give list a chance to pop up and handle key + LLScrollListItem* last_selected_item = mList->getLastSelectedItem(); + if (last_selected_item) + { + // highlight the original selection before potentially selecting a new item + mList->highlightNthItem(mList->getItemIndex(last_selected_item)); + } + result = mList->handleKeyHere(key, mask, FALSE); + // if selection has changed, pop open list + if (mList->getLastSelectedItem() != last_selected_item) + { + showList(); + } + } + return result; +} + +BOOL LLComboBox::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent) +{ + BOOL result = FALSE; + if (gFocusMgr.childHasKeyboardFocus(this)) + { + // space bar just shows the list + if (' ' != uni_char ) + { + LLScrollListItem* last_selected_item = mList->getLastSelectedItem(); + if (last_selected_item) + { + // highlight the original selection before potentially selecting a new item + mList->highlightNthItem(mList->getItemIndex(last_selected_item)); + } + result = mList->handleUnicodeCharHere(uni_char, called_from_parent); + if (mList->getLastSelectedItem() != last_selected_item) + { + showList(); + } + } + } + return result; +} + +void LLComboBox::setAllowTextEntry(BOOL allow, S32 max_chars, BOOL set_tentative) +{ + LLRect rect( 0, mRect.getHeight(), mRect.getWidth(), 0); + if (allow && !mAllowTextEntry) + { + S32 shadow_size = LLUI::sConfigGroup->getS32("DropShadowButton"); + mButton->setRect(LLRect( mRect.getWidth() - mArrowImage->getWidth() - 2 * shadow_size, + rect.mTop, rect.mRight, rect.mBottom)); + mButton->setTabStop(FALSE); + + // clear label on button + LLString cur_label = mButton->getLabelSelected(); + setLabel(""); + if (!mTextEntry) + { + LLRect text_entry_rect(0, mRect.getHeight(), mRect.getWidth(), 0); + text_entry_rect.mRight -= mArrowImage->getWidth() + 2 * LLUI::sConfigGroup->getS32("DropShadowButton"); + mTextEntry = new LLLineEditor("combo_text_entry", + text_entry_rect, + "", + LLFontGL::sSansSerifSmall, + max_chars, + onTextCommit, + onTextEntry, + NULL, + this, + NULL, // prevalidate func + LLViewBorder::BEVEL_NONE, + LLViewBorder::STYLE_LINE, + 0); // no border + mTextEntry->setSelectAllonFocusReceived(TRUE); + mTextEntry->setHandleEditKeysDirectly(TRUE); + mTextEntry->setCommitOnFocusLost(FALSE); + mTextEntry->setText(cur_label); + mTextEntry->setIgnoreTab(TRUE); + addChild(mTextEntry); + mMaxChars = max_chars; + } + else + { + mTextEntry->setVisible(TRUE); + } + } + else if (!allow && mAllowTextEntry) + { + mButton->setRect(rect); + mButton->setTabStop(TRUE); + + if (mTextEntry) + { + mTextEntry->setVisible(FALSE); + } + } + mAllowTextEntry = allow; + mTextEntryTentative = set_tentative; +} + +void LLComboBox::setTextEntry(const LLString& text) +{ + if (mTextEntry) + { + mTextEntry->setText(text); + updateSelection(); + } +} + +//static +void LLComboBox::onTextEntry(LLLineEditor* line_editor, void* user_data) +{ + LLComboBox* self = (LLComboBox*)user_data; + + if (self->mTextEntryCallback) + { + (*self->mTextEntryCallback)(line_editor, self->mCallbackUserData); + } + + KEY key = gKeyboard->currentKey(); + if (key == KEY_BACKSPACE || + key == KEY_DELETE) + { + if (self->mList->selectSimpleItem(line_editor->getText(), FALSE)) + { + line_editor->setTentative(FALSE); + } + else + { + line_editor->setTentative(self->mTextEntryTentative); + } + return; + } + + if (key == KEY_LEFT || + key == KEY_RIGHT) + { + return; + } + + if (key == KEY_DOWN) + { + self->setCurrentByIndex(llmin(self->getItemCount() - 1, self->getCurrentIndex() + 1)); + if (!self->mList->getVisible()) + { + if( self->mPrearrangeCallback ) + { + self->mPrearrangeCallback( self, self->mCallbackUserData ); + } + + if (self->mList->getItemCount() != 0) + { + self->showList(); + } + } + line_editor->selectAll(); + line_editor->setTentative(FALSE); + } + else if (key == KEY_UP) + { + self->setCurrentByIndex(llmax(0, self->getCurrentIndex() - 1)); + if (!self->mList->getVisible()) + { + if( self->mPrearrangeCallback ) + { + self->mPrearrangeCallback( self, self->mCallbackUserData ); + } + + if (self->mList->getItemCount() != 0) + { + self->showList(); + } + } + line_editor->selectAll(); + line_editor->setTentative(FALSE); + } + else + { + // RN: presumably text entry + self->updateSelection(); + } +} + +void LLComboBox::updateSelection() +{ + LLWString left_wstring = mTextEntry->getWText().substr(0, mTextEntry->getCursor()); + // user-entered portion of string, based on assumption that any selected + // text was a result of auto-completion + LLWString user_wstring = mTextEntry->hasSelection() ? left_wstring : mTextEntry->getWText(); + LLString full_string = mTextEntry->getText(); + + // go ahead and arrange drop down list on first typed character, even + // though we aren't showing it... some code relies on prearrange + // callback to populate content + if( mTextEntry->getWText().size() == 1 ) + { + if (mPrearrangeCallback) + { + mPrearrangeCallback( this, mCallbackUserData ); + } + } + + if (mList->selectSimpleItem(full_string, FALSE)) + { + mTextEntry->setTentative(FALSE); + } + else if (!mList->selectSimpleItemByPrefix(left_wstring, FALSE)) + { + mList->deselectAllItems(); + mTextEntry->setText(wstring_to_utf8str(user_wstring)); + mTextEntry->setTentative(mTextEntryTentative); + } + else + { + LLWString selected_item = utf8str_to_wstring(mList->getSimpleSelectedItem()); + LLWString wtext = left_wstring + selected_item.substr(left_wstring.size(), selected_item.size()); + mTextEntry->setText(wstring_to_utf8str(wtext)); + mTextEntry->setSelection(left_wstring.size(), mTextEntry->getWText().size()); + mTextEntry->endSelection(); + mTextEntry->setTentative(FALSE); + } +} + +//static +void LLComboBox::onTextCommit(LLUICtrl* caller, void* user_data) +{ + LLComboBox* self = (LLComboBox*)user_data; + LLString text = self->mTextEntry->getText(); + self->setSimple(text); + self->onCommit(); + self->mTextEntry->selectAll(); +} + +void LLComboBox::setFocus(BOOL b) +{ + LLUICtrl::setFocus(b); + + if (b) + { + mList->clearSearchString(); + } +} + +//============================================================================ +// LLCtrlListInterface functions + +S32 LLComboBox::getItemCount() const +{ + return mList->getItemCount(); +} + +void LLComboBox::addColumn(const LLSD& column, EAddPosition pos) +{ + mList->clearColumns(); + mList->addColumn(column, pos); +} + +void LLComboBox::clearColumns() +{ + mList->clearColumns(); +} + +void LLComboBox::setColumnLabel(const LLString& column, const LLString& label) +{ + mList->setColumnLabel(column, label); +} + +LLScrollListItem* LLComboBox::addElement(const LLSD& value, EAddPosition pos, void* userdata) +{ + return mList->addElement(value, pos, userdata); +} + +LLScrollListItem* LLComboBox::addSimpleElement(const LLString& value, EAddPosition pos, const LLSD& id) +{ + return mList->addSimpleElement(value, pos, id); +} + +void LLComboBox::clearRows() +{ + mList->clearRows(); +} + +void LLComboBox::sortByColumn(LLString name, BOOL ascending) +{ +} + +//============================================================================ +//LLCtrlSelectionInterface functions + +BOOL LLComboBox::setCurrentByID(const LLUUID& id) +{ + BOOL found = mList->selectByID( id ); + + if (found) + { + setLabel(mList->getSimpleSelectedItem()); + } + + return found; +} + +LLUUID LLComboBox::getCurrentID() +{ + return mList->getStringUUIDSelectedItem(); +} +BOOL LLComboBox::setSelectedByValue(LLSD value, BOOL selected) +{ + BOOL found = mList->setSelectedByValue(value, selected); + if (found) + { + setLabel(mList->getSimpleSelectedItem()); + } + return found; +} + +LLSD LLComboBox::getSimpleSelectedValue() +{ + return mList->getSimpleSelectedValue(); +} + +BOOL LLComboBox::isSelected(LLSD value) +{ + return mList->isSelected(value); +} + +BOOL LLComboBox::operateOnSelection(EOperation op) +{ + if (op == OP_DELETE) + { + mList->deleteSelectedItems(); + return TRUE; + } + return FALSE; +} + +BOOL LLComboBox::operateOnAll(EOperation op) +{ + if (op == OP_DELETE) + { + clearRows(); + return TRUE; + } + return FALSE; +} + +//static +void LLComboBox::onListFocusLost(LLUICtrl* old_focus) +{ + // if focus is going to nothing (user hit ESC), take it back + LLComboBox* combo = (LLComboBox*)old_focus->getParent(); + combo->hideList(); + if (gFocusMgr.getKeyboardFocus() == NULL) + { + combo->focusFirstItem(); + } +} diff --git a/indra/llui/llcombobox.h b/indra/llui/llcombobox.h new file mode 100644 index 0000000000..1ec31ec1c0 --- /dev/null +++ b/indra/llui/llcombobox.h @@ -0,0 +1,178 @@ +/** + * @file llcombobox.h + * @brief LLComboBox base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// A control that displays the name of the chosen item, which when clicked +// shows a scrolling box of choices. + +#ifndef LL_LLCOMBOBOX_H +#define LL_LLCOMBOBOX_H + +#include "llbutton.h" +#include "lluictrl.h" +#include "llctrlselectioninterface.h" +#include "llimagegl.h" +#include "llrect.h" + +// Classes + +class LLFontGL; +class LLButton; +class LLSquareButton; +class LLScrollListCtrl; +class LLLineEditor; +class LLViewBorder; + +extern S32 LLCOMBOBOX_HEIGHT; +extern S32 LLCOMBOBOX_WIDTH; + +class LLComboBox +: public LLUICtrl, public LLCtrlListInterface +{ +public: + LLComboBox( + const LLString& name, + const LLRect &rect, + const LLString& label, + void (*commit_callback)(LLUICtrl*, void*) = NULL, + void *callback_userdata = NULL, + S32 list_width = 0 + ); + virtual ~LLComboBox(); + + // LLView interface + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_COMBO_BOX; } + virtual LLString getWidgetTag() const { return LL_COMBO_BOX_TAG; } + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + virtual void draw(); + virtual void onFocusLost(); + + virtual void setEnabled(BOOL enabled); + + virtual BOOL handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect); + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent); + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleRightMouseUp(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + + // LLUICtrl interface + virtual void clear(); // select nothing + virtual void onCommit(); + virtual BOOL acceptsTextInput() const { return mAllowTextEntry; } + + virtual void setFocus(BOOL b); + + // Selects item by underlying LLSD value, using LLSD::asString() matching. + // For simple items, this is just the name of the label. + virtual void setValue(const LLSD& value ); + + // Gets underlying LLSD value for currently selected items. For simple + // items, this is just the label. + virtual LLSD getValue() const; + + void setAllowTextEntry(BOOL allow, S32 max_chars = 50, BOOL make_tentative = TRUE); + void setTextEntry(const LLString& text); + + void add(const LLString& name, EAddPosition pos = ADD_BOTTOM, BOOL enabled = TRUE); // add item "name" to menu + void add(const LLString& name, const LLUUID& id, EAddPosition pos = ADD_BOTTOM, BOOL enabled = TRUE); + void add(const LLString& name, void* userdata, EAddPosition pos = ADD_BOTTOM, BOOL enabled = TRUE); + void add(const LLString& name, LLSD value, EAddPosition pos = ADD_BOTTOM, BOOL enabled = TRUE); + BOOL remove( S32 index ); // remove item by index, return TRUE if found and removed + void removeall() { clearRows(); } + + void sortByName(); // Sort the entries in the combobox by name + + // Select current item by name using selectSimpleItem. Returns FALSE if not found. + BOOL setSimple(const LLString& name); + // Get name of current item. Returns an empty string if not found. + const LLString& getSimple() const; + // Get contents of column x of selected row + const LLString& getSimpleSelectedItem(S32 column = 0) const; + + // Sets the label, which doesn't have to exist in the label. + // This is probably a UI abuse. + void setLabel(const LLString& name); + + BOOL remove(const LLString& name); // remove item "name", return TRUE if found and removed + + BOOL setCurrentByIndex( S32 index ); + S32 getCurrentIndex() const; + + //======================================================================== + LLCtrlSelectionInterface* getSelectionInterface() { return (LLCtrlSelectionInterface*)this; }; + LLCtrlListInterface* getListInterface() { return (LLCtrlListInterface*)this; }; + + // LLCtrlListInterface functions + // See llscrolllistctrl.h + virtual S32 getItemCount() const; + // Overwrites the default column (See LLScrollListCtrl for format) + virtual void addColumn(const LLSD& column, EAddPosition pos = ADD_BOTTOM); + virtual void clearColumns(); + virtual void setColumnLabel(const LLString& column, const LLString& label); + virtual LLScrollListItem* addElement(const LLSD& value, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL); + virtual LLScrollListItem* addSimpleElement(const LLString& value, EAddPosition pos = ADD_BOTTOM, const LLSD& id = LLSD()); + virtual void clearRows(); + virtual void sortByColumn(LLString name, BOOL ascending); + + // LLCtrlSelectionInterface functions + virtual BOOL getCanSelect() const { return TRUE; } + virtual BOOL selectFirstItem() { return setCurrentByIndex(0); } + virtual BOOL selectNthItem( S32 index ) { return setCurrentByIndex(index); } + virtual S32 getFirstSelectedIndex() { return getCurrentIndex(); } + virtual BOOL setCurrentByID( const LLUUID& id ); + virtual LLUUID getCurrentID(); // LLUUID::null if no items in menu + virtual BOOL setSelectedByValue(LLSD value, BOOL selected); + virtual LLSD getSimpleSelectedValue(); + virtual BOOL isSelected(LLSD value); + virtual BOOL operateOnSelection(EOperation op); + virtual BOOL operateOnAll(EOperation op); + + //======================================================================== + + void* getCurrentUserdata(); + + void setPrearrangeCallback( void (*cb)(LLUICtrl*,void*) ) { mPrearrangeCallback = cb; } + void setTextEntryCallback( void (*cb)(LLLineEditor*, void*) ) { mTextEntryCallback = cb; } + + void setButtonVisible(BOOL visible); + + static void onButtonClick(void *userdata); + static void onItemSelected(LLUICtrl* item, void *userdata); + static void onTopViewLost(LLView* old_focus); + static void onMouseCaptureLost(LLMouseHandler* old_captor); + static void onTextEntry(LLLineEditor* line_editor, void* user_data); + static void onTextCommit(LLUICtrl* caller, void* user_data); + + void updateSelection(); + void showList(); + void hideList(); + + static void onListFocusLost(LLUICtrl* old_focus); + +protected: + LLButton* mButton; + LLScrollListCtrl* mList; + LLViewBorder* mBorder; + BOOL mKeyboardFocusOnClick; + BOOL mDrawButton; + LLLineEditor* mTextEntry; + LLPointer mArrowImage; + BOOL mAllowTextEntry; + S32 mMaxChars; + BOOL mTextEntryTentative; + void (*mPrearrangeCallback)(LLUICtrl*,void*); + void (*mTextEntryCallback)(LLLineEditor*, void*); + S32 mListWidth; // width of pop-up list, 0 = use combobox width +}; + +#endif diff --git a/indra/llui/llctrlselectioninterface.cpp b/indra/llui/llctrlselectioninterface.cpp new file mode 100644 index 0000000000..a58fb88e75 --- /dev/null +++ b/indra/llui/llctrlselectioninterface.cpp @@ -0,0 +1,44 @@ +/** + * @file llctrlselectioninterface.cpp + * @brief Programmatic selection of items in a list. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "llctrlselectioninterface.h" + +#include "llsd.h" + +// virtual +LLCtrlSelectionInterface::~LLCtrlSelectionInterface() +{ } + +BOOL LLCtrlSelectionInterface::selectByValue(LLSD value) +{ + return setSelectedByValue(value, TRUE); +} + +BOOL LLCtrlSelectionInterface::deselectByValue(LLSD value) +{ + return setSelectedByValue(value, FALSE); +} + + +// virtual +LLCtrlListInterface::~LLCtrlListInterface() +{ } + +LLScrollListItem* LLCtrlListInterface::addSimpleElement(const LLString& value) +{ + return addSimpleElement(value, ADD_BOTTOM, LLSD()); +} + +LLScrollListItem* LLCtrlListInterface::addSimpleElement(const LLString& value, EAddPosition pos) +{ + return addSimpleElement(value, pos, LLSD()); +} + +// virtual +LLCtrlScrollInterface::~LLCtrlScrollInterface() +{ } diff --git a/indra/llui/llctrlselectioninterface.h b/indra/llui/llctrlselectioninterface.h new file mode 100644 index 0000000000..4e2807e9a1 --- /dev/null +++ b/indra/llui/llctrlselectioninterface.h @@ -0,0 +1,84 @@ +/** + * @file llctrlselectioninterface.h + * @brief Programmatic selection of items in a list. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LLCTRLSELECTIONINTERFACE_H +#define LLCTRLSELECTIONINTERFACE_H + +#include "stdtypes.h" +#include "stdenums.h" +#include "llstring.h" + +class LLSD; +class LLUUID; +class LLScrollListItem; + +class LLCtrlSelectionInterface +{ +public: + virtual ~LLCtrlSelectionInterface(); + + enum EOperation + { + OP_DELETE = 1, + OP_SELECT, + OP_DESELECT, + }; + + virtual BOOL getCanSelect() const = 0; + + virtual BOOL selectFirstItem() = 0; + virtual BOOL selectNthItem( S32 index ) = 0; + + virtual S32 getFirstSelectedIndex() = 0; + + // TomY TODO: Simply cast the UUIDs to LLSDs, using the selectByValue function + virtual BOOL setCurrentByID( const LLUUID& id ) = 0; + virtual LLUUID getCurrentID() = 0; + + BOOL selectByValue(LLSD value); + BOOL deselectByValue(LLSD value); + virtual BOOL setSelectedByValue(LLSD value, BOOL selected) = 0; + virtual LLSD getSimpleSelectedValue() = 0; + + virtual BOOL isSelected(LLSD value) = 0; + + virtual BOOL operateOnSelection(EOperation op) = 0; + virtual BOOL operateOnAll(EOperation op) = 0; +}; + +class LLCtrlListInterface : public LLCtrlSelectionInterface +{ +public: + virtual ~LLCtrlListInterface(); + + virtual S32 getItemCount() const = 0; + virtual void addColumn(const LLSD& column, EAddPosition pos = ADD_BOTTOM) = 0; + virtual void clearColumns() = 0; + virtual void setColumnLabel(const LLString& column, const LLString& label) = 0; + // TomY TODO: Document this + virtual LLScrollListItem* addElement(const LLSD& value, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL) = 0; + + LLScrollListItem* addSimpleElement(const LLString& value); // defaults to bottom + LLScrollListItem* addSimpleElement(const LLString& value, EAddPosition pos); // defaults to no LLSD() id + virtual LLScrollListItem* addSimpleElement(const LLString& value, EAddPosition pos, const LLSD& id) = 0; + + virtual void clearRows() = 0; + virtual void sortByColumn(LLString name, BOOL ascending) = 0; +}; + +class LLCtrlScrollInterface +{ +public: + virtual ~LLCtrlScrollInterface(); + + virtual S32 getScrollPos() = 0; + virtual void setScrollPos( S32 pos ) = 0; + virtual void scrollToShowSelected() = 0; +}; + +#endif diff --git a/indra/llui/lldraghandle.cpp b/indra/llui/lldraghandle.cpp new file mode 100644 index 0000000000..a88fbb7744 --- /dev/null +++ b/indra/llui/lldraghandle.cpp @@ -0,0 +1,353 @@ +/** + * @file lldraghandle.cpp + * @brief LLDragHandle base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// A widget for dragging a view around the screen using the mouse. + +#include "linden_common.h" + +#include "lldraghandle.h" + +#include "llmath.h" + +//#include "llviewerwindow.h" +#include "llui.h" +#include "llmenugl.h" +#include "lltextbox.h" +#include "llcontrol.h" +#include "llresmgr.h" +#include "llfontgl.h" +#include "llwindow.h" +#include "llfocusmgr.h" + +const S32 LEADING_PAD = 5; +const S32 TITLE_PAD = 8; +const S32 BORDER_PAD = 1; +const S32 LEFT_PAD = BORDER_PAD + TITLE_PAD + LEADING_PAD; +const S32 RIGHT_PAD = BORDER_PAD + 32; // HACK: space for close btn and minimize btn + +S32 LLDragHandle::sSnapMargin = 5; + +LLDragHandle::LLDragHandle( const LLString& name, const LLRect& rect, const LLString& title ) +: LLView( name, rect, TRUE ), + mDragLastScreenX( 0 ), + mDragLastScreenY( 0 ), + mLastMouseScreenX( 0 ), + mLastMouseScreenY( 0 ), + mDragHighlightColor( LLUI::sColorsGroup->getColor( "DefaultHighlightLight" ) ), + mDragShadowColor( LLUI::sColorsGroup->getColor( "DefaultShadowDark" ) ), + mTitleBox( NULL ), + mMaxTitleWidth( 0 ), + mForeground( TRUE ) +{ + sSnapMargin = LLUI::sConfigGroup->getS32("SnapMargin"); + + setSaveToXML(false); +} + +void LLDragHandle::setTitleVisible(BOOL visible) +{ + mTitleBox->setVisible(visible); +} + +LLDragHandleTop::LLDragHandleTop(const LLString& name, const LLRect &rect, const LLString& title) +: LLDragHandle(name, rect, title) +{ + setFollowsAll(); + setTitle( title ); +} + +EWidgetType LLDragHandleTop::getWidgetType() const +{ + return WIDGET_TYPE_DRAG_HANDLE_TOP; +} + +LLString LLDragHandleTop::getWidgetTag() const +{ + return LL_DRAG_HANDLE_TOP_TAG; +} + +LLDragHandleLeft::LLDragHandleLeft(const LLString& name, const LLRect &rect, const LLString& title) +: LLDragHandle(name, rect, title) +{ + setFollowsAll(); + setTitle( title ); +} + +EWidgetType LLDragHandleLeft::getWidgetType() const +{ + return WIDGET_TYPE_DRAG_HANDLE_LEFT; +} + +LLString LLDragHandleLeft::getWidgetTag() const +{ + return LL_DRAG_HANDLE_LEFT_TAG; +} + +void LLDragHandleTop::setTitle(const LLString& title) +{ + if( mTitleBox ) + { + removeChild(mTitleBox); + delete mTitleBox; + } + + LLString trimmed_title = title; + LLString::trim(trimmed_title); + + const LLFontGL* font = gResMgr->getRes( LLFONT_SANSSERIF ); + mTitleBox = new LLTextBox( "Drag Handle Title", mRect, trimmed_title, font ); + mTitleBox->setFollows(FOLLOWS_TOP | FOLLOWS_LEFT | FOLLOWS_RIGHT); + reshapeTitleBox(); + + // allow empty titles, as default behavior replaces them with title box name + if (trimmed_title.empty()) + { + mTitleBox->setText(LLString::null); + } + addChild( mTitleBox ); +} + + +const LLString& LLDragHandleTop::getTitle() const +{ + return mTitleBox->getText(); +} + + +void LLDragHandleLeft::setTitle(const LLString& ) +{ + if( mTitleBox ) + { + removeChild(mTitleBox); + delete mTitleBox; + } + + mTitleBox = NULL; + + /* no title on left edge */ +} + + +const LLString& LLDragHandleLeft::getTitle() const +{ + return LLString::null; +} + + +void LLDragHandleTop::draw() +{ + /* Disable lines. Can drag anywhere in most windows. JC + if( getVisible() && mEnabled && mForeground) + { + const S32 BORDER_PAD = 2; + const S32 HPAD = 2; + const S32 VPAD = 2; + S32 left = BORDER_PAD + HPAD; + S32 top = mRect.getHeight() - 2 * VPAD; + S32 right = mRect.getWidth() - HPAD; +// S32 bottom = VPAD; + + // draw lines for drag areas + + const S32 LINE_SPACING = (DRAG_HANDLE_HEIGHT - 2 * VPAD) / 4; + S32 line = top - LINE_SPACING; + + LLRect title_rect = mTitleBox->getRect(); + S32 title_right = title_rect.mLeft + mTitleWidth; + BOOL show_right_side = title_right < mRect.getWidth(); + + for( S32 i=0; i<4; i++ ) + { + gl_line_2d(left, line+1, title_rect.mLeft - LEADING_PAD, line+1, mDragHighlightColor); + if( show_right_side ) + { + gl_line_2d(title_right, line+1, right, line+1, mDragHighlightColor); + } + + gl_line_2d(left, line, title_rect.mLeft - LEADING_PAD, line, mDragShadowColor); + if( show_right_side ) + { + gl_line_2d(title_right, line, right, line, mDragShadowColor); + } + line -= LINE_SPACING; + } + } + */ + + // Colorize the text to match the frontmost state + if (mTitleBox) + { + mTitleBox->setEnabled(mForeground); + } + + LLView::draw(); +} + + +// assumes GL state is set for 2D +void LLDragHandleLeft::draw() +{ + /* Disable lines. Can drag anywhere in most windows. JC + if( getVisible() && mEnabled && mForeground ) + { + const S32 BORDER_PAD = 2; +// const S32 HPAD = 2; + const S32 VPAD = 2; + const S32 LINE_SPACING = 3; + + S32 left = BORDER_PAD + LINE_SPACING; + S32 top = mRect.getHeight() - 2 * VPAD; +// S32 right = mRect.getWidth() - HPAD; + S32 bottom = VPAD; + + // draw lines for drag areas + + // no titles yet + //LLRect title_rect = mTitleBox->getRect(); + //S32 title_right = title_rect.mLeft + mTitleWidth; + //BOOL show_right_side = title_right < mRect.getWidth(); + + S32 line = left; + for( S32 i=0; i<4; i++ ) + { + gl_line_2d(line, top, line, bottom, mDragHighlightColor); + + gl_line_2d(line+1, top, line+1, bottom, mDragShadowColor); + + line += LINE_SPACING; + } + } + */ + + // Colorize the text to match the frontmost state + if (mTitleBox) + { + mTitleBox->setEnabled(mForeground); + } + + LLView::draw(); +} + +void LLDragHandleTop::reshapeTitleBox() +{ + const LLFontGL* font = gResMgr->getRes( LLFONT_SANSSERIF ); + S32 title_width = font->getWidth( mTitleBox->getText() ) + TITLE_PAD; + if (mMaxTitleWidth > 0) + title_width = llmin(title_width, mMaxTitleWidth); + S32 title_height = llround(font->getLineHeight()); + LLRect title_rect; + title_rect.setLeftTopAndSize( + LEFT_PAD, + mRect.getHeight() - BORDER_PAD, + mRect.getWidth() - LEFT_PAD - RIGHT_PAD, + title_height); + + mTitleBox->setRect( title_rect ); +} + +void LLDragHandleTop::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLView::reshape(width, height, called_from_parent); + reshapeTitleBox(); +} + +void LLDragHandleLeft::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLView::reshape(width, height, called_from_parent); +} + +//------------------------------------------------------------- +// UI event handling +//------------------------------------------------------------- + +BOOL LLDragHandle::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // Route future Mouse messages here preemptively. (Release on mouse up.) + // No handler needed for focus lost since this clas has no state that depends on it. + gFocusMgr.setMouseCapture(this, NULL ); + + localPointToScreen(x, y, &mDragLastScreenX, &mDragLastScreenY); + mLastMouseScreenX = mDragLastScreenX; + mLastMouseScreenY = mDragLastScreenY; + + // Note: don't pass on to children + return TRUE; +} + + +BOOL LLDragHandle::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if( gFocusMgr.getMouseCapture() == this ) + { + // Release the mouse + gFocusMgr.setMouseCapture( NULL, NULL ); + } + + // Note: don't pass on to children + return TRUE; +} + + +BOOL LLDragHandle::handleHover(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + // We only handle the click if the click both started and ended within us + if( gFocusMgr.getMouseCapture() == this ) + { + S32 screen_x; + S32 screen_y; + localPointToScreen(x, y, &screen_x, &screen_y); + + // Resize the parent + S32 delta_x = screen_x - mDragLastScreenX; + S32 delta_y = screen_y - mDragLastScreenY; + getParent()->translate(delta_x, delta_y); + S32 pre_snap_x = getParent()->getRect().mLeft; + S32 pre_snap_y = getParent()->getRect().mBottom; + mDragLastScreenX = screen_x; + mDragLastScreenY = screen_y; + + LLRect new_rect; + LLCoordGL mouse_dir; + // use hysteresis on mouse motion to preserve user intent when mouse stops moving + mouse_dir.mX = (screen_x == mLastMouseScreenX) ? mLastMouseDir.mX : screen_x - mLastMouseScreenX; + mouse_dir.mY = (screen_y == mLastMouseScreenY) ? mLastMouseDir.mY : screen_y - mLastMouseScreenY; + mLastMouseDir = mouse_dir; + mLastMouseScreenX = screen_x; + mLastMouseScreenY = screen_y; + + LLView* snap_view = getParent()->findSnapRect(new_rect, mouse_dir, SNAP_PARENT_AND_SIBLINGS, sSnapMargin); + + getParent()->snappedTo(snap_view); + delta_x = new_rect.mLeft - pre_snap_x; + delta_y = new_rect.mBottom - pre_snap_y; + getParent()->translate(delta_x, delta_y); + mDragLastScreenX += delta_x; + mDragLastScreenY += delta_y; + + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" <setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl; + handled = TRUE; + } + + // Note: don't pass on to children + + return handled; +} + +void LLDragHandle::setValue(const LLSD& value) +{ + setTitle(value.asString()); +} diff --git a/indra/llui/lldraghandle.h b/indra/llui/lldraghandle.h new file mode 100644 index 0000000000..557c01cec6 --- /dev/null +++ b/indra/llui/lldraghandle.h @@ -0,0 +1,98 @@ +/** + * @file lldraghandle.h + * @brief LLDragHandle base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// A widget for dragging a view around the screen using the mouse. + +#ifndef LL_DRAGHANDLE_H +#define LL_DRAGHANDLE_H + +#include "llview.h" +#include "v4color.h" +#include "llrect.h" +#include "llcoord.h" + +class LLTextBox; + +class LLDragHandle : public LLView +{ +public: + LLDragHandle(const LLString& name, const LLRect& rect, const LLString& title ); + + virtual void setValue(const LLSD& value); + + void setForeground(BOOL b) { mForeground = b; } + void setMaxTitleWidth(S32 max_width) {mMaxTitleWidth = llmin(max_width, mMaxTitleWidth); } + void setTitleVisible(BOOL visible); + + virtual void setTitle( const LLString& title ) = 0; + virtual const LLString& getTitle() const = 0; + virtual void draw() = 0; + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE) = 0; + + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + +protected: + S32 mDragLastScreenX; + S32 mDragLastScreenY; + S32 mLastMouseScreenX; + S32 mLastMouseScreenY; + LLCoordGL mLastMouseDir; + LLColor4 mDragHighlightColor; + LLColor4 mDragShadowColor; + LLTextBox* mTitleBox; + S32 mMaxTitleWidth; + BOOL mForeground; + + // Pixels near the edge to snap floaters. + static S32 sSnapMargin; +}; + + +// Use this one for traditional top-of-window draggers +class LLDragHandleTop +: public LLDragHandle +{ +public: + LLDragHandleTop(const LLString& name, const LLRect& rect, const LLString& title ); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual void setTitle( const LLString& title ); + virtual const LLString& getTitle() const; + virtual void draw(); + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + +private: + void reshapeTitleBox(); +}; + + +// Use this for left-side, vertical text draggers +class LLDragHandleLeft +: public LLDragHandle +{ +public: + LLDragHandleLeft(const LLString& name, const LLRect& rect, const LLString& title ); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual void setTitle( const LLString& title ); + virtual const LLString& getTitle() const; + virtual void draw(); + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + +}; + +const S32 DRAG_HANDLE_HEIGHT = 16; +const S32 DRAG_HANDLE_WIDTH = 16; + +#endif // LL_DRAGHANDLE_H diff --git a/indra/llui/lleditmenuhandler.cpp b/indra/llui/lleditmenuhandler.cpp new file mode 100644 index 0000000000..656eaff563 --- /dev/null +++ b/indra/llui/lleditmenuhandler.cpp @@ -0,0 +1,107 @@ +/** +* @file lleditmenuhandler.cpp +* @authors Aaron Yonas, James Cook +* +* Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. +* $License$ +*/ + +#include "stdtypes.h" + +#include "lleditmenuhandler.h" + +LLEditMenuHandler* gEditMenuHandler = NULL; + +// virtual +LLEditMenuHandler::~LLEditMenuHandler() +{ } + +// virtual +void LLEditMenuHandler::undo() +{ } + +// virtual +BOOL LLEditMenuHandler::canUndo() +{ + return FALSE; +} + +// virtual +void LLEditMenuHandler::redo() +{ } + +// virtual +BOOL LLEditMenuHandler::canRedo() +{ + return FALSE; +} + +// virtual +void LLEditMenuHandler::cut() +{ } + +// virtual +BOOL LLEditMenuHandler::canCut() +{ + return FALSE; +} + +// virtual +void LLEditMenuHandler::copy() +{ } + +// virtual +BOOL LLEditMenuHandler::canCopy() +{ + return FALSE; +} + +// virtual +void LLEditMenuHandler::paste() +{ } + +// virtual +BOOL LLEditMenuHandler::canPaste() +{ + return FALSE; +} + +// virtual +void LLEditMenuHandler::doDelete() +{ } + +// virtual +BOOL LLEditMenuHandler::canDoDelete() +{ + return FALSE; +} + +// virtual +void LLEditMenuHandler::selectAll() +{ } + +// virtual +BOOL LLEditMenuHandler::canSelectAll() +{ + return FALSE; +} + +// virtual +void LLEditMenuHandler::deselect() +{ } + +// virtual +BOOL LLEditMenuHandler::canDeselect() +{ + return FALSE; +} + +// virtual +void LLEditMenuHandler::duplicate() +{ } + +// virtual +BOOL LLEditMenuHandler::canDuplicate() +{ + return FALSE; +} diff --git a/indra/llui/lleditmenuhandler.h b/indra/llui/lleditmenuhandler.h new file mode 100644 index 0000000000..3f49f2c6e8 --- /dev/null +++ b/indra/llui/lleditmenuhandler.h @@ -0,0 +1,50 @@ +/** +* @file lleditmenuhandler.h +* @authors Aaron Yonas, James Cook +* +* Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. +* $License$ +*/ + +#ifndef LLEDITMENUHANDLER_H +#define LLEDITMENUHANDLER_H + +// Interface used by menu system for plug-in hotkey/menu handling +class LLEditMenuHandler +{ +public: + // this is needed even though this is just an interface class. + virtual ~LLEditMenuHandler(); + + virtual void undo(); + virtual BOOL canUndo(); + + virtual void redo(); + virtual BOOL canRedo(); + + virtual void cut(); + virtual BOOL canCut(); + + virtual void copy(); + virtual BOOL canCopy(); + + virtual void paste(); + virtual BOOL canPaste(); + + // "delete" is a keyword + virtual void doDelete(); + virtual BOOL canDoDelete(); + + virtual void selectAll(); + virtual BOOL canSelectAll(); + + virtual void deselect(); + virtual BOOL canDeselect(); + + virtual void duplicate(); + virtual BOOL canDuplicate(); +}; + +extern LLEditMenuHandler* gEditMenuHandler; + +#endif diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp new file mode 100644 index 0000000000..3f9139fe86 --- /dev/null +++ b/indra/llui/llfloater.cpp @@ -0,0 +1,2933 @@ +/** + * @file llfloater.cpp + * @brief LLFloater base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Floating "windows" within the GL display, like the inventory floater, +// mini-map floater, etc. + +#include "linden_common.h" + +#include "llfloater.h" + +#include "llfocusmgr.h" + +#include "lluictrlfactory.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "lldraghandle.h" +#include "llfocusmgr.h" +#include "llresizebar.h" +#include "llresizehandle.h" +#include "llkeyboard.h" +#include "llmenugl.h" // MENU_BAR_HEIGHT +#include "lltextbox.h" +#include "llresmgr.h" +#include "llui.h" +#include "llviewborder.h" +#include "llwindow.h" +#include "llstl.h" +#include "llcontrol.h" +#include "lltabcontainer.h" +#include "v2math.h" + +extern BOOL gNoRender; + +const S32 MINIMIZED_WIDTH = 160; +const S32 CLOSE_BOX_FROM_TOP = 1; + +LLString LLFloater::sButtonActiveImageNames[BUTTON_COUNT] = +{ + "UIImgBtnCloseActiveUUID", //BUTTON_CLOSE + "UIImgBtnRestoreActiveUUID", //BUTTON_RESTORE + "UIImgBtnMinimizeActiveUUID", //BUTTON_MINIMIZE + "UIImgBtnTearOffActiveUUID", //BUTTON_TEAR_OFF + "UIImgBtnCloseActiveUUID", //BUTTON_EDIT +}; + +LLString LLFloater::sButtonInactiveImageNames[BUTTON_COUNT] = +{ + "UIImgBtnCloseInactiveUUID", //BUTTON_CLOSE + "UIImgBtnRestoreInactiveUUID", //BUTTON_RESTORE + "UIImgBtnMinimizeInactiveUUID", //BUTTON_MINIMIZE + "UIImgBtnTearOffInactiveUUID", //BUTTON_TEAR_OFF + "UIImgBtnCloseInactiveUUID", //BUTTON_EDIT +}; + +LLString LLFloater::sButtonPressedImageNames[BUTTON_COUNT] = +{ + "UIImgBtnClosePressedUUID", //BUTTON_CLOSE + "UIImgBtnRestorePressedUUID", //BUTTON_RESTORE + "UIImgBtnMinimizePressedUUID", //BUTTON_MINIMIZE + "UIImgBtnTearOffPressedUUID", //BUTTON_TEAR_OFF + "UIImgBtnClosePressedUUID", //BUTTON_EDIT +}; + +LLString LLFloater::sButtonNames[BUTTON_COUNT] = +{ + "llfloater_close_btn", //BUTTON_CLOSE + "llfloater_restore_btn", //BUTTON_RESTORE + "llfloater_minimize_btn", //BUTTON_MINIMIZE + "llfloater_tear_off_btn", //BUTTON_TEAR_OFF + "llfloater_edit_btn", //BUTTON_EDIT +}; + +LLString LLFloater::sButtonToolTips[BUTTON_COUNT] = +{ +#ifdef LL_DARWIN + "Close (Cmd-W)", //BUTTON_CLOSE +#else + "Close (Ctrl-W)", //BUTTON_CLOSE +#endif + "Restore", //BUTTON_RESTORE + "Minimize", //BUTTON_MINIMIZE + "Tear Off", //BUTTON_TEAR_OFF + "Edit", //BUTTON_EDIT +}; + +LLFloater::click_callback LLFloater::sButtonCallbacks[BUTTON_COUNT] = +{ + LLFloater::onClickClose, //BUTTON_CLOSE + LLFloater::onClickMinimize, //BUTTON_RESTORE + LLFloater::onClickMinimize, //BUTTON_MINIMIZE + LLFloater::onClickTearOff, //BUTTON_TEAR_OFF + LLFloater::onClickEdit, //BUTTON_EDIT +}; + +LLMultiFloater* LLFloater::sHostp = NULL; +BOOL LLFloater::sEditModeEnabled; +LLFloater::handle_map_t LLFloater::sFloaterMap; + +LLFloaterView* gFloaterView = NULL; + +LLFloater::LLFloater() +{ + // automatically take focus when opened + mAutoFocus = TRUE; + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + mButtonsEnabled[i] = FALSE; + mButtons[i] = NULL; + } + mDragHandle = NULL; +} + +LLFloater::LLFloater(const LLString& name) +: LLPanel(name) +{ + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + mButtonsEnabled[i] = FALSE; + mButtons[i] = NULL; + } + + LLString title; // null string + // automatically take focus when opened + mAutoFocus = TRUE; + init(title, FALSE, DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT, FALSE, TRUE, TRUE); // defaults +} + + +LLFloater::LLFloater(const LLString& name, const LLRect& rect, const LLString& title, + BOOL resizable, + S32 min_width, + S32 min_height, + BOOL drag_on_left, + BOOL minimizable, + BOOL close_btn, + BOOL bordered) +: LLPanel(name, rect, bordered) +{ + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + mButtonsEnabled[i] = FALSE; + mButtons[i] = NULL; + } + // automatically take focus when opened + mAutoFocus = TRUE; + init( title, resizable, min_width, min_height, drag_on_left, minimizable, close_btn); +} + +LLFloater::LLFloater(const LLString& name, const LLString& rect_control, const LLString& title, + BOOL resizable, + S32 min_width, + S32 min_height, + BOOL drag_on_left, + BOOL minimizable, + BOOL close_btn, + BOOL bordered) +: LLPanel(name, rect_control, bordered) +{ + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + mButtonsEnabled[i] = FALSE; + mButtons[i] = NULL; + } + // automatically take focus when opened + mAutoFocus = TRUE; + init( title, resizable, min_width, min_height, drag_on_left, minimizable, close_btn); +} + + +// Note: Floaters constructed from XML call init() twice! +void LLFloater::init(const LLString& title, + BOOL resizable, S32 min_width, S32 min_height, + BOOL drag_on_left, BOOL minimizable, BOOL close_btn) +{ + // Init function can be called more than once, so clear out old data. + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + mButtonsEnabled[i] = FALSE; + if (mButtons[i] != NULL) + { + removeChild(mButtons[i]); + delete mButtons[i]; + mButtons[i] = NULL; + } + } + mButtonScale = 1.f; + + LLPanel::deleteAllChildren(); + //sjb: HACK! we had a border which was just deleted, so re-create it + if (mBorder != NULL) + { + addBorder(); + } + + // chrome floaters don't take focus at all + mIsFocusRoot = !getIsChrome(); + + // Reset cached pointers + mDragHandle = NULL; + for (S32 i = 0; i < 4; i++) + { + mResizeBar[i] = NULL; + mResizeHandle[i] = NULL; + } + mCanTearOff = TRUE; + mEditing = FALSE; + + // Clicks stop here. + setMouseOpaque(TRUE); + + mFirstLook = TRUE; + mForeground = FALSE; + mDragOnLeft = drag_on_left == TRUE; + + // Floaters always draw their background, unlike every other panel. + setBackgroundVisible(TRUE); + + // Floaters start not minimized. When minimized, they save their + // prior rectangle to be used on restore. + mMinimized = FALSE; + mPreviousRect.set(0,0,0,0); + + S32 close_pad; // space to the right of close box + S32 close_box_size; // For layout purposes, how big is the close box? + if (close_btn) + { + close_box_size = LLFLOATER_CLOSE_BOX_SIZE; + close_pad = 0; + } + else + { + close_box_size = 0; + close_pad = 0; + } + + S32 minimize_box_size; + S32 minimize_pad; + if (minimizable && !drag_on_left) + { + minimize_box_size = LLFLOATER_CLOSE_BOX_SIZE; + minimize_pad = 0; + } + else + { + minimize_box_size = 0; + minimize_pad = 0; + } + + // Drag Handle + // Add first so it's in the background. +// const S32 drag_pad = 2; + LLRect drag_handle_rect; + if (!drag_on_left) + { + drag_handle_rect.set( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + + /* + drag_handle_rect.setLeftTopAndSize( + 0, mRect.getHeight(), + mRect.getWidth() + - LLPANEL_BORDER_WIDTH + - drag_pad + - minimize_box_size - minimize_pad + - close_box_size - close_pad, + DRAG_HANDLE_HEIGHT); + */ + mDragHandle = new LLDragHandleTop( "Drag Handle", drag_handle_rect, title ); + } + else + { + drag_handle_rect.setOriginAndSize( + 0, 0, + DRAG_HANDLE_WIDTH, + mRect.getHeight() - LLPANEL_BORDER_WIDTH - close_box_size); + mDragHandle = new LLDragHandleLeft("drag", drag_handle_rect, title ); + } + mDragHandle->setSaveToXML(false); + addChild(mDragHandle); + + // Resize Handle + mResizable = resizable; + mMinWidth = min_width; + mMinHeight = min_height; + + if( mResizable ) + { + // Resize bars (sides) + const S32 RESIZE_BAR_THICKNESS = 3; + mResizeBar[0] = new LLResizeBar( + "resizebar_left", + LLRect( 0, mRect.getHeight(), RESIZE_BAR_THICKNESS, 0), + min_width, min_height, LLResizeBar::LEFT ); + mResizeBar[0]->setSaveToXML(false); + addChild( mResizeBar[0] ); + + mResizeBar[1] = new LLResizeBar( + "resizebar_top", + LLRect( 0, mRect.getHeight(), mRect.getWidth(), mRect.getHeight() - RESIZE_BAR_THICKNESS), + min_width, min_height, LLResizeBar::TOP ); + mResizeBar[1]->setSaveToXML(false); + addChild( mResizeBar[1] ); + + mResizeBar[2] = new LLResizeBar( + "resizebar_right", + LLRect( mRect.getWidth() - RESIZE_BAR_THICKNESS, mRect.getHeight(), mRect.getWidth(), 0), + min_width, min_height, LLResizeBar::RIGHT ); + mResizeBar[2]->setSaveToXML(false); + addChild( mResizeBar[2] ); + + mResizeBar[3] = new LLResizeBar( + "resizebar_bottom", + LLRect( 0, RESIZE_BAR_THICKNESS, mRect.getWidth(), 0), + min_width, min_height, LLResizeBar::BOTTOM ); + mResizeBar[3]->setSaveToXML(false); + addChild( mResizeBar[3] ); + + + // Resize handles (corners) + mResizeHandle[0] = new LLResizeHandle( + "Resize Handle", + LLRect( mRect.getWidth() - RESIZE_HANDLE_WIDTH, RESIZE_HANDLE_HEIGHT, mRect.getWidth(), 0), + min_width, + min_height, + LLResizeHandle::RIGHT_BOTTOM); + mResizeHandle[0]->setSaveToXML(false); + addChild(mResizeHandle[0]); + + mResizeHandle[1] = new LLResizeHandle( "resize", + LLRect( mRect.getWidth() - RESIZE_HANDLE_WIDTH, mRect.getHeight(), mRect.getWidth(), mRect.getHeight() - RESIZE_HANDLE_HEIGHT), + min_width, + min_height, + LLResizeHandle::RIGHT_TOP ); + mResizeHandle[1]->setSaveToXML(false); + addChild(mResizeHandle[1]); + + mResizeHandle[2] = new LLResizeHandle( "resize", + LLRect( 0, RESIZE_HANDLE_HEIGHT, RESIZE_HANDLE_WIDTH, 0 ), + min_width, + min_height, + LLResizeHandle::LEFT_BOTTOM ); + mResizeHandle[2]->setSaveToXML(false); + addChild(mResizeHandle[2]); + + mResizeHandle[3] = new LLResizeHandle( "resize", + LLRect( 0, mRect.getHeight(), RESIZE_HANDLE_WIDTH, mRect.getHeight() - RESIZE_HANDLE_HEIGHT ), + min_width, + min_height, + LLResizeHandle::LEFT_TOP ); + mResizeHandle[3]->setSaveToXML(false); + addChild(mResizeHandle[3]); + } + else + { + mResizeBar[0] = NULL; + mResizeBar[1] = NULL; + mResizeBar[2] = NULL; + mResizeBar[3] = NULL; + + mResizeHandle[0] = NULL; + mResizeHandle[1] = NULL; + mResizeHandle[2] = NULL; + mResizeHandle[3] = NULL; + } + + // Close button. + if (close_btn) + { + mButtonsEnabled[BUTTON_CLOSE] = TRUE; + } + + // Minimize button only for top draggers + if ( !drag_on_left && minimizable ) + { + mButtonsEnabled[BUTTON_MINIMIZE] = TRUE; + } + + buildButtons(); + + // JC - Don't do this here, because many floaters first construct themselves, + // then show themselves. Put it in setVisibleAndFrontmost. + // make_ui_sound("UISndWindowOpen"); + + // RN: floaters are created in the invisible state + setVisible(FALSE); + + // add self to handle->floater map + sFloaterMap[mViewHandle] = this; + + if (!getParent()) + { + gFloaterView->addChild(this); + } +} + +// virtual +LLFloater::~LLFloater() +{ + control_map_t::iterator itor; + for (itor = mFloaterControls.begin(); itor != mFloaterControls.end(); ++itor) + { + delete itor->second; + } + mFloaterControls.clear(); + + //// am I not hosted by another floater? + //if (mHostHandle.isDead()) + //{ + // LLFloaterView* parent = (LLFloaterView*) getParent(); + + // if( parent ) + // { + // parent->removeChild( this ); + // } + //} + + // Just in case we might still have focus here, release it. + releaseFocus(); + + // This is important so that floaters with persistent rects (i.e., those + // created with rect control rather than an LLRect) are restored in their + // correct, non-minimized positions. + setMinimized( FALSE ); + + sFloaterMap.erase(mViewHandle); + + delete mDragHandle; + for (S32 i = 0; i < 4; i++) + { + delete mResizeBar[i]; + delete mResizeHandle[i]; + } +} + +// virtual +EWidgetType LLFloater::getWidgetType() const +{ + return WIDGET_TYPE_FLOATER; +} + +// virtual +LLString LLFloater::getWidgetTag() const +{ + return LL_FLOATER_TAG; +} + +void LLFloater::destroy() +{ + die(); +} + +void LLFloater::setVisible( BOOL visible ) +{ + LLPanel::setVisible(visible); + if( visible && mFirstLook ) + { + mFirstLook = FALSE; + } + + if( !visible ) + { + if( gFocusMgr.childIsTopView( this ) ) + { + gFocusMgr.setTopView(NULL, NULL); + } + + if( gFocusMgr.childHasMouseCapture( this ) ) + { + gFocusMgr.setMouseCapture(NULL, NULL); + } + } + + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ) + { + LLFloater* floaterp = LLFloater::getFloaterByHandle(*dependent_it); + + if (floaterp) + { + floaterp->setVisible(visible); + } + ++dependent_it; + } +} + +LLView* LLFloater::getRootMostFastFrameView() +{ + // trying to render a background floater in a fast frame, abort!!! + //if (!isFrontmost()) + //{ + // gViewerWindow->finishFastFrame(); + //} + + return LLView::getRootMostFastFrameView(); +} + +void LLFloater::open() +{ + //RN: for now, we don't allow rehosting from one multifloater to another + // just need to fix the bugs + LLMultiFloater* hostp = getHost(); + if (sHostp != NULL && hostp == NULL) + { + // needs a host + sHostp->addFloater(this, TRUE); + } + else if (hostp != NULL) + { + // already hosted + hostp->showFloater(this); + } + else + { + setMinimized(FALSE); + setVisibleAndFrontmost(mAutoFocus); + } + + if (mSoundFlags != SILENT) + { + if (!getVisible() || isMinimized()) + { + make_ui_sound("UISndWindowOpen"); + } + } +} + +void LLFloater::close(bool app_quitting) +{ + // Always unminimize before trying to close. + // Most of the time the user will never see this state. + setMinimized(FALSE); + + if (canClose()) + { + if (getHost()) + { + ((LLMultiFloater*)getHost())->removeFloater(this); + } + + if (mSoundFlags != SILENT + && getVisible() + && !app_quitting) + { + make_ui_sound("UISndWindowClose"); + } + + // now close dependent floater + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ) + { + + LLFloater* floaterp = LLFloater::getFloaterByHandle(*dependent_it); + if (floaterp) + { + ++dependent_it; + floaterp->close(); + } + else + { + mDependents.erase(dependent_it++); + } + } + + cleanupHandles(); + gFocusMgr.clearLastFocusForGroup(this); + + // Do this early, so UI controls will commit before the + // window is taken down. + releaseFocus(); + + // give focus to dependee floater if it exists, and we had focus first + if (isDependent()) + { + LLFloater* dependee = LLFloater::getFloaterByHandle(mDependeeHandle); + if (dependee && !dependee->isDead()) + { + dependee->setFocus(TRUE); + } + } + + // Let floater do cleanup. + onClose(app_quitting); + } +} + + +void LLFloater::releaseFocus() +{ + if( gFocusMgr.childIsTopView( this ) ) + { + gFocusMgr.setTopView(NULL, NULL); + } + + if( gFocusMgr.childHasKeyboardFocus( this ) ) + { + gFocusMgr.setKeyboardFocus(NULL, NULL); + } + + if( gFocusMgr.childHasMouseCapture( this ) ) + { + gFocusMgr.setMouseCapture(NULL, NULL); + } +} + + +void LLFloater::setResizeLimits( S32 min_width, S32 min_height ) +{ + mMinWidth = min_width; + mMinHeight = min_height; + + for( S32 i = 0; i < 4; i++ ) + { + if( mResizeBar[i] ) + { + mResizeBar[i]->setResizeLimits( min_width, min_height ); + } + if( mResizeHandle[i] ) + { + mResizeHandle[i]->setResizeLimits( min_width, min_height ); + } + } +} + + +void LLFloater::center() +{ + if(getHost()) + { + // hosted floaters can't move + return; + } + const LLRect &window = gFloaterView->getRect(); + + S32 left = window.mLeft + (window.getWidth() - mRect.getWidth()) / 2; + S32 bottom = window.mBottom + (window.getHeight() - mRect.getHeight()) / 2; + + translate( left - mRect.mLeft, bottom - mRect.mBottom ); +} + +void LLFloater::applyRectControl() +{ + if (!mRectControl.empty()) + { + const LLRect& rect = LLUI::sConfigGroup->getRect(mRectControl); + translate( rect.mLeft - mRect.mLeft, rect.mBottom - mRect.mBottom); + if (mResizable) + { + reshape(llmax(mMinWidth, rect.getWidth()), llmax(mMinHeight, rect.getHeight())); + } + } +} + +void LLFloater::setTitle( const LLString& title ) +{ + if (gNoRender) + { + return; + } + mDragHandle->setTitle( title ); +} + +const LLString& LLFloater::getTitle() const +{ + return mDragHandle ? mDragHandle->getTitle() : LLString::null; +} + +void LLFloater::translate(S32 x, S32 y) +{ + LLView::translate(x, y); + + if (x != 0 || y != 0) + { + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ++dependent_it) + { + LLFloater* floaterp = LLFloater::getFloaterByHandle(*dependent_it); + // is a dependent snapped to us? + if (floaterp && floaterp->getSnapTarget() == mViewHandle) + { + floaterp->translate(x, y); + } + } + } +} + +BOOL LLFloater::canSnapTo(LLView* other_view) +{ + if (other_view && other_view != getParent()) + { + LLFloater* other_floaterp = (LLFloater*)other_view; + + if (other_floaterp->getSnapTarget() == mViewHandle && mDependents.find(other_floaterp->getHandle()) != mDependents.end()) + { + // this is a dependent that is already snapped to us, so don't snap back to it + return FALSE; + } + } + + return LLView::canSnapTo(other_view); +} + +void LLFloater::snappedTo(LLView* snap_view) +{ + if (!snap_view || snap_view == getParent()) + { + clearSnapTarget(); + } + else + { + //RN: assume it's a floater as it must be a sibling to our parent floater + LLFloater* floaterp = (LLFloater*)snap_view; + + setSnapTarget(floaterp->getHandle()); + } +} + +void LLFloater::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + S32 old_width = mRect.getWidth(); + S32 old_height = mRect.getHeight(); + + LLView::reshape(width, height, called_from_parent); + + if (width != old_width || height != old_height) + { + // gather all snapped dependents + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ++dependent_it) + { + LLFloater* floaterp = LLFloater::getFloaterByHandle(*dependent_it); + // is a dependent snapped to us? + if (floaterp && floaterp->getSnapTarget() == mViewHandle) + { + S32 delta_x = 0; + S32 delta_y = 0; + // check to see if it snapped to right or top + LLRect floater_rect = floaterp->getRect(); + if (floater_rect.mLeft - mRect.mLeft >= old_width || + floater_rect.mRight == mRect.mLeft + old_width) + { + // was snapped directly onto right side or aligned with it + delta_x += width - old_width; + } + if (floater_rect.mBottom - mRect.mBottom >= old_height || + floater_rect.mTop == mRect.mBottom + old_height) + { + // was snapped directly onto top side or aligned with it + delta_y += height - old_height; + } + + floaterp->translate(delta_x, delta_y); + } + } + } +} + +void LLFloater::setMinimized(BOOL minimize) +{ + if (minimize == mMinimized) return; + + if (minimize) + { + mMinimized = TRUE; + + mPreviousRect = mRect; + + reshape( MINIMIZED_WIDTH, LLFLOATER_HEADER_SIZE, TRUE); + + S32 left, bottom; + gFloaterView->getMinimizePosition(&left, &bottom); + setOrigin( left, bottom ); + + if (mButtonsEnabled[BUTTON_MINIMIZE]) + { + mButtonsEnabled[BUTTON_MINIMIZE] = FALSE; + mButtonsEnabled[BUTTON_RESTORE] = TRUE; + } + + mMinimizedHiddenChildren.clear(); + // hide all children + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if (!viewp->getVisible()) + { + mMinimizedHiddenChildren.push_back(viewp); + } + viewp->setVisible(FALSE); + } + + // except the special controls + if (mDragHandle) + { + mDragHandle->setVisible(TRUE); + } + + setBorderVisible(TRUE); + + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ) + { + LLFloater* floaterp = LLFloater::getFloaterByHandle(*dependent_it); + if (floaterp) + { + floaterp->setVisible(FALSE); + } + ++dependent_it; + } + + // Lose keyboard focus when minimized + releaseFocus(); + } + else + { + reshape( mPreviousRect.getWidth(), mPreviousRect.getHeight(), TRUE ); + setOrigin( mPreviousRect.mLeft, mPreviousRect.mBottom ); + + mMinimized = FALSE; + + if (mButtonsEnabled[BUTTON_RESTORE]) + { + mButtonsEnabled[BUTTON_MINIMIZE] = TRUE; + mButtonsEnabled[BUTTON_RESTORE] = FALSE; + } + + // show all children + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + viewp->setVisible(TRUE); + } + + std::vector::iterator itor = mMinimizedHiddenChildren.begin(); + while (itor != mMinimizedHiddenChildren.end()) + { + (*itor)->setVisible(FALSE); + ++itor; + } + mMinimizedHiddenChildren.clear(); + + // show dependent floater + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ) + { + LLFloater* floaterp = LLFloater::getFloaterByHandle(*dependent_it); + if (floaterp) + { + floaterp->setVisible(TRUE); + } + ++dependent_it; + } + } + make_ui_sound("UISndWindowClose"); + updateButtons(); +} + +void LLFloater::setFocus( BOOL b ) +{ + if (b && getIsChrome()) + { + return; + } + LLUICtrl* last_focus = gFocusMgr.getLastFocusForGroup(this); + // a descendent already has focus + BOOL child_had_focus = gFocusMgr.childHasKeyboardFocus(this); + + // give focus to first valid descendent + LLPanel::setFocus(b); + + if (b) + { + // only push focused floaters to front of stack if not in midst of ctrl-tab cycle + if (!getHost() && !((LLFloaterView*)getParent())->getCycleMode()) + { + if (!isFrontmost()) + { + setFrontmost(); + } + } + + // when getting focus, delegate to last descendent which had focus + if (last_focus && !child_had_focus && + last_focus->isInEnabledChain() && + last_focus->isInVisibleChain()) + { + // FIXME: should handle case where focus doesn't stick + last_focus->setFocus(TRUE); + } + } +} + +void LLFloater::setIsChrome(BOOL is_chrome) +{ + // chrome floaters don't take focus at all + if (is_chrome) + { + // remove focus if we're changing to chrome + setFocus(FALSE); + // can't Ctrl-Tab to "chrome" floaters + mIsFocusRoot = FALSE; + } + + // no titles displayed on "chrome" floaters + mDragHandle->setTitleVisible(!is_chrome); + + LLPanel::setIsChrome(is_chrome); +} + +// Change the draw style to account for the foreground state. +void LLFloater::setForeground(BOOL front) +{ + if (front != mForeground) + { + mForeground = front; + mDragHandle->setForeground( front ); + + if (!front) + { + releaseFocus(); + } + + setBackgroundOpaque( front ); + } +} + +void LLFloater::cleanupHandles() +{ + // remove handles to non-existent dependents + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ) + { + LLFloater* floaterp = LLFloater::getFloaterByHandle(*dependent_it); + if (!floaterp) + { + mDependents.erase(dependent_it++); + } + else + { + ++dependent_it; + } + } +} + +void LLFloater::setHost(LLMultiFloater* host) +{ + if (mHostHandle.isDead() && host) + { + // make buttons smaller for hosted windows to differentiate from parent + mButtonScale = 0.9f; + + // add tear off button + if (mCanTearOff) + { + mButtonsEnabled[BUTTON_TEAR_OFF] = TRUE; + } + + mIsFocusRoot = FALSE; + } + else if (!mHostHandle.isDead() && !host) + { + mButtonScale = 1.f; + mIsFocusRoot = TRUE; + //mButtonsEnabled[BUTTON_TEAR_OFF] = FALSE; + } + updateButtons(); + if (host) + { + mHostHandle = host->getHandle(); + mLastHostHandle = host->getHandle(); + } + else + { + mHostHandle.markDead(); + } +} + +void LLFloater::moveResizeHandleToFront() +{ + // 0 is the bottom right + if( mResizeHandle[0] ) + { + sendChildToFront(mResizeHandle[0]); + } +} + +BOOL LLFloater::isFrontmost() +{ + return gFloaterView && gFloaterView->getFrontmost() == this && getVisible(); +} + +void LLFloater::addDependentFloater(LLFloater* floaterp, BOOL reposition) +{ + mDependents.insert(floaterp->getHandle()); + floaterp->mDependeeHandle = getHandle(); + + if (reposition) + { + floaterp->setRect(gFloaterView->findNeighboringPosition(this, floaterp)); + floaterp->setSnapTarget(mViewHandle); + } + gFloaterView->adjustToFitScreen(floaterp, FALSE); + if (floaterp->isFrontmost()) + { + // make sure to bring self and sibling floaters to front + gFloaterView->bringToFront(floaterp); + } +} + +void LLFloater::addDependentFloater(LLViewHandle dependent, BOOL reposition) +{ + LLFloater* dependent_floaterp = LLFloater::getFloaterByHandle(dependent); + if(dependent_floaterp) + { + addDependentFloater(dependent_floaterp, reposition); + } +} + +void LLFloater::removeDependentFloater(LLFloater* floaterp) +{ + mDependents.erase(floaterp->getHandle()); + floaterp->mDependeeHandle = LLViewHandle::sDeadHandle; +} + +// virtual +BOOL LLFloater::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if( mMinimized ) + { + // Offer the click to the close button. + // Any other click = restore + if( mButtonsEnabled[BUTTON_CLOSE] ) + { + S32 local_x = x - mButtons[BUTTON_CLOSE]->getRect().mLeft; + S32 local_y = y - mButtons[BUTTON_CLOSE]->getRect().mBottom; + + if (mButtons[BUTTON_CLOSE]->pointInView(local_x, local_y) + && mButtons[BUTTON_CLOSE]->handleMouseDown(local_x, local_y, mask)) + { + // close button handled it, return + return TRUE; + } + } + + // restore + bringToFront( x, y ); + return TRUE; + } + else + { + bringToFront( x, y ); + return LLPanel::handleMouseDown( x, y, mask ); + } +} + +// virtual +BOOL LLFloater::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL was_minimized = mMinimized; + bringToFront( x, y ); + return was_minimized || LLPanel::handleRightMouseDown( x, y, mask ); +} + + +// virtual +BOOL LLFloater::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + BOOL was_minimized = mMinimized; + setMinimized(FALSE); + return was_minimized || LLPanel::handleDoubleClick(x, y, mask); +} + +void LLFloater::bringToFront( S32 x, S32 y ) +{ + if (getVisible() && pointInView(x, y)) + { + LLMultiFloater* hostp = getHost(); + if (hostp) + { + hostp->showFloater(this); + } + else + { + LLFloaterView* parent = (LLFloaterView*) getParent(); + if (parent) + { + parent->bringToFront( this ); + } + } + } +} + + +// virtual +void LLFloater::setVisibleAndFrontmost(BOOL take_focus) +{ + setVisible(TRUE); + setFrontmost(take_focus); +} + +void LLFloater::setFrontmost(BOOL take_focus) +{ + LLMultiFloater* hostp = getHost(); + if (hostp) + { + // this will bring the host floater to the front and select + // the appropriate panel + hostp->showFloater(this); + } + else + { + // there are more than one floater view + // so we need to query our parent directly + ((LLFloaterView*)getParent())->bringToFront(this, take_focus); + } +} + +// static +LLFloater* LLFloater::getFloaterByHandle(LLViewHandle handle) +{ + LLFloater* floater = NULL; + if (sFloaterMap.count(handle)) + { + floater = sFloaterMap[handle]; + } + if (floater && !floater->isDead()) + { + return floater; + } + else + { + return NULL; + } +} + +//static +void LLFloater::setEditModeEnabled(BOOL enable) +{ + if (enable != sEditModeEnabled) + { + S32 count = 0; + std::map::iterator iter; + for(iter = sFloaterMap.begin(); iter != sFloaterMap.end(); ++iter) + { + LLFloater* floater = iter->second; + if (!floater->isDead()) + { + iter->second->mButtonsEnabled[BUTTON_EDIT] = enable; + iter->second->updateButtons(); + } + count++; + } + } + + sEditModeEnabled = enable; +} + +//static +BOOL LLFloater::getEditModeEnabled() +{ + return sEditModeEnabled; +} + +// static +void LLFloater::onClickMinimize(void *userdata) +{ + LLFloater* self = (LLFloater*) userdata; + if (!self) return; + + self->setMinimized( !self->isMinimized() ); +} + +void LLFloater::onClickTearOff(void *userdata) +{ + LLFloater* self = (LLFloater*) userdata; + if (!self) return; + + LLMultiFloater* host_floater = self->getHost(); + if (host_floater) //Tear off + { + LLRect new_rect; + host_floater->removeFloater(self); + // reparent to floater view + gFloaterView->addChild(self); + + new_rect.setLeftTopAndSize(host_floater->getRect().mLeft + 5, host_floater->getRect().mTop - LLFLOATER_HEADER_SIZE - 5, self->mRect.getWidth(), self->mRect.getHeight()); + + self->open(); + self->setRect(new_rect); + gFloaterView->adjustToFitScreen(self, FALSE); + self->setCanDrag(TRUE); + self->setCanResize(TRUE); + self->setCanMinimize(TRUE); + } + else //Attach to parent. + { + LLMultiFloater* new_host = (LLMultiFloater*)LLFloater::getFloaterByHandle(self->mLastHostHandle); + if (new_host) + { + new_host->showFloater(self); + } + } +} + +// static +void LLFloater::onClickEdit(void *userdata) +{ + LLFloater* self = (LLFloater*) userdata; + if (!self) return; + + self->mEditing = self->mEditing ? FALSE : TRUE; +} + +// static +void LLFloater::closeByMenu( void* userdata ) +{ + LLFloater* self = (LLFloater*) userdata; + if (!self || self->getHost()) return; + + LLFloaterView* parent = (LLFloaterView*) self->getParent(); + + // grab focus status before close just in case floater is deleted + BOOL has_focus = gFocusMgr.childHasKeyboardFocus(self); + self->close(); + + // if this floater used to have focus and now nothing took focus + // give it to next floater (to allow closing multiple windows via keyboard in rapid succession) + if (has_focus && gFocusMgr.getKeyboardFocus() == NULL) + { + parent->focusFrontFloater(); + } + +} + + +// static +void LLFloater::onClickClose( void* userdata ) +{ + LLFloater* self = (LLFloater*) userdata; + if (!self) return; + + self->close(); +} + + +// virtual +void LLFloater::draw() +{ + if( getVisible() ) + { + // draw background + if( mBgVisible ) + { + S32 left = LLPANEL_BORDER_WIDTH; + S32 top = mRect.getHeight() - LLPANEL_BORDER_WIDTH; + S32 right = mRect.getWidth() - LLPANEL_BORDER_WIDTH; + S32 bottom = LLPANEL_BORDER_WIDTH; + + LLColor4 shadow_color = LLUI::sColorsGroup->getColor("ColorDropShadow"); + F32 shadow_offset = (F32)LLUI::sConfigGroup->getS32("DropShadowFloater"); + if (!mBgOpaque) + { + shadow_offset *= 0.2f; + shadow_color.mV[VALPHA] *= 0.5f; + } + gl_drop_shadow(left, top, right, bottom, + shadow_color, + llround(shadow_offset)); + + // No transparent windows in simple UI + if (mBgOpaque) + { + gl_rect_2d( left, top, right, bottom, mBgColorOpaque ); + } + else + { + gl_rect_2d( left, top, right, bottom, mBgColorAlpha ); + } + + if(gFocusMgr.childHasKeyboardFocus(this) && !getIsChrome() && !getTitle().empty()) + { + // draw highlight on title bar to indicate focus. RDW + const LLFontGL* font = gResMgr->getRes( LLFONT_SANSSERIF ); + LLRect r = getRect(); + gl_rect_2d_offset_local(0, r.getHeight(), r.getWidth(), r.getHeight() - (S32)font->getLineHeight() - 1, + LLUI::sColorsGroup->getColor("TitleBarFocusColor"), 0, TRUE); + } + } + + if( mDefaultBtn) + { + if (gFocusMgr.childHasKeyboardFocus( this ) && mDefaultBtn->getEnabled()) + { + LLUICtrl* focus_ctrl = gFocusMgr.getKeyboardFocus(); + // is this button a direct descendent and not a nested widget (e.g. checkbox)? + BOOL focus_is_child_button = focus_ctrl->getWidgetType() == WIDGET_TYPE_BUTTON && focus_ctrl->getParent() == this; + // only enable default button when current focus is not a button + mDefaultBtn->setBorderEnabled(!focus_is_child_button); + } + else + { + mDefaultBtn->setBorderEnabled(FALSE); + } + } + + // draw children + LLView* focused_child = gFocusMgr.getKeyboardFocus(); + BOOL focused_child_visible = FALSE; + if (focused_child && focused_child->getParent() == this) + { + focused_child_visible = focused_child->getVisible(); + focused_child->setVisible(FALSE); + } + + LLView::draw(); + + if( mBgVisible ) + { + // add in a border to improve spacialized visual aclarity ;) + // use lines instead of gl_rect_2d so we can round the edges as per james' recommendation + LLUI::setLineWidth(1.5f); + LLColor4 outlineColor = gFocusMgr.childHasKeyboardFocus(this) ? LLUI::sColorsGroup->getColor("FloaterFocusBorderColor") : LLUI::sColorsGroup->getColor("FloaterUnfocusBorderColor"); + gl_rect_2d_offset_local(0, mRect.getHeight() + 1, mRect.getWidth() + 1, 0, outlineColor, -LLPANEL_BORDER_WIDTH, FALSE); + LLUI::setLineWidth(1.f); + } + + if (focused_child_visible) + { + focused_child->setVisible(TRUE); + } + drawChild(focused_child); + } +} + +// virtual +void LLFloater::onClose(bool app_quitting) +{ + destroy(); +} + +// virtual +BOOL LLFloater::canClose() +{ + return TRUE; +} + +// virtual +BOOL LLFloater::canSaveAs() +{ + return FALSE; +} + +// virtual +void LLFloater::saveAs() +{ +} + +void LLFloater::setCanMinimize(BOOL can_minimize) +{ + // removing minimize/restore button programmatically, + // go ahead and uniminimize floater + if (!can_minimize) + { + setMinimized(FALSE); + } + + if (can_minimize) + { + if (isMinimized()) + { + mButtonsEnabled[BUTTON_MINIMIZE] = FALSE; + mButtonsEnabled[BUTTON_RESTORE] = TRUE; + } + else + { + mButtonsEnabled[BUTTON_MINIMIZE] = TRUE; + mButtonsEnabled[BUTTON_RESTORE] = FALSE; + } + } + else + { + mButtonsEnabled[BUTTON_MINIMIZE] = FALSE; + mButtonsEnabled[BUTTON_RESTORE] = FALSE; + } + + updateButtons(); +} + +void LLFloater::setCanClose(BOOL can_close) +{ + mButtonsEnabled[BUTTON_CLOSE] = can_close; + + updateButtons(); +} + +void LLFloater::setCanTearOff(BOOL can_tear_off) +{ + mCanTearOff = can_tear_off; + mButtonsEnabled[BUTTON_TEAR_OFF] = mCanTearOff && !mHostHandle.isDead(); + + updateButtons(); +} + + +void LLFloater::setCanResize(BOOL can_resize) +{ + if (mResizable && !can_resize) + { + removeChild(mResizeBar[0]); + removeChild(mResizeBar[1]); + removeChild(mResizeBar[2]); + removeChild(mResizeBar[3]); + removeChild(mResizeHandle[0]); + removeChild(mResizeHandle[1]); + removeChild(mResizeHandle[2]); + removeChild(mResizeHandle[3]); + delete mResizeBar[0]; + delete mResizeBar[1]; + delete mResizeBar[2]; + delete mResizeBar[3]; + delete mResizeHandle[0]; + delete mResizeHandle[1]; + delete mResizeHandle[2]; + mResizeHandle[3] = NULL; + mResizeBar[0] = NULL; + mResizeBar[1] = NULL; + mResizeBar[2] = NULL; + mResizeBar[3] = NULL; + mResizeHandle[0] = NULL; + mResizeHandle[1] = NULL; + mResizeHandle[2] = NULL; + mResizeHandle[3] = NULL; + } + else if (!mResizable && can_resize) + { + // Resize bars (sides) + const S32 RESIZE_BAR_THICKNESS = 3; + mResizeBar[0] = new LLResizeBar( + "resizebar_left", + LLRect( 0, mRect.getHeight(), RESIZE_BAR_THICKNESS, 0), + mMinWidth, mMinHeight, LLResizeBar::LEFT ); + mResizeBar[0]->setSaveToXML(false); + addChild( mResizeBar[0] ); + + mResizeBar[1] = new LLResizeBar( + "resizebar_top", + LLRect( 0, mRect.getHeight(), mRect.getWidth(), mRect.getHeight() - RESIZE_BAR_THICKNESS), + mMinWidth, mMinHeight, LLResizeBar::TOP ); + mResizeBar[1]->setSaveToXML(false); + addChild( mResizeBar[1] ); + + mResizeBar[2] = new LLResizeBar( + "resizebar_right", + LLRect( mRect.getWidth() - RESIZE_BAR_THICKNESS, mRect.getHeight(), mRect.getWidth(), 0), + mMinWidth, mMinHeight, LLResizeBar::RIGHT ); + mResizeBar[2]->setSaveToXML(false); + addChild( mResizeBar[2] ); + + mResizeBar[3] = new LLResizeBar( + "resizebar_bottom", + LLRect( 0, RESIZE_BAR_THICKNESS, mRect.getWidth(), 0), + mMinWidth, mMinHeight, LLResizeBar::BOTTOM ); + mResizeBar[3]->setSaveToXML(false); + addChild( mResizeBar[3] ); + + + // Resize handles (corners) + mResizeHandle[0] = new LLResizeHandle( + "Resize Handle", + LLRect( mRect.getWidth() - RESIZE_HANDLE_WIDTH, RESIZE_HANDLE_HEIGHT, mRect.getWidth(), 0), + mMinWidth, + mMinHeight, + LLResizeHandle::RIGHT_BOTTOM); + mResizeHandle[0]->setSaveToXML(false); + addChild(mResizeHandle[0]); + + mResizeHandle[1] = new LLResizeHandle( "resize", + LLRect( mRect.getWidth() - RESIZE_HANDLE_WIDTH, mRect.getHeight(), mRect.getWidth(), mRect.getHeight() - RESIZE_HANDLE_HEIGHT), + mMinWidth, + mMinHeight, + LLResizeHandle::RIGHT_TOP ); + mResizeHandle[1]->setSaveToXML(false); + addChild(mResizeHandle[1]); + + mResizeHandle[2] = new LLResizeHandle( "resize", + LLRect( 0, RESIZE_HANDLE_HEIGHT, RESIZE_HANDLE_WIDTH, 0 ), + mMinWidth, + mMinHeight, + LLResizeHandle::LEFT_BOTTOM ); + mResizeHandle[2]->setSaveToXML(false); + addChild(mResizeHandle[2]); + + mResizeHandle[3] = new LLResizeHandle( "resize", + LLRect( 0, mRect.getHeight(), RESIZE_HANDLE_WIDTH, mRect.getHeight() - RESIZE_HANDLE_HEIGHT ), + mMinWidth, + mMinHeight, + LLResizeHandle::LEFT_TOP ); + mResizeHandle[3]->setSaveToXML(false); + addChild(mResizeHandle[3]); + } + mResizable = can_resize; +} + +void LLFloater::setCanDrag(BOOL can_drag) +{ + // if we delete drag handle, we no longer have access to the floater's title + // so just enable/disable it + if (!can_drag && mDragHandle->getEnabled()) + { + mDragHandle->setEnabled(FALSE); + } + else if (can_drag && !mDragHandle->getEnabled()) + { + mDragHandle->setEnabled(TRUE); + } +} + +void LLFloater::updateButtons() +{ + S32 button_count = 0; + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + if (mButtonsEnabled[i]) + { + button_count++; + + LLRect btn_rect; + if (mDragOnLeft) + { + btn_rect.setLeftTopAndSize( + LLPANEL_BORDER_WIDTH, + mRect.getHeight() - CLOSE_BOX_FROM_TOP - (LLFLOATER_CLOSE_BOX_SIZE + 1) * button_count, + llround((F32)LLFLOATER_CLOSE_BOX_SIZE * mButtonScale), + llround((F32)LLFLOATER_CLOSE_BOX_SIZE * mButtonScale)); + } + else + { + btn_rect.setLeftTopAndSize( + mRect.getWidth() - LLPANEL_BORDER_WIDTH - (LLFLOATER_CLOSE_BOX_SIZE + 1) * button_count, + mRect.getHeight() - CLOSE_BOX_FROM_TOP, + llround((F32)LLFLOATER_CLOSE_BOX_SIZE * mButtonScale), + llround((F32)LLFLOATER_CLOSE_BOX_SIZE * mButtonScale)); + } + + mButtons[i]->setRect(btn_rect); + mButtons[i]->setVisible(TRUE); + mButtons[i]->setEnabled(TRUE); + // the restore button should have a tab stop so that it takes action when you Ctrl-Tab to a minimized floater + mButtons[i]->setTabStop(i == BUTTON_RESTORE); + } + else if (mButtons[i]) + { + mButtons[i]->setVisible(FALSE); + mButtons[i]->setEnabled(FALSE); + } + } + + mDragHandle->setMaxTitleWidth(mRect.getWidth() - (button_count * (LLFLOATER_CLOSE_BOX_SIZE + 1))); +} + +void LLFloater::buildButtons() +{ + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + LLRect btn_rect; + if (mDragOnLeft) + { + btn_rect.setLeftTopAndSize( + LLPANEL_BORDER_WIDTH, + mRect.getHeight() - CLOSE_BOX_FROM_TOP - (LLFLOATER_CLOSE_BOX_SIZE + 1) * (i + 1), + llround(LLFLOATER_CLOSE_BOX_SIZE * mButtonScale), + llround(LLFLOATER_CLOSE_BOX_SIZE * mButtonScale)); + } + else + { + btn_rect.setLeftTopAndSize( + mRect.getWidth() - LLPANEL_BORDER_WIDTH - (LLFLOATER_CLOSE_BOX_SIZE + 1) * (i + 1), + mRect.getHeight() - CLOSE_BOX_FROM_TOP, + llround(LLFLOATER_CLOSE_BOX_SIZE * mButtonScale), + llround(LLFLOATER_CLOSE_BOX_SIZE * mButtonScale)); + } + + LLButton* buttonp = new LLButton( + sButtonNames[i], + btn_rect, + sButtonActiveImageNames[i], + sButtonPressedImageNames[i], + "", + sButtonCallbacks[i], + this, + LLFontGL::sSansSerif); + + buttonp->setTabStop(FALSE); + buttonp->setFollowsTop(); + buttonp->setFollowsRight(); + buttonp->setToolTip( sButtonToolTips[i] ); + buttonp->setImageColor(LLUI::sColorsGroup->getColor("FloaterButtonImageColor")); + buttonp->setHoverImages(sButtonPressedImageNames[i], + sButtonPressedImageNames[i]); + buttonp->setScaleImage(TRUE); + buttonp->setSaveToXML(false); + addChild(buttonp); + mButtons[i] = buttonp; + } + + updateButtons(); +} + +///////////////////////////////////////////////////// +// LLFloaterView + +LLFloaterView::LLFloaterView( const LLString& name, const LLRect& rect ) +: LLUICtrl( name, rect, FALSE, NULL, NULL, FOLLOWS_ALL ), + mFocusCycleMode(FALSE), + mSnapOffsetBottom(0) +{ + setTabStop(FALSE); + resetStartingFloaterPosition(); +} + +EWidgetType LLFloaterView::getWidgetType() const +{ + return WIDGET_TYPE_FLOATER_VIEW; +} + +LLString LLFloaterView::getWidgetTag() const +{ + return LL_FLOATER_VIEW_TAG; +} + +// By default, adjust vertical. +void LLFloaterView::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + reshape(width, height, called_from_parent, ADJUST_VERTICAL_YES); +} + +// When reshaping this view, make the floaters follow their closest edge. +void LLFloaterView::reshape(S32 width, S32 height, BOOL called_from_parent, BOOL adjust_vertical) +{ + S32 old_width = mRect.getWidth(); + S32 old_height = mRect.getHeight(); + + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + LLFloater* floaterp = (LLFloater*)viewp; + if (floaterp->isDependent()) + { + // dependents use same follow flags as their "dependee" + continue; + } + LLRect r = floaterp->getRect(); + + // Compute absolute distance from each edge of screen + S32 left_offset = llabs(r.mLeft - 0); + S32 right_offset = llabs(old_width - r.mRight); + + S32 top_offset = llabs(old_height - r.mTop); + S32 bottom_offset = llabs(r.mBottom - 0); + + // Make if follow the edge it is closest to + U32 follow_flags = 0x0; + + if (left_offset < right_offset) + { + follow_flags |= FOLLOWS_LEFT; + } + else + { + follow_flags |= FOLLOWS_RIGHT; + } + + // "No vertical adjustment" usually means that the bottom of the view + // has been pushed up or down. Hence we want the floaters to follow + // the top. + if (!adjust_vertical) + { + follow_flags |= FOLLOWS_TOP; + } + else if (top_offset < bottom_offset) + { + follow_flags |= FOLLOWS_TOP; + } + else + { + follow_flags |= FOLLOWS_BOTTOM; + } + + floaterp->setFollows(follow_flags); + + //RN: all dependent floaters copy follow behavior of "parent" + for(LLFloater::handle_set_iter_t dependent_it = floaterp->mDependents.begin(); + dependent_it != floaterp->mDependents.end(); ++dependent_it) + { + LLFloater* dependent_floaterp = getFloaterByHandle(*dependent_it); + if (dependent_floaterp) + { + dependent_floaterp->setFollows(follow_flags); + } + } + } + + LLView::reshape(width, height, called_from_parent); +} + + +void LLFloaterView::restoreAll() +{ + // make sure all subwindows aren't minimized + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLFloater* floaterp = (LLFloater*)*child_it; + floaterp->setMinimized(FALSE); + } + + //FIXME: make sure dependents are restored + + // children then deleted by default view constructor +} + + +void LLFloaterView::getNewFloaterPosition(S32* left,S32* top) +{ + // Workaround: mRect may change between when this object is created and the first time it is used. + static BOOL first = TRUE; + if( first ) + { + resetStartingFloaterPosition(); + first = FALSE; + } + + const S32 FLOATER_PAD = 16; + LLCoordWindow window_size; + getWindow()->getSize(&window_size); + LLRect full_window(0, window_size.mY, window_size.mX, 0); + LLRect floater_creation_rect( + 160, + full_window.getHeight() - 2 * MENU_BAR_HEIGHT, + full_window.getWidth() * 2 / 3, + 130 ); + floater_creation_rect.stretch( -FLOATER_PAD ); + + *left = mNextLeft; + *top = mNextTop; + + const S32 STEP = 25; + S32 bottom = floater_creation_rect.mBottom + 2 * STEP; + S32 right = floater_creation_rect.mRight - 4 * STEP; + + mNextTop -= STEP; + mNextLeft += STEP; + + if( (mNextTop < bottom ) || (mNextLeft > right) ) + { + mColumn++; + mNextTop = floater_creation_rect.mTop; + mNextLeft = STEP * mColumn; + + if( (mNextTop < bottom) || (mNextLeft > right) ) + { + // Advancing the column didn't work, so start back at the beginning + resetStartingFloaterPosition(); + } + } +} + +void LLFloaterView::resetStartingFloaterPosition() +{ + const S32 FLOATER_PAD = 16; + LLCoordWindow window_size; + getWindow()->getSize(&window_size); + LLRect full_window(0, window_size.mY, window_size.mX, 0); + LLRect floater_creation_rect( + 160, + full_window.getHeight() - 2 * MENU_BAR_HEIGHT, + full_window.getWidth() * 2 / 3, + 130 ); + floater_creation_rect.stretch( -FLOATER_PAD ); + + mNextLeft = floater_creation_rect.mLeft; + mNextTop = floater_creation_rect.mTop; + mColumn = 0; +} + +LLRect LLFloaterView::findNeighboringPosition( LLFloater* reference_floater, LLFloater* neighbor ) +{ + LLRect base_rect = reference_floater->getRect(); + S32 width = neighbor->getRect().getWidth(); + S32 height = neighbor->getRect().getHeight(); + LLRect new_rect = neighbor->getRect(); + + LLRect expanded_base_rect = base_rect; + expanded_base_rect.stretch(10); + for(LLFloater::handle_set_iter_t dependent_it = reference_floater->mDependents.begin(); + dependent_it != reference_floater->mDependents.end(); ++dependent_it) + { + LLFloater* sibling = LLFloater::getFloaterByHandle(*dependent_it); + // check for dependents within 10 pixels of base floater + if (sibling && + sibling != neighbor && + sibling->getVisible() && + expanded_base_rect.rectInRect(&sibling->getRect())) + { + base_rect |= sibling->getRect(); + } + } + + S32 left_margin = llmax(0, base_rect.mLeft); + S32 right_margin = llmax(0, mRect.getWidth() - base_rect.mRight); + S32 top_margin = llmax(0, mRect.getHeight() - base_rect.mTop); + S32 bottom_margin = llmax(0, base_rect.mBottom); + + // find position for floater in following order + // right->left->bottom->top + for (S32 i = 0; i < 5; i++) + { + if (right_margin > width) + { + new_rect.translate(base_rect.mRight - neighbor->mRect.mLeft, base_rect.mTop - neighbor->mRect.mTop); + return new_rect; + } + else if (left_margin > width) + { + new_rect.translate(base_rect.mLeft - neighbor->mRect.mRight, base_rect.mTop - neighbor->mRect.mTop); + return new_rect; + } + else if (bottom_margin > height) + { + new_rect.translate(base_rect.mLeft - neighbor->mRect.mLeft, base_rect.mBottom - neighbor->mRect.mTop); + return new_rect; + } + else if (top_margin > height) + { + new_rect.translate(base_rect.mLeft - neighbor->mRect.mLeft, base_rect.mTop - neighbor->mRect.mBottom); + return new_rect; + } + + // keep growing margins to find "best" fit + left_margin += 20; + right_margin += 20; + top_margin += 20; + bottom_margin += 20; + } + + // didn't find anything, return initial rect + return new_rect; +} + +void LLFloaterView::setCycleMode(BOOL mode) +{ + mFocusCycleMode = mode; +} + +BOOL LLFloaterView::getCycleMode() +{ + return mFocusCycleMode; +} + +void LLFloaterView::bringToFront(LLFloater* child, BOOL give_focus) +{ + //FIXME: make this respect floater's mAutoFocus value, instead of using parameter + if (child->getHost()) + { + // this floater is hosted elsewhere and hence not one of our children, abort + return; + } + std::vector floaters_to_move; + // Look at all floaters...tab + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + LLFloater *floater = (LLFloater *)viewp; + + // ...but if I'm a dependent floater... + if (child->isDependent()) + { + // ...look for floaters that have me as a dependent... + LLFloater::handle_set_iter_t found_dependent = floater->mDependents.find(child->getHandle()); + + if (found_dependent != floater->mDependents.end()) + { + // ...and make sure all children of that floater (including me) are brought to front... + for(LLFloater::handle_set_iter_t dependent_it = floater->mDependents.begin(); + dependent_it != floater->mDependents.end(); ) + { + LLFloater* sibling = LLFloater::getFloaterByHandle(*dependent_it); + if (sibling) + { + floaters_to_move.push_back(sibling); + } + ++dependent_it; + } + //...before bringing my parent to the front... + floaters_to_move.push_back(floater); + } + } + } + + std::vector::iterator view_it; + for(view_it = floaters_to_move.begin(); view_it != floaters_to_move.end(); ++view_it) + { + LLFloater* floaterp = (LLFloater*)(*view_it); + sendChildToFront(floaterp); + + floaterp->setMinimized(FALSE); + } + floaters_to_move.clear(); + + // ...then bringing my own dependents to the front... + for(LLFloater::handle_set_iter_t dependent_it = child->mDependents.begin(); + dependent_it != child->mDependents.end(); ) + { + LLFloater* dependent = getFloaterByHandle(*dependent_it); + if (dependent) + { + sendChildToFront(dependent); + dependent->setMinimized(FALSE); + } + ++dependent_it; + } + + // ...and finally bringing myself to front + // (do this last, so that I'm left in front at end of this call) + if( *getChildList()->begin() != child ) + { + sendChildToFront(child); + } + child->setMinimized(FALSE); + if (give_focus && !gFocusMgr.childHasKeyboardFocus(child)) + { + child->setFocus(TRUE); + } +} + +void LLFloaterView::highlightFocusedFloater() +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLFloater *floater = (LLFloater *)(*child_it); + + // skip dependent floaters, as we'll handle them in a batch along with their dependee(?) + if (floater->isDependent()) + { + continue; + } + + BOOL floater_or_dependent_has_focus = gFocusMgr.childHasKeyboardFocus(floater); + for(LLFloater::handle_set_iter_t dependent_it = floater->mDependents.begin(); + dependent_it != floater->mDependents.end(); + ++dependent_it) + { + LLFloater* dependent_floaterp = getFloaterByHandle(*dependent_it); + if (dependent_floaterp && gFocusMgr.childHasKeyboardFocus(dependent_floaterp)) + { + floater_or_dependent_has_focus = TRUE; + } + } + + // now set this floater and all its dependents + floater->setForeground(floater_or_dependent_has_focus); + + for(LLFloater::handle_set_iter_t dependent_it = floater->mDependents.begin(); + dependent_it != floater->mDependents.end(); ) + { + LLFloater* dependent_floaterp = getFloaterByHandle(*dependent_it); + if (dependent_floaterp) + { + dependent_floaterp->setForeground(floater_or_dependent_has_focus); + } + ++dependent_it; + } + + floater->cleanupHandles(); + } +} + +void LLFloaterView::unhighlightFocusedFloater() +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLFloater *floater = (LLFloater *)(*child_it); + + floater->setForeground(FALSE); + } +} + +void LLFloaterView::focusFrontFloater() +{ + LLFloater* floaterp = getFrontmost(); + if (floaterp) + { + floaterp->setFocus(TRUE); + } +} + +void LLFloaterView::getMinimizePosition(S32 *left, S32 *bottom) +{ + // count the number of minimized children + S32 count = 0; + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + LLFloater *floater = (LLFloater *)viewp; + if (floater->isMinimized()) + { + count++; + } + } + + // space over for that many and up if necessary + S32 tiles_per_row = mRect.getWidth() / MINIMIZED_WIDTH; + + *left = (count % tiles_per_row) * MINIMIZED_WIDTH; + *bottom = (count / tiles_per_row) * LLFLOATER_HEADER_SIZE; +} + + +void LLFloaterView::destroyAllChildren() +{ + LLView::deleteAllChildren(); +} + +void LLFloaterView::closeAllChildren(bool app_quitting) +{ + // iterate over a copy of the list, because closing windows will destroy + // some windows on the list. + child_list_t child_list = *(getChildList()); + + for (child_list_const_iter_t it = child_list.begin(); it != child_list.end(); ++it) + { + LLView* viewp = *it; + child_list_const_iter_t exists = std::find(getChildList()->begin(), getChildList()->end(), viewp); + if (exists == getChildList()->end()) + { + // this floater has already been removed + continue; + } + + LLFloater* floaterp = (LLFloater*)viewp; + + // Attempt to close floater. This will cause the "do you want to save" + // dialogs to appear. + if (floaterp->canClose()) + { + floaterp->close(app_quitting); + } + } +} + + +BOOL LLFloaterView::allChildrenClosed() +{ + // see if there are any visible floaters (some floaters "close" + // by setting themselves invisible) + S32 visible_count = 0; + for (child_list_const_iter_t it = getChildList()->begin(); it != getChildList()->end(); ++it) + { + LLView* viewp = *it; + LLFloater* floaterp = (LLFloater*)viewp; + + if (floaterp->getVisible() && floaterp->canClose()) + { + visible_count++; + } + } + + return (visible_count == 0); +} + + +void LLFloaterView::refresh() +{ + // Constrain children to be entirely on the screen + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLFloater* floaterp = (LLFloater*)*child_it; + if( floaterp->getVisible() ) + { + adjustToFitScreen(floaterp, TRUE); + } + } +} + +void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_outside) +{ + if (floater->getParent() != this) + { + // floater is hosted elsewhere, so ignore + return; + } + S32 screen_width = getSnapRect().getWidth(); + S32 screen_height = getSnapRect().getHeight(); + // convert to local coordinate frame + LLRect snap_rect_local = getSnapRect(); + snap_rect_local.translate(-mRect.mLeft, -mRect.mBottom); + + if( floater->isResizable() ) + { + LLRect view_rect = floater->getRect(); + S32 view_width = view_rect.getWidth(); + S32 view_height = view_rect.getHeight(); + S32 min_width; + S32 min_height; + floater->getResizeLimits( &min_width, &min_height ); + + S32 new_width = llmax( min_width, view_width ); + S32 new_height = llmax( min_height, view_height ); + + if( (new_width > screen_width) || (new_height > screen_height) ) + { + new_width = llmin(new_width, screen_width); + new_height = llmin(new_height, screen_height); + + floater->reshape( new_width, new_height, TRUE ); + + // Make sure the damn thing is actually onscreen. + if (floater->translateIntoRect(snap_rect_local, FALSE)) + { + floater->clearSnapTarget(); + } + } + else if (!floater->isMinimized()) + { + floater->reshape(new_width, new_height, TRUE); + } + } + + if (floater->translateIntoRect( snap_rect_local, allow_partial_outside )) + { + floater->clearSnapTarget(); + } +} + +void LLFloaterView::draw() +{ + if( getVisible() ) + { + refresh(); + + // hide focused floater if in cycle mode, so that it can be drawn on top + LLFloater* focused_floater = getFocusedFloater(); + BOOL floater_visible = FALSE; + if (mFocusCycleMode && focused_floater) + { + floater_visible = focused_floater->getVisible(); + focused_floater->setVisible(FALSE); + } + + // And actually do the draw + LLView::draw(); + + // manually draw focused floater on top when in cycle mode + if (mFocusCycleMode && focused_floater) + { + // draw focused item on top for better feedback + focused_floater->setVisible(floater_visible); + if (floater_visible) + { + drawChild(focused_floater); + } + } + } +} + +const LLRect LLFloaterView::getSnapRect() const +{ + LLRect snap_rect = mRect; + snap_rect.mBottom += mSnapOffsetBottom; + + return snap_rect; +} + +LLFloater *LLFloaterView::getFocusedFloater() +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLUICtrl* ctrlp = (*child_it)->isCtrl() ? static_cast(*child_it) : NULL; + if ( ctrlp && ctrlp->hasFocus() ) + { + return static_cast(ctrlp); + } + } + return NULL; +} + +LLFloater *LLFloaterView::getFrontmost() +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if ( viewp->getVisible() ) + { + return (LLFloater *)viewp; + } + } + return NULL; +} + +LLFloater *LLFloaterView::getBackmost() +{ + LLFloater* back_most = NULL; + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if ( viewp->getVisible() ) + { + back_most = (LLFloater *)viewp; + } + } + return back_most; +} + +void LLFloaterView::syncFloaterTabOrder() +{ + // bring focused floater to front + for ( child_list_const_reverse_iter_t child_it = getChildList()->rbegin(); child_it != getChildList()->rend(); ++child_it) + { + LLFloater* floaterp = (LLFloater*)*child_it; + if (gFocusMgr.childHasKeyboardFocus(floaterp)) + { + bringToFront(floaterp, FALSE); + break; + } + } + + // then sync draw order to tab order + for ( child_list_const_reverse_iter_t child_it = getChildList()->rbegin(); child_it != getChildList()->rend(); ++child_it) + { + LLFloater* floaterp = (LLFloater*)*child_it; + moveChildToFrontOfTabGroup(floaterp); + } +} + +LLFloater* LLFloaterView::getFloaterByHandle(LLViewHandle handle) +{ + if (handle == LLViewHandle::sDeadHandle) + { + return NULL; + } + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if (((LLFloater*)viewp)->getHandle() == handle) + { + return (LLFloater*)viewp; + } + } + return NULL; +} + +LLFloater* LLFloaterView::getParentFloater(LLView* viewp) +{ + LLView* parentp = viewp->getParent(); + + while(parentp && parentp != this) + { + viewp = parentp; + parentp = parentp->getParent(); + } + + if (parentp == this) + { + return (LLFloater*)viewp; + } + + return NULL; +} + +S32 LLFloaterView::getZOrder(LLFloater* child) +{ + S32 rv = 0; + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if(viewp == child) + { + break; + } + ++rv; + } + return rv; +} + +void LLFloaterView::pushVisibleAll(BOOL visible, const skip_list_t& skip_list) +{ + for (child_list_const_iter_t child_iter = getChildList()->begin(); + child_iter != getChildList()->end(); ++child_iter) + { + LLView *view = *child_iter; + if (skip_list.find(view) == skip_list.end()) + { + view->pushVisible(visible); + } + } +} + +void LLFloaterView::popVisibleAll(const skip_list_t& skip_list) +{ + for (child_list_const_iter_t child_iter = getChildList()->begin(); + child_iter != getChildList()->end(); ++child_iter) + { + LLView *view = *child_iter; + if (skip_list.find(view) == skip_list.end()) + { + view->popVisible(); + } + } +} + +// +// LLMultiFloater +// + +LLMultiFloater::LLMultiFloater() : + mTabContainer(NULL), + mTabPos(LLTabContainerCommon::TOP), + mAutoResize(FALSE) +{ + +} + +LLMultiFloater::LLMultiFloater(LLTabContainerCommon::TabPosition tab_pos) : + mTabContainer(NULL), + mTabPos(tab_pos), + mAutoResize(FALSE) +{ + +} + +LLMultiFloater::LLMultiFloater(const LLString &name) : + LLFloater(name), + mTabContainer(NULL), + mTabPos(LLTabContainerCommon::TOP), + mAutoResize(FALSE) +{ +} + +LLMultiFloater::LLMultiFloater( + const LLString& name, + const LLRect& rect, + LLTabContainer::TabPosition tab_pos, + BOOL auto_resize) : + LLFloater(name, rect, name), + mTabContainer(NULL), + mTabPos(LLTabContainerCommon::TOP), + mAutoResize(auto_resize) +{ + mTabContainer = new LLTabContainer("Preview Tabs", + LLRect(LLPANEL_BORDER_WIDTH, mRect.getHeight() - LLFLOATER_HEADER_SIZE, mRect.getWidth() - LLPANEL_BORDER_WIDTH, 0), + mTabPos, + NULL, + NULL); + mTabContainer->setFollowsAll(); + if (mResizable) + { + mTabContainer->setRightTabBtnOffset(RESIZE_HANDLE_WIDTH); + } + + addChild(mTabContainer); +} + +LLMultiFloater::LLMultiFloater( + const LLString& name, + const LLString& rect_control, + LLTabContainer::TabPosition tab_pos, + BOOL auto_resize) : + LLFloater(name, rect_control, name), + mTabContainer(NULL), + mTabPos(tab_pos), + mAutoResize(auto_resize) +{ + mTabContainer = new LLTabContainer("Preview Tabs", + LLRect(LLPANEL_BORDER_WIDTH, mRect.getHeight() - LLFLOATER_HEADER_SIZE, mRect.getWidth() - LLPANEL_BORDER_WIDTH, 0), + mTabPos, + NULL, + NULL); + mTabContainer->setFollowsAll(); + if (mResizable && mTabPos == LLTabContainerCommon::BOTTOM) + { + mTabContainer->setRightTabBtnOffset(RESIZE_HANDLE_WIDTH); + } + + addChild(mTabContainer); + +} + +LLMultiFloater::~LLMultiFloater() +{ +} + +// virtual +EWidgetType LLMultiFloater::getWidgetType() const +{ + return WIDGET_TYPE_MULTI_FLOATER; +} + +// virtual +LLString LLMultiFloater::getWidgetTag() const +{ + return LL_MULTI_FLOATER_TAG; +} + +void LLMultiFloater::init(const LLString& title, BOOL resizable, + S32 min_width, S32 min_height, BOOL drag_on_left, + BOOL minimizable, BOOL close_btn) +{ + LLFloater::init(title, resizable, min_width, min_height, drag_on_left, minimizable, close_btn); + + /*mTabContainer = new LLTabContainer("Preview Tabs", + LLRect(LLPANEL_BORDER_WIDTH, mRect.getHeight() - LLFLOATER_HEADER_SIZE, mRect.getWidth() - LLPANEL_BORDER_WIDTH, 0), + mTabPos, + NULL, + NULL); + mTabContainer->setFollowsAll(); + if (mResizable && mTabPos == LLTabContainerCommon::BOTTOM) + { + mTabContainer->setRightTabBtnOffset(RESIZE_HANDLE_WIDTH); + } + + addChild(mTabContainer);*/ +} + +void LLMultiFloater::open() +{ + if (mTabContainer->getTabCount() > 0) + { + LLFloater::open(); + } + else + { + // for now, don't allow multifloaters + // without any child floaters + close(); + } +} + +void LLMultiFloater::onClose(bool app_quitting) +{ + if(closeAllFloaters() == TRUE) + { + LLFloater::onClose(app_quitting ? true : false); + }//else not all tabs could be closed... +} + +void LLMultiFloater::draw() +{ + if (mTabContainer->getTabCount() == 0) + { + //RN: could this potentially crash in draw hierarchy? + close(); + } + else + { + for (S32 i = 0; i < mTabContainer->getTabCount(); i++) + { + LLFloater* floaterp = (LLFloater*)mTabContainer->getPanelByIndex(i); + if (floaterp->getTitle() != mTabContainer->getPanelTitle(i)) + { + mTabContainer->setPanelTitle(i, floaterp->getTitle()); + } + } + LLFloater::draw(); + } +} + +BOOL LLMultiFloater::closeAllFloaters() +{ + S32 tabToClose = 0; + S32 lastTabCount = mTabContainer->getTabCount(); + while (tabToClose < mTabContainer->getTabCount()) + { + LLFloater* first_floater = (LLFloater*)mTabContainer->getPanelByIndex(tabToClose); + first_floater->close(); + if(lastTabCount == mTabContainer->getTabCount()) + { + //Tab did not actually close, possibly due to a pending Save Confirmation dialog.. + //so try and close the next one in the list... + tabToClose++; + }else + { + //Tab closed ok. + lastTabCount = mTabContainer->getTabCount(); + } + } + if( mTabContainer->getTabCount() != 0 ) + return FALSE; // Couldn't close all the tabs (pending save dialog?) so return FALSE. + return TRUE; //else all tabs were successfully closed... +} + +void LLMultiFloater::growToFit(LLFloater* floaterp, S32 width, S32 height) +{ + floater_data_map_t::iterator found_data_it; + found_data_it = mFloaterDataMap.find(floaterp->getHandle()); + if (found_data_it != mFloaterDataMap.end()) + { + // store new width and height with this floater so that it will keep its size when detached + found_data_it->second.mWidth = width; + found_data_it->second.mHeight = height; + + S32 cur_height = mRect.getHeight(); + reshape(llmax(mRect.getWidth(), width + LLPANEL_BORDER_WIDTH * 2), llmax(mRect.getHeight(), height + LLFLOATER_HEADER_SIZE + TABCNTR_HEADER_HEIGHT + (LLPANEL_BORDER_WIDTH * 2))); + + // make sure upper left corner doesn't move + translate(0, mRect.getHeight() - cur_height); + + // Try to keep whole view onscreen, don't allow partial offscreen. + gFloaterView->adjustToFitScreen(this, FALSE); + } +} + +/** + void addFloater(LLFloater* floaterp, BOOL select_added_floater) + + Adds the LLFloater pointed to by floaterp to this. + If floaterp is already hosted by this, then it is re-added to get + new titles, etc. + If select_added_floater is true, the LLFloater pointed to by floaterp will + become the selected tab in this + + Affects: mTabContainer, floaterp +**/ +void LLMultiFloater::addFloater(LLFloater* floaterp, BOOL select_added_floater, LLTabContainer::eInsertionPoint insertion_point) +{ + if (!floaterp) + { + return; + } + + if (!mTabContainer) + { + llerrs << "Tab Container used without having been initialized." << llendl; + } + + if (floaterp->getHost() == this) + { + // already hosted by me, remove + // do this so we get updated title, etc. + mFloaterDataMap.erase(floaterp->getHandle()); + mTabContainer->removeTabPanel(floaterp); + } + else if (floaterp->getHost()) + { + // floaterp is hosted by somebody else and + // this is adding it, so remove it from it's old host + floaterp->getHost()->removeFloater(floaterp); + } + else if (floaterp->getParent() == gFloaterView) + { + // rehost preview floater as child panel + gFloaterView->removeChild(floaterp); + } + + // store original configuration + LLFloaterData floater_data; + floater_data.mWidth = floaterp->getRect().getWidth(); + floater_data.mHeight = floaterp->getRect().getHeight(); + floater_data.mCanMinimize = floaterp->isMinimizeable(); + floater_data.mCanResize = floaterp->isResizable(); + + // remove minimize and close buttons + floaterp->setCanMinimize(FALSE); + floaterp->setCanResize(FALSE); + floaterp->setCanDrag(FALSE); + + S32 new_width = llmax(mRect.getWidth(), floaterp->getRect().getWidth()); + S32 new_height = llmax(mRect.getHeight(), floaterp->getRect().getHeight() + LLFLOATER_HEADER_SIZE + TABCNTR_HEADER_HEIGHT); + + reshape(new_width, new_height); + + //add the panel, add it to proper maps + mTabContainer->addTabPanel(floaterp, floaterp->getTitle(), FALSE, onTabSelected, this, 0, FALSE, insertion_point); + mFloaterDataMap[floaterp->getHandle()] = floater_data; + + if ( select_added_floater ) + { + mTabContainer->selectLastTab(); + // explicitly call tabopen to load preview assets, etc. + tabOpen((LLFloater*)mTabContainer->getCurrentPanel(), true); + } + + floaterp->setHost(this); + if (mMinimized) + { + floaterp->setVisible(FALSE); + } +} + +/** + BOOL selectFloater(LLFloater* floaterp) + + If the LLFloater pointed to by floaterp is hosted by this, + then its tab is selected and returns true. Otherwise returns false. + + Affects: mTabContainer +**/ +BOOL LLMultiFloater::selectFloater(LLFloater* floaterp) +{ + return mTabContainer->selectTabPanel(floaterp); +} + +// virtual +void LLMultiFloater::selectNextFloater() +{ + mTabContainer->selectNextTab(); +} + +// virtual +void LLMultiFloater::selectPrevFloater() +{ + mTabContainer->selectPrevTab(); +} + +void LLMultiFloater::showFloater(LLFloater* floaterp) +{ + // we won't select a panel that already is selected + // it is hard to do this internally to tab container + // as tab selection is handled via index and the tab at a given + // index might have changed + if (floaterp != mTabContainer->getCurrentPanel() && + !mTabContainer->selectTabPanel(floaterp)) + { + addFloater(floaterp, TRUE); + } + setVisibleAndFrontmost(); +} + +void LLMultiFloater::removeFloater(LLFloater* floaterp) +{ + if ( floaterp->getHost() != this ) + return; + + floater_data_map_t::iterator found_data_it = mFloaterDataMap.find(floaterp->getHandle()); + if (found_data_it != mFloaterDataMap.end()) + { + LLFloaterData& floater_data = found_data_it->second; + floaterp->setCanMinimize(floater_data.mCanMinimize); + if (!floater_data.mCanResize) + { + // restore original size + floaterp->reshape(floater_data.mWidth, floater_data.mHeight); + } + floaterp->setCanResize(floater_data.mCanResize); + mFloaterDataMap.erase(found_data_it); + } + mTabContainer->removeTabPanel(floaterp); + floaterp->setBackgroundVisible(TRUE); + floaterp->setHost(NULL); + + if (mAutoResize) + { + floater_data_map_t::iterator floater_it; + S32 new_width = 0; + S32 new_height = 0; + for (floater_it = mFloaterDataMap.begin(); floater_it != mFloaterDataMap.end(); ++floater_it) + { + new_width = llmax(new_width, floater_it->second.mWidth + LLPANEL_BORDER_WIDTH * 2); + new_height = llmax(new_height, floater_it->second.mHeight + LLFLOATER_HEADER_SIZE + TABCNTR_HEADER_HEIGHT); + } + + S32 cur_height = mRect.getHeight(); + + reshape(new_width, new_height); + + // make sure upper left corner doesn't move + translate(0, cur_height - new_height); + + // Try to keep whole view onscreen, don't allow partial offscreen. + gFloaterView->adjustToFitScreen(this, FALSE); + } + + tabOpen((LLFloater*)mTabContainer->getCurrentPanel(), false); +} + +void LLMultiFloater::tabOpen(LLFloater* opened_floater, bool from_click) +{ + // default implementation does nothing +} + +void LLMultiFloater::tabClose() +{ + if (mTabContainer->getTabCount() == 0) + { + // no more children, close myself + close(); + } +} + +void LLMultiFloater::setVisible(BOOL visible) +{ + //FIXME: shouldn't have to do this, fix adding to minimized multifloater + LLFloater::setVisible(visible); + + if (mTabContainer) + { + LLPanel* cur_floaterp = mTabContainer->getCurrentPanel(); + + if (cur_floaterp) + { + cur_floaterp->setVisible(visible); + } + } +} + +BOOL LLMultiFloater::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + if (getEnabled() + && mask == (MASK_CONTROL|MASK_SHIFT)) + { + if (key == 'W') + { + LLFloater* floater = getActiveFloater(); + if (floater && floater->canClose()) + { + floater->close(); + } + return TRUE; + } + } + + return LLFloater::handleKeyHere(key, mask, called_from_parent); +} + +LLFloater* LLMultiFloater::getActiveFloater() +{ + return (LLFloater*)mTabContainer->getCurrentPanel(); +} + +S32 LLMultiFloater::getFloaterCount() +{ + return mTabContainer->getTabCount(); +} + +/** + BOOL isFloaterFlashing(LLFloater* floaterp) + + Returns true if the LLFloater pointed to by floaterp + is currently in a flashing state and is hosted by this. + False otherwise. + + Requires: floaterp != NULL +**/ +BOOL LLMultiFloater::isFloaterFlashing(LLFloater* floaterp) +{ + if ( floaterp && floaterp->getHost() == this ) + return mTabContainer->getTabPanelFlashing(floaterp); + + return FALSE; +} + +/** + BOOL setFloaterFlashing(LLFloater* floaterp, BOOL flashing) + + Sets the current flashing state of the LLFloater pointed + to by floaterp to be the BOOL flashing if the LLFloater pointed + to by floaterp is hosted by this. + + Requires: floaterp != NULL +**/ +void LLMultiFloater::setFloaterFlashing(LLFloater* floaterp, BOOL flashing) +{ + if ( floaterp && floaterp->getHost() == this ) + mTabContainer->setTabPanelFlashing(floaterp, flashing); +} + +//static +void LLMultiFloater::onTabSelected(void* userdata, bool from_click) +{ + LLMultiFloater* floaterp = (LLMultiFloater*)userdata; + + floaterp->tabOpen((LLFloater*)floaterp->mTabContainer->getCurrentPanel(), from_click); +} + +void LLMultiFloater::setCanResize(BOOL can_resize) +{ + LLFloater::setCanResize(can_resize); + if (mResizable && mTabContainer->getTabPosition() == LLTabContainer::BOTTOM) + { + mTabContainer->setRightTabBtnOffset(RESIZE_HANDLE_WIDTH); + } + else + { + mTabContainer->setRightTabBtnOffset(0); + } +} + +BOOL LLMultiFloater::postBuild() +{ + if (mTabContainer) + { + return TRUE; + } + + requires("Preview Tabs", WIDGET_TYPE_TAB_CONTAINER); + if (checkRequirements()) + { + mTabContainer = LLUICtrlFactory::getTabContainerByName(this, "Preview Tabs"); + return TRUE; + } + + return FALSE; +} + +// virtual +LLXMLNodePtr LLFloater::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLPanel::getXML(); + + node->createChild("title", TRUE)->setStringValue(getTitle()); + + node->createChild("can_resize", TRUE)->setBoolValue(isResizable()); + + node->createChild("can_minimize", TRUE)->setBoolValue(isMinimizeable()); + + node->createChild("can_close", TRUE)->setBoolValue(isCloseable()); + + node->createChild("can_drag_on_left", TRUE)->setBoolValue(isDragOnLeft()); + + node->createChild("min_width", TRUE)->setIntValue(getMinWidth()); + + node->createChild("min_height", TRUE)->setIntValue(getMinHeight()); + + node->createChild("can_tear_off", TRUE)->setBoolValue(mCanTearOff); + + return node; +} + +// static +LLView* LLFloater::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("floater"); + node->getAttributeString("name", name); + + LLFloater *floaterp = new LLFloater(name); + + LLString filename; + node->getAttributeString("filename", filename); + + if (filename.empty()) + { + // Load from node + floaterp->initFloaterXML(node, parent, factory); + } + else + { + // Load from file + factory->buildFloater(floaterp, filename); + } + + return floaterp; +} + +void LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory, BOOL open) +{ + LLString name(getName()); + LLString title(getTitle()); + LLString rect_control(""); + BOOL resizable = isResizable(); + S32 min_width = getMinWidth(); + S32 min_height = getMinHeight(); + BOOL drag_on_left = isDragOnLeft(); + BOOL minimizable = isMinimizeable(); + BOOL close_btn = isCloseable(); + LLRect rect; + + node->getAttributeString("name", name); + node->getAttributeString("title", title); + node->getAttributeString("rect_control", rect_control); + node->getAttributeBOOL("can_resize", resizable); + node->getAttributeBOOL("can_minimize", minimizable); + node->getAttributeBOOL("can_close", close_btn); + node->getAttributeBOOL("can_drag_on_left", drag_on_left); + node->getAttributeS32("min_width", min_width); + node->getAttributeS32("min_height", min_height); + + if (! rect_control.empty()) + { + setRectControl(rect_control); + } + + createRect(node, rect, parent, LLRect()); + + setRect(rect); + setName(name); + + init(title, + resizable, + min_width, + min_height, + drag_on_left, + minimizable, + close_btn); + + BOOL can_tear_off; + if (node->getAttributeBOOL("can_tear_off", can_tear_off)) + { + setCanTearOff(can_tear_off); + } + + initFromXML(node, parent); + + LLMultiFloater* last_host = LLFloater::getFloaterHost(); + if (node->hasName("multi_floater")) + { + LLFloater::setFloaterHost((LLMultiFloater*) this); + } + + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + factory->createWidget(this, child); + } + if (node->hasName("multi_floater")) + { + LLFloater::setFloaterHost(last_host); + } + + + BOOL result = postBuild(); + + if (!result) + { + llerrs << "Failed to construct floater " << name << llendl; + } + + applyRectControl(); + if (open) + { + this->open(); + } +} diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h new file mode 100644 index 0000000000..d682c7a36a --- /dev/null +++ b/indra/llui/llfloater.h @@ -0,0 +1,399 @@ +/** + * @file llfloater.h + * @brief LLFloater base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Floating "windows" within the GL display, like the inventory floater, +// mini-map floater, etc. + + +#ifndef LL_FLOATER_H +#define LL_FLOATER_H + +#include "llpanel.h" +#include "lluuid.h" +#include "lltabcontainer.h" +#include + +class LLDragHandle; +class LLResizeHandle; +class LLResizeBar; +class LLButton; +class LLMultiFloater; + +const S32 LLFLOATER_VPAD = 6; +const S32 LLFLOATER_HPAD = 6; +const S32 LLFLOATER_CLOSE_BOX_SIZE = 16; +const S32 LLFLOATER_HEADER_SIZE = 16; + +const BOOL RESIZE_YES = TRUE; +const BOOL RESIZE_NO = FALSE; + +const S32 DEFAULT_MIN_WIDTH = 100; +const S32 DEFAULT_MIN_HEIGHT = 100; + +const BOOL DRAG_ON_TOP = FALSE; +const BOOL DRAG_ON_LEFT = TRUE; + +const BOOL MINIMIZE_YES = TRUE; +const BOOL MINIMIZE_NO = FALSE; + +const BOOL CLOSE_YES = TRUE; +const BOOL CLOSE_NO = FALSE; + +const BOOL ADJUST_VERTICAL_YES = TRUE; +const BOOL ADJUST_VERTICAL_NO = FALSE; + + +class LLFloater : public LLPanel +{ +friend class LLFloaterView; +public: + enum EFloaterButtons + { + BUTTON_CLOSE, + BUTTON_RESTORE, + BUTTON_MINIMIZE, + BUTTON_TEAR_OFF, + BUTTON_EDIT, + BUTTON_COUNT + }; + + LLFloater(); + LLFloater(const LLString& name); //simple constructor for data-driven initialization + LLFloater( const LLString& name, const LLRect& rect, const LLString& title, + BOOL resizable = FALSE, + S32 min_width = DEFAULT_MIN_WIDTH, + S32 min_height = DEFAULT_MIN_HEIGHT, + BOOL drag_on_left = FALSE, + BOOL minimizable = TRUE, + BOOL close_btn = TRUE, + BOOL bordered = BORDER_NO); + + LLFloater( const LLString& name, const LLString& rect_control, const LLString& title, + BOOL resizable = FALSE, + S32 min_width = DEFAULT_MIN_WIDTH, + S32 min_height = DEFAULT_MIN_HEIGHT, + BOOL drag_on_left = FALSE, + BOOL minimizable = TRUE, + BOOL close_btn = TRUE, + BOOL bordered = BORDER_NO); + + virtual ~LLFloater(); + + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + void initFloaterXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory, BOOL open = TRUE); + + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = 1); + /*virtual*/ void translate(S32 x, S32 y); + /*virtual*/ BOOL canSnapTo(LLView* other_view); + /*virtual*/ void snappedTo(LLView* snap_view); + /*virtual*/ void setFocus( BOOL b ); + /*virtual*/ void setIsChrome(BOOL is_chrome); + + // Can be called multiple times to reset floater parameters. + // Deletes all children of the floater. + virtual void init(const LLString& title, BOOL resizable, + S32 min_width, S32 min_height, BOOL drag_on_left, + BOOL minimizable, BOOL close_btn); + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual void open(); + + // If allowed, close the floater cleanly, releasing focus. + // app_quitting is passed to onClose() below. + virtual void close(bool app_quitting = false); + + void setAutoFocus(BOOL focus) { mAutoFocus = focus; setFocus(focus); } + + // Release keyboard and mouse focus + void releaseFocus(); + + // moves to center of gFloaterView + void center(); + // applies rectangle stored in mRectControl, if any + void applyRectControl(); + + + LLMultiFloater* getHost() { return (LLMultiFloater*)LLFloater::getFloaterByHandle(mHostHandle); } + + void setTitle( const LLString& title ); + const LLString& getTitle() const; + virtual void setMinimized(BOOL b); + void moveResizeHandleToFront(); + void addDependentFloater(LLFloater* dependent, BOOL reposition = TRUE); + void addDependentFloater(LLViewHandle dependent_handle, BOOL reposition = TRUE); + LLFloater* getDependee() { return (LLFloater*)LLFloater::getFloaterByHandle(mDependeeHandle); } + void removeDependentFloater(LLFloater* dependent); + BOOL isMinimized() { return mMinimized; } + BOOL isFrontmost(); + BOOL isDependent() { return !mDependeeHandle.isDead(); } + void setCanMinimize(BOOL can_minimize); + void setCanClose(BOOL can_close); + void setCanTearOff(BOOL can_tear_off); + virtual void setCanResize(BOOL can_resize); + void setCanDrag(BOOL can_drag); + void setHost(LLMultiFloater* host); + BOOL isResizable() const { return mResizable; } + void setResizeLimits( S32 min_width, S32 min_height ); + void getResizeLimits( S32* min_width, S32* min_height ) { *min_width = mMinWidth; *min_height = mMinHeight; } + + + bool isMinimizeable() const{ return mButtonsEnabled[BUTTON_MINIMIZE]; } + // Does this window have a close button, NOT can we close it right now. + bool isCloseable() const{ return (mButtonsEnabled[BUTTON_CLOSE] ? true : false); } + bool isDragOnLeft() const{ return mDragOnLeft; } + S32 getMinWidth() const{ return mMinWidth; } + S32 getMinHeight() const{ return mMinHeight; } + + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); + + virtual void draw(); + + // Call destroy() to free memory, or setVisible(FALSE) to keep it + // If app_quitting, you might not want to save your visibility. + // Defaults to destroy(). + virtual void onClose(bool app_quitting); + + // Defaults to true. + virtual BOOL canClose(); + + virtual void setVisible(BOOL visible); + void setFrontmost(BOOL take_focus = TRUE); + + // Defaults to false. + virtual BOOL canSaveAs(); + + // Defaults to no-op. + virtual void saveAs(); + + void setSnapTarget(LLViewHandle handle) { mSnappedTo = handle; } + void clearSnapTarget() { mSnappedTo.markDead(); } + LLViewHandle getSnapTarget() { return mSnappedTo; } + + /*virtual*/ LLView* getRootMostFastFrameView(); + + static void closeByMenu(void *userdata); + static void onClickClose(void *userdata); + static void onClickMinimize(void *userdata); + static void onClickTearOff(void *userdata); + static void onClickEdit(void *userdata); + + static void setFloaterHost(LLMultiFloater* hostp) {sHostp = hostp; } + static void setEditModeEnabled(BOOL enable); + static BOOL getEditModeEnabled(); + static LLMultiFloater* getFloaterHost() {return sHostp; } + + static LLFloater* getFloaterByHandle(LLViewHandle handle); + +protected: + // Don't call this directly. You probably want to call close(). JC + void destroy(); + virtual void bringToFront(S32 x, S32 y); + virtual void setVisibleAndFrontmost(BOOL take_focus=TRUE); + void setForeground(BOOL b); // called only by floaterview + void cleanupHandles(); // remove handles to dead floaters + void createMinimizeButton(); + void updateButtons(); + void buildButtons(); + +protected: +// static LLViewerImage* sBackgroundImage; +// static LLViewerImage* sShadowImage; + + LLDragHandle* mDragHandle; + LLResizeBar* mResizeBar[4]; + LLResizeHandle* mResizeHandle[4]; + LLButton *mMinimizeButton; + BOOL mCanTearOff; + BOOL mMinimized; + LLRect mPreviousRect; + BOOL mForeground; + LLViewHandle mDependeeHandle; + + BOOL mFirstLook; // TRUE if the _next_ time this floater is visible will be the first time in the session that it is visible. + + BOOL mResizable; + S32 mMinWidth; + S32 mMinHeight; + + BOOL mAutoFocus; + BOOL mEditing; + + typedef std::set handle_set_t; + typedef std::set::iterator handle_set_iter_t; + handle_set_t mDependents; + bool mDragOnLeft; + + BOOL mButtonsEnabled[BUTTON_COUNT]; + LLButton* mButtons[BUTTON_COUNT]; + F32 mButtonScale; + + LLViewHandle mSnappedTo; + + LLViewHandle mHostHandle; + LLViewHandle mLastHostHandle; + + static BOOL sEditModeEnabled; + static LLMultiFloater* sHostp; + static LLString sButtonActiveImageNames[BUTTON_COUNT]; + static LLString sButtonInactiveImageNames[BUTTON_COUNT]; + static LLString sButtonPressedImageNames[BUTTON_COUNT]; + static LLString sButtonNames[BUTTON_COUNT]; + static LLString sButtonToolTips[BUTTON_COUNT]; + typedef void (*click_callback)(void *); + static click_callback sButtonCallbacks[BUTTON_COUNT]; + + typedef std::map handle_map_t; + typedef std::map::iterator handle_map_iter_t; + static handle_map_t sFloaterMap; + + std::vector mMinimizedHiddenChildren; +}; + + +///////////////////////////////////////////////////////////// +// LLFloaterView +// Parent of all floating panels + +class LLFloaterView : public LLUICtrl +{ +public: + LLFloaterView( const LLString& name, const LLRect& rect ); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent); + void reshape(S32 width, S32 height, BOOL called_from_parent, BOOL adjust_vertical); + + /*virtual*/ void draw(); + /*virtual*/ const LLRect getSnapRect() const; + void refresh(); + + void getNewFloaterPosition( S32* left, S32* top ); + void resetStartingFloaterPosition(); + LLRect findNeighboringPosition( LLFloater* reference_floater, LLFloater* neighbor ); + + // Given a child of gFloaterView, make sure this view can fit entirely onscreen. + void adjustToFitScreen(LLFloater* floater, BOOL allow_partial_outside); + + void getMinimizePosition( S32 *left, S32 *bottom); + void restoreAll(); // un-minimize all floaters + typedef std::set skip_list_t; + void LLFloaterView::pushVisibleAll(BOOL visible, const skip_list_t& skip_list = skip_list_t()); + void LLFloaterView::popVisibleAll(const skip_list_t& skip_list = skip_list_t()); + + void setCycleMode(BOOL mode); + BOOL getCycleMode(); + void bringToFront( LLFloater* child, BOOL give_focus = TRUE ); + void highlightFocusedFloater(); + void unhighlightFocusedFloater(); + void focusFrontFloater(); + void destroyAllChildren(); + // attempt to close all floaters + void closeAllChildren(bool app_quitting); + BOOL allChildrenClosed(); + + LLFloater* getFrontmost(); + LLFloater* getBackmost(); + LLFloater* getParentFloater(LLView* viewp); + LLFloater* getFocusedFloater(); + void syncFloaterTabOrder(); + + // Get a floater based the handle. If this returns NULL, it is up + // to the caller to discard the handle. + LLFloater* getFloaterByHandle(LLViewHandle handle); + + // Returns z order of child provided. 0 is closest, larger numbers + // are deeper in the screen. If there is no such child, the return + // value is not defined. + S32 getZOrder(LLFloater* child); + + void setSnapOffsetBottom(S32 offset) { mSnapOffsetBottom = offset; } + +private: + S32 mColumn; + S32 mNextLeft; + S32 mNextTop; + BOOL mFocusCycleMode; + S32 mSnapOffsetBottom; +}; + +class LLMultiFloater : public LLFloater +{ +public: + LLMultiFloater(); + LLMultiFloater(LLTabContainerCommon::TabPosition tab_pos); + LLMultiFloater(const LLString& name); + LLMultiFloater(const LLString& name, const LLRect& rect, LLTabContainer::TabPosition tab_pos = LLTabContainer::TOP, BOOL auto_resize = FALSE); + LLMultiFloater(const LLString& name, const LLString& rect_control, LLTabContainer::TabPosition tab_pos = LLTabContainer::TOP, BOOL auto_resize = FALSE); + virtual ~LLMultiFloater(); + + virtual void init(const LLString& title, BOOL resizable, + S32 min_width, S32 min_height, BOOL drag_on_left, + BOOL minimizable, BOOL close_btn); + + virtual BOOL postBuild(); + /*virtual*/ void open(); + /*virtual*/ void onClose(bool app_quitting); + /*virtual*/ void draw(); + /*virtual*/ void setVisible(BOOL visible); + /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + + /*virtual*/ EWidgetType getWidgetType() const; + /*virtual*/ LLString getWidgetTag() const; + + virtual void setCanResize(BOOL can_resize); + virtual void growToFit(LLFloater* floaterp, S32 width, S32 height); + virtual void addFloater(LLFloater* floaterp, BOOL select_added_floater, LLTabContainerCommon::eInsertionPoint insertion_point = LLTabContainerCommon::END); + + virtual void showFloater(LLFloater* floaterp); + virtual void removeFloater(LLFloater* floaterp); + + virtual void tabOpen(LLFloater* opened_floater, bool from_click); + virtual void tabClose(); + + virtual BOOL selectFloater(LLFloater* floaterp); + virtual void selectNextFloater(); + virtual void selectPrevFloater(); + + virtual LLFloater* getActiveFloater(); + virtual BOOL isFloaterFlashing(LLFloater* floaterp); + virtual S32 getFloaterCount(); + + virtual void setFloaterFlashing(LLFloater* floaterp, BOOL flashing); + virtual BOOL closeAllFloaters(); //Returns FALSE if the floater could not be closed due to pending confirmation dialogs + void setTabContainer(LLTabContainerCommon* tab_container) { if (!mTabContainer) mTabContainer = tab_container; } + static void onTabSelected(void* userdata, bool); + +protected: + struct LLFloaterData + { + S32 mWidth; + S32 mHeight; + BOOL mCanMinimize; + BOOL mCanResize; + }; + + LLTabContainerCommon* mTabContainer; + + typedef std::map floater_data_map_t; + floater_data_map_t mFloaterDataMap; + + LLTabContainerCommon::TabPosition mTabPos; + BOOL mAutoResize; +}; + + +extern LLFloaterView* gFloaterView; + +#endif // LL_FLOATER_H + diff --git a/indra/llui/llfocusmgr.cpp b/indra/llui/llfocusmgr.cpp new file mode 100644 index 0000000000..030fbf0653 --- /dev/null +++ b/indra/llui/llfocusmgr.cpp @@ -0,0 +1,369 @@ +/** + * @file llfocusmgr.cpp + * @brief LLFocusMgr base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llfocusmgr.h" +#include "lluictrl.h" +#include "v4color.h" + +const F32 FOCUS_FADE_TIME = 0.3f; + +LLFocusMgr gFocusMgr; + +LLFocusMgr::LLFocusMgr() + : + mLockedView( NULL ), + mKeyboardLockedFocusLostCallback( NULL ), + mMouseCaptor( NULL ), + mMouseCaptureLostCallback( NULL ), + mKeyboardFocus( NULL ), + mDefaultKeyboardFocus( NULL ), + mKeyboardFocusLostCallback( NULL ), + mTopView( NULL ), + mTopViewLostCallback( NULL ), + mFocusWeight(0.f), + mAppHasFocus(TRUE) // Macs don't seem to notify us that we've gotten focus, so default to true + #ifdef _DEBUG + , mMouseCaptorName("none") + , mKeyboardFocusName("none") + , mTopViewName("none") + #endif +{ +} + +LLFocusMgr::~LLFocusMgr() +{ + mFocusHistory.clear(); +} + +void LLFocusMgr::releaseFocusIfNeeded( LLView* view ) +{ + if( childHasMouseCapture( view ) ) + { + setMouseCapture( NULL, NULL ); + } + + if( childHasKeyboardFocus( view )) + { + if (view == mLockedView) + { + mLockedView = NULL; + mKeyboardLockedFocusLostCallback = NULL; + setKeyboardFocus( NULL, NULL ); + } + else + { + setKeyboardFocus( mLockedView, mKeyboardLockedFocusLostCallback ); + } + } + + if( childIsTopView( view ) ) + { + setTopView( NULL, NULL ); + } +} + + +void LLFocusMgr::setKeyboardFocus(LLUICtrl* new_focus, FocusLostCallback on_focus_lost, BOOL lock) +{ + if (mLockedView && + (new_focus == NULL || + (new_focus != mLockedView && !new_focus->hasAncestor(mLockedView)))) + { + // don't allow focus to go to anything that is not the locked focus + // or one of its descendants + return; + } + FocusLostCallback old_callback = mKeyboardFocusLostCallback; + mKeyboardFocusLostCallback = on_focus_lost; + + //llinfos << "Keyboard focus handled by " << (new_focus ? new_focus->getName() : "nothing") << llendl; + + if( new_focus != mKeyboardFocus ) + { + LLUICtrl* old_focus = mKeyboardFocus; + mKeyboardFocus = new_focus; + + // clear out any existing flash + if (new_focus) + { + mFocusWeight = 0.f; + } + mFocusTimer.reset(); + + if( old_callback ) + { + old_callback( old_focus ); + } + + #ifdef _DEBUG + mKeyboardFocusName = new_focus ? new_focus->getName() : "none"; + #endif + + // If we've got a default keyboard focus, and the caller is + // releasing keyboard focus, move to the default. + if (mDefaultKeyboardFocus != NULL && new_focus == NULL) + { + mDefaultKeyboardFocus->setFocus(TRUE); + } + + LLView* focus_subtree = new_focus; + LLView* viewp = new_focus; + // find root-most focus root + while(viewp) + { + if (viewp->isFocusRoot()) + { + focus_subtree = viewp; + } + viewp = viewp->getParent(); + } + + + if (focus_subtree) + { + mFocusHistory[focus_subtree->mViewHandle] = new_focus ? new_focus->mViewHandle : LLViewHandle::sDeadHandle; + } + } + + if (lock) + { + mLockedView = new_focus; + mKeyboardLockedFocusLostCallback = on_focus_lost; + } +} + +void LLFocusMgr::setDefaultKeyboardFocus(LLUICtrl* default_focus) +{ + mDefaultKeyboardFocus = default_focus; +} + +// Returns TRUE is parent or any descedent of parent has keyboard focus. +BOOL LLFocusMgr::childHasKeyboardFocus(const LLView* parent ) const +{ + LLView* focus_view = mKeyboardFocus; + while( focus_view ) + { + if( focus_view == parent ) + { + return TRUE; + } + focus_view = focus_view->getParent(); + } + return FALSE; +} + +// Returns TRUE is parent or any descedent of parent is the mouse captor. +BOOL LLFocusMgr::childHasMouseCapture( LLView* parent ) +{ + if( mMouseCaptor && mMouseCaptor->isView() ) + { + LLView* captor_view = (LLView*)mMouseCaptor; + while( captor_view ) + { + if( captor_view == parent ) + { + return TRUE; + } + captor_view = captor_view->getParent(); + } + } + return FALSE; +} + +void LLFocusMgr::removeKeyboardFocusWithoutCallback( LLView* focus ) +{ + // should be ok to unlock here, as you have to know the locked view + // in order to unlock it + if (focus == mLockedView) + { + mLockedView = NULL; + mKeyboardLockedFocusLostCallback = NULL; + } + + if( mKeyboardFocus == focus ) + { + mKeyboardFocus = NULL; + mKeyboardFocusLostCallback = NULL; + #ifdef _DEBUG + mKeyboardFocusName = "none"; + #endif + } +} + + +void LLFocusMgr::setMouseCapture( LLMouseHandler* new_captor, void (*on_capture_lost)(LLMouseHandler* old_captor) ) +{ + //if (mFocusLocked) + //{ + // return; + //} + + void (*old_callback)(LLMouseHandler*) = mMouseCaptureLostCallback; + mMouseCaptureLostCallback = on_capture_lost; + + if( new_captor != mMouseCaptor ) + { + LLMouseHandler* old_captor = mMouseCaptor; + mMouseCaptor = new_captor; + /* + if (new_captor) + { + if ( new_captor->getName() == "Stickto") + { + llinfos << "New mouse captor: " << new_captor->getName() << llendl; + } + else + { + llinfos << "New mouse captor: " << new_captor->getName() << llendl; + } + } + else + { + llinfos << "New mouse captor: NULL" << llendl; + } + */ + + if( old_callback ) + { + old_callback( old_captor ); + } + + #ifdef _DEBUG + mMouseCaptorName = new_captor ? new_captor->getName() : "none"; + #endif + } +} + +void LLFocusMgr::removeMouseCaptureWithoutCallback( LLMouseHandler* captor ) +{ + //if (mFocusLocked) + //{ + // return; + //} + if( mMouseCaptor == captor ) + { + mMouseCaptor = NULL; + mMouseCaptureLostCallback = NULL; + #ifdef _DEBUG + mMouseCaptorName = "none"; + #endif + } +} + + +BOOL LLFocusMgr::childIsTopView( LLView* parent ) +{ + LLView* top_view = mTopView; + while( top_view ) + { + if( top_view == parent ) + { + return TRUE; + } + top_view = top_view->getParent(); + } + return FALSE; +} + + + +// set new_top = NULL to release top_view. +void LLFocusMgr::setTopView( LLView* new_top, void (*on_top_lost)(LLView* old_top) ) +{ + void (*old_callback)(LLView*) = mTopViewLostCallback; + mTopViewLostCallback = on_top_lost; + + if( new_top != mTopView ) + { + LLView* old_top = mTopView; + mTopView = new_top; + if( old_callback ) + { + old_callback( old_top ); + } + + mTopView = new_top; + + #ifdef _DEBUG + mTopViewName = new_top ? new_top->getName() : "none"; + #endif + } +} + +void LLFocusMgr::removeTopViewWithoutCallback( LLView* top_view ) +{ + if( mTopView == top_view ) + { + mTopView = NULL; + mTopViewLostCallback = NULL; + #ifdef _DEBUG + mTopViewName = "none"; + #endif + } +} + +void LLFocusMgr::unlockFocus() +{ + mLockedView = NULL; + mKeyboardLockedFocusLostCallback = NULL; +} + +F32 LLFocusMgr::getFocusFlashAmt() +{ + return clamp_rescale(getFocusTime(), 0.f, FOCUS_FADE_TIME, mFocusWeight, 0.f); +} + +LLColor4 LLFocusMgr::getFocusColor() +{ + LLColor4 focus_color = lerp(LLUI::sColorsGroup->getColor( "FocusColor" ), LLColor4::white, getFocusFlashAmt()); + // de-emphasize keyboard focus when app has lost focus (to avoid typing into wrong window problem) + if (!mAppHasFocus) + { + focus_color.mV[VALPHA] *= 0.4f; + } + return focus_color; +} + +void LLFocusMgr::triggerFocusFlash() +{ + mFocusTimer.reset(); + mFocusWeight = 1.f; +} + +void LLFocusMgr::setAppHasFocus(BOOL focus) +{ + if (!mAppHasFocus && focus) + { + triggerFocusFlash(); + } + mAppHasFocus = focus; +} + +LLUICtrl* LLFocusMgr::getLastFocusForGroup(LLView* subtree_root) +{ + if (subtree_root) + { + focus_history_map_t::iterator found_it = mFocusHistory.find(subtree_root->mViewHandle); + if (found_it != mFocusHistory.end()) + { + // found last focus for this subtree + return static_cast(LLView::getViewByHandle(found_it->second)); + } + } + return NULL; +} + +void LLFocusMgr::clearLastFocusForGroup(LLView* subtree_root) +{ + if (subtree_root) + { + mFocusHistory.erase(subtree_root->mViewHandle); + } +} diff --git a/indra/llui/llfocusmgr.h b/indra/llui/llfocusmgr.h new file mode 100644 index 0000000000..cb555fca91 --- /dev/null +++ b/indra/llui/llfocusmgr.h @@ -0,0 +1,102 @@ +/** + * @file llfocusmgr.h + * @brief LLFocusMgr base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Singleton that manages keyboard and mouse focus + +#ifndef LL_LLFOCUSMGR_H +#define LL_LLFOCUSMGR_H + +#include "llstring.h" +#include "llframetimer.h" +#include "llview.h" + +class LLUICtrl; +class LLMouseHandler; + +class LLFocusMgr +{ +public: + typedef void (*FocusLostCallback)(LLUICtrl*); + + LLFocusMgr(); + ~LLFocusMgr(); + + // Mouse Captor + void setMouseCapture(LLMouseHandler* new_captor,void (*on_capture_lost)(LLMouseHandler* old_captor)); // new_captor = NULL to release the mouse. + LLMouseHandler* getMouseCapture() { return mMouseCaptor; } + void removeMouseCaptureWithoutCallback( LLMouseHandler* captor ); + BOOL childHasMouseCapture( LLView* parent ); + + // Keyboard Focus + void setKeyboardFocus(LLUICtrl* new_focus, FocusLostCallback on_focus_lost, BOOL lock = FALSE); // new_focus = NULL to release the focus. + LLUICtrl* getKeyboardFocus() const { return mKeyboardFocus; } + BOOL childHasKeyboardFocus( const LLView* parent ) const; + void removeKeyboardFocusWithoutCallback( LLView* focus ); + FocusLostCallback getFocusCallback() { return mKeyboardFocusLostCallback; } + F32 getFocusTime() const { return mFocusTimer.getElapsedTimeF32(); } + F32 getFocusFlashAmt(); + LLColor4 getFocusColor(); + void triggerFocusFlash(); + BOOL getAppHasFocus() { return mAppHasFocus; } + void setAppHasFocus(BOOL focus); + LLUICtrl* getLastFocusForGroup(LLView* subtree_root); + void clearLastFocusForGroup(LLView* subtree_root); + + // If setKeyboardFocus(NULL) is called, and there is a non-NULL default + // keyboard focus view, focus goes there. JC + void setDefaultKeyboardFocus(LLUICtrl* default_focus); + LLUICtrl* getDefaultKeyboardFocus() const { return mDefaultKeyboardFocus; } + + + // Top View + void setTopView(LLView* new_top, void (*on_top_lost)(LLView* old_top)); + LLView* getTopView() const { return mTopView; } + void removeTopViewWithoutCallback( LLView* top_view ); + BOOL childIsTopView( LLView* parent ); + + // All Three + void releaseFocusIfNeeded( LLView* top_view ); + void unlockFocus(); + BOOL focusLocked() { return mLockedView != NULL; } + +protected: + LLUICtrl* mLockedView; + FocusLostCallback mKeyboardLockedFocusLostCallback; + + // Mouse Captor + LLMouseHandler* mMouseCaptor; // Mouse events are premptively routed to this object + void (*mMouseCaptureLostCallback)(LLMouseHandler*); // The object to which mouse events are routed is called before another object takes its place + + // Keyboard Focus + LLUICtrl* mKeyboardFocus; // Keyboard events are preemptively routed to this object + LLUICtrl* mDefaultKeyboardFocus; + FocusLostCallback mKeyboardFocusLostCallback; // The object to which keyboard events are routed is called before another object takes its place + + // Top View + LLView* mTopView; + void (*mTopViewLostCallback)(LLView*); + + LLFrameTimer mFocusTimer; + F32 mFocusWeight; + + BOOL mAppHasFocus; + + typedef std::map focus_history_map_t; + focus_history_map_t mFocusHistory; + + #ifdef _DEBUG + LLString mMouseCaptorName; + LLString mKeyboardFocusName; + LLString mTopViewName; + #endif +}; + +extern LLFocusMgr gFocusMgr; + +#endif // LL_LLFOCUSMGR_H + diff --git a/indra/llui/lliconctrl.cpp b/indra/llui/lliconctrl.cpp new file mode 100644 index 0000000000..006334fa4e --- /dev/null +++ b/indra/llui/lliconctrl.cpp @@ -0,0 +1,139 @@ +/** + * @file lliconctrl.cpp + * @brief LLIconCtrl base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lliconctrl.h" + +// Linden library includes + +// Project includes +#include "llcontrol.h" +#include "llui.h" +#include "lluictrlfactory.h" + +const F32 RESOLUTION_BUMP = 1.f; + +LLIconCtrl::LLIconCtrl(const LLString& name, const LLRect &rect, const LLUUID &image_id) +: LLUICtrl(name, + rect, + FALSE, // mouse opaque + NULL, NULL, + FOLLOWS_LEFT | FOLLOWS_TOP), + mColor( LLColor4::white ), + mImageName("") +{ + setImage( image_id ); + setTabStop(FALSE); +} + +LLIconCtrl::LLIconCtrl(const LLString& name, const LLRect &rect, const LLString &image_name) +: LLUICtrl(name, + rect, + FALSE, // mouse opaque + NULL, NULL, + FOLLOWS_LEFT | FOLLOWS_TOP), + mColor( LLColor4::white ), + mImageName(image_name) +{ + LLUUID image_id; + image_id.set(LLUI::sAssetsGroup->getString( image_name )); + setImage( image_id ); + setTabStop(FALSE); +} + + +LLIconCtrl::~LLIconCtrl() +{ + mImagep = NULL; +} + + +void LLIconCtrl::setImage(const LLUUID &image_id) +{ + mImageID = image_id; + mImagep = LLUI::sImageProvider->getUIImageByID(image_id); +} + + +void LLIconCtrl::draw() +{ + if( getVisible() ) + { + // Border + BOOL has_image = !mImageID.isNull(); + + if( has_image ) + { + if( mImagep.notNull() ) + { + gl_draw_scaled_image(0, 0, + mRect.getWidth(), mRect.getHeight(), + mImagep, + mColor ); + } + } + + LLUICtrl::draw(); + } +} + +// virtual +void LLIconCtrl::setValue(const LLSD& value ) +{ + setImage(value.asUUID()); +} + +// virtual +LLSD LLIconCtrl::getValue() const +{ + LLSD ret = getImage(); + return ret; +} + +// virtual +LLXMLNodePtr LLIconCtrl::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + if (mImageName != "") + { + node->createChild("image_name", TRUE)->setStringValue(mImageName); + } + + node->createChild("color", TRUE)->setFloatValue(4, mColor.mV); + + return node; +} + +LLView* LLIconCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("icon"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + LLUUID image_id; + if (node->hasAttribute("image_name")) + { + LLString image_name; + node->getAttributeString("image_name", image_name); + image_id.set(LLUI::sAssetsGroup->getString( image_name )); + } + + LLColor4 color(LLColor4::white); + LLUICtrlFactory::getAttributeColor(node,"color", color); + + LLIconCtrl* icon = new LLIconCtrl(name, rect, image_id); + icon->setColor(color); + + icon->initFromXML(node, parent); + + return icon; +} diff --git a/indra/llui/lliconctrl.h b/indra/llui/lliconctrl.h new file mode 100644 index 0000000000..ea762982a2 --- /dev/null +++ b/indra/llui/lliconctrl.h @@ -0,0 +1,55 @@ +/** + * @file lliconctrl.h + * @brief LLIconCtrl base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLICONCTRL_H +#define LL_LLICONCTRL_H + +#include "lluuid.h" +#include "v4color.h" +#include "lluictrl.h" +#include "stdenums.h" +#include "llimagegl.h" + +class LLTextBox; + +// +// Classes +// +class LLIconCtrl +: public LLUICtrl +{ +public: + LLIconCtrl(const LLString& name, const LLRect &rect, const LLUUID &image_id); + LLIconCtrl(const LLString& name, const LLRect &rect, const LLString &image_name); + virtual ~LLIconCtrl(); + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_ICON; } + virtual LLString getWidgetTag() const { return LL_ICON_CTRL_TAG; } + + // llview overrides + virtual void draw(); + + void setImage(const LLUUID &image_id); + const LLUUID &getImage() const { return mImageID; } + + // Takes a UUID, wraps get/setImage + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + + void setColor(const LLColor4& color) { mColor = color; } + + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + +protected: + LLColor4 mColor; + LLString mImageName; + LLUUID mImageID; + LLPointer mImagep; +}; + +#endif diff --git a/indra/llui/llkeywords.cpp b/indra/llui/llkeywords.cpp new file mode 100644 index 0000000000..e8628c9374 --- /dev/null +++ b/indra/llui/llkeywords.cpp @@ -0,0 +1,502 @@ +/** + * @file llkeywords.cpp + * @brief Keyword list for LSL + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include +#include + +#include "llkeywords.h" +#include "lltexteditor.h" +#include "llstl.h" +#include + +const U32 KEYWORD_FILE_CURRENT_VERSION = 2; + +inline BOOL LLKeywordToken::isHead(const llwchar* s) +{ + // strncmp is much faster than string compare + BOOL res = TRUE; + const llwchar* t = mToken.c_str(); + S32 len = mToken.size(); + for (S32 i=0; i> buffer; + if( strcmp( buffer, "llkeywords" ) ) + { + llinfos << filename << " does not appear to be a keyword file" << llendl; + return mLoaded; + } + + // Check file version + file >> buffer; + U32 version_num; + file >> version_num; + if( strcmp(buffer, "version") || version_num != (U32)KEYWORD_FILE_CURRENT_VERSION ) + { + llinfos << filename << " does not appear to be a version " << KEYWORD_FILE_CURRENT_VERSION << " keyword file" << llendl; + return mLoaded; + } + + // start of line (SOL) + const char SOL_COMMENT[] = "#"; + const char SOL_WORD[] = "[word "; + const char SOL_LINE[] = "[line "; + const char SOL_ONE_SIDED_DELIMITER[] = "[one_sided_delimiter "; + const char SOL_TWO_SIDED_DELIMITER[] = "[two_sided_delimiter "; + + LLColor3 cur_color( 1, 0, 0 ); + LLKeywordToken::TOKEN_TYPE cur_type = LLKeywordToken::WORD; + + while (!file.eof()) + { + file.getline( buffer, BUFFER_SIZE ); + if( !strncmp( buffer, SOL_COMMENT, strlen(SOL_COMMENT) ) ) + { + continue; + } + else + if( !strncmp( buffer, SOL_WORD, strlen(SOL_WORD) ) ) + { + cur_color = readColor( buffer + strlen(SOL_WORD) ); + cur_type = LLKeywordToken::WORD; + continue; + } + else + if( !strncmp( buffer, SOL_LINE, strlen(SOL_LINE) ) ) + { + cur_color = readColor( buffer + strlen(SOL_LINE) ); + cur_type = LLKeywordToken::LINE; + continue; + } + else + if( !strncmp( buffer, SOL_TWO_SIDED_DELIMITER, strlen(SOL_TWO_SIDED_DELIMITER) ) ) + { + cur_color = readColor( buffer + strlen(SOL_TWO_SIDED_DELIMITER) ); + cur_type = LLKeywordToken::TWO_SIDED_DELIMITER; + continue; + } + if( !strncmp( buffer, SOL_ONE_SIDED_DELIMITER, strlen(SOL_ONE_SIDED_DELIMITER) ) ) + { + cur_color = readColor( buffer + strlen(SOL_ONE_SIDED_DELIMITER) ); + cur_type = LLKeywordToken::ONE_SIDED_DELIMITER; + continue; + } + + LLString token_buffer( buffer ); + LLString::trim(token_buffer); + + typedef boost::tokenizer > tokenizer; + boost::char_separator sep_word("", " \t"); + tokenizer word_tokens(token_buffer, sep_word); + tokenizer::iterator token_word_iter = word_tokens.begin(); + + if( !token_buffer.empty() && token_word_iter != word_tokens.end() ) + { + // first word is keyword + LLString keyword = (*token_word_iter); + LLString::trim(keyword); + + // following words are tooltip + LLString tool_tip; + while (++token_word_iter != word_tokens.end()) + { + tool_tip += (*token_word_iter); + } + LLString::trim(tool_tip); + + if( !tool_tip.empty() ) + { + // Replace : with \n for multi-line tool tips. + LLString::replaceChar( tool_tip, ':', '\n' ); + addToken(cur_type, keyword, cur_color, tool_tip ); + } + else + { + addToken(cur_type, keyword, cur_color, NULL ); + } + } + } + + file.close(); + + mLoaded = TRUE; + return mLoaded; +} + +// Add the token as described +void LLKeywords::addToken(LLKeywordToken::TOKEN_TYPE type, + const LLString& key_in, + const LLColor3& color, + const LLString& tool_tip_in ) +{ + LLWString key = utf8str_to_wstring(key_in); + LLWString tool_tip = utf8str_to_wstring(tool_tip_in); + switch(type) + { + case LLKeywordToken::WORD: + mWordTokenMap[key] = new LLKeywordToken(type, color, key, tool_tip); + break; + + case LLKeywordToken::LINE: + mLineTokenList.push_front(new LLKeywordToken(type, color, key, tool_tip)); + break; + + case LLKeywordToken::TWO_SIDED_DELIMITER: + case LLKeywordToken::ONE_SIDED_DELIMITER: + mDelimiterTokenList.push_front(new LLKeywordToken(type, color, key, tool_tip)); + break; + + default: + llassert(0); + } +} + +LLColor3 LLKeywords::readColor( const LLString& s ) +{ + F32 r, g, b; + r = g = b = 0.0f; + S32 read = sscanf(s.c_str(), "%f, %f, %f]", &r, &g, &b ); + if( read != 3 ) + { + llinfos << " poorly formed color in keyword file" << llendl; + } + return LLColor3( r, g, b ); +} + +// Walk through a string, applying the rules specified by the keyword token list and +// create a list of color segments. +void LLKeywords::findSegments(std::vector* seg_list, const LLWString& wtext) +{ + std::for_each(seg_list->begin(), seg_list->end(), DeletePointer()); + seg_list->clear(); + + if( wtext.empty() ) + { + return; + } + + S32 text_len = wtext.size(); + + seg_list->push_back( new LLTextSegment( LLColor3(0,0,0), 0, text_len ) ); + + const llwchar* base = wtext.c_str(); + const llwchar* cur = base; + const llwchar* line = NULL; + + while( *cur ) + { + if( *cur == '\n' || cur == base ) + { + if( *cur == '\n' ) + { + cur++; + if( !*cur || *cur == '\n' ) + { + continue; + } + } + + // Start of a new line + line = cur; + + // Skip white space + while( *cur && isspace(*cur) && (*cur != '\n') ) + { + cur++; + } + if( !*cur || *cur == '\n' ) + { + continue; + } + + // cur is now at the first non-whitespace character of a new line + + // Line start tokens + { + BOOL line_done = FALSE; + for (token_list_t::iterator iter = mLineTokenList.begin(); + iter != mLineTokenList.end(); ++iter) + { + LLKeywordToken* cur_token = *iter; + if( cur_token->isHead( cur ) ) + { + S32 seg_start = cur - base; + while( *cur && *cur != '\n' ) + { + // skip the rest of the line + cur++; + } + S32 seg_end = cur - base; + + //llinfos << "Seg: [" << (char*)LLString( base, seg_start, seg_end-seg_start) << "]" << llendl; + LLTextSegment* text_segment = new LLTextSegment( cur_token->getColor(), seg_start, seg_end ); + text_segment->setToken( cur_token ); + insertSegment( seg_list, text_segment, text_len); + line_done = TRUE; // to break out of second loop. + break; + } + } + + if( line_done ) + { + continue; + } + } + } + + // Skip white space + while( *cur && isspace(*cur) && (*cur != '\n') ) + { + cur++; + } + + while( *cur && *cur != '\n' ) + { + // Check against delimiters + { + S32 seg_start = 0; + LLKeywordToken* cur_delimiter = NULL; + for (token_list_t::iterator iter = mDelimiterTokenList.begin(); + iter != mDelimiterTokenList.end(); ++iter) + { + LLKeywordToken* delimiter = *iter; + if( delimiter->isHead( cur ) ) + { + cur_delimiter = delimiter; + break; + } + } + + if( cur_delimiter ) + { + S32 between_delimiters = 0; + S32 seg_end = 0; + + seg_start = cur - base; + cur += cur_delimiter->getLength(); + + if( cur_delimiter->getType() == LLKeywordToken::TWO_SIDED_DELIMITER ) + { + while( *cur && !cur_delimiter->isHead(cur)) + { + // Check for an escape sequence. + if (*cur == '\\') + { + // Count the number of backslashes. + S32 num_backslashes = 0; + while (*cur == '\\') + { + num_backslashes++; + between_delimiters++; + cur++; + } + // Is the next character the end delimiter? + if (cur_delimiter->isHead(cur)) + { + // Is there was an odd number of backslashes, then this delimiter + // does not end the sequence. + if (num_backslashes % 2 == 1) + { + between_delimiters++; + cur++; + } + else + { + // This is an end delimiter. + break; + } + } + } + else + { + between_delimiters++; + cur++; + } + } + + if( *cur ) + { + cur += cur_delimiter->getLength(); + seg_end = seg_start + between_delimiters + 2 * cur_delimiter->getLength(); + } + else + { + // eof + seg_end = seg_start + between_delimiters + cur_delimiter->getLength(); + } + } + else + { + llassert( cur_delimiter->getType() == LLKeywordToken::ONE_SIDED_DELIMITER ); + // Left side is the delimiter. Right side is eol or eof. + while( *cur && ('\n' != *cur) ) + { + between_delimiters++; + cur++; + } + seg_end = seg_start + between_delimiters + cur_delimiter->getLength(); + } + + + //llinfos << "Seg: [" << (char*)LLString( base, seg_start, seg_end-seg_start ) << "]" << llendl; + LLTextSegment* text_segment = new LLTextSegment( cur_delimiter->getColor(), seg_start, seg_end ); + text_segment->setToken( cur_delimiter ); + insertSegment( seg_list, text_segment, text_len); + + // Note: we don't increment cur, since the end of one delimited seg may be immediately + // followed by the start of another one. + continue; + } + } + + // check against words + llwchar prev = cur > base ? *(cur-1) : 0; + if( !isalnum( prev ) && (prev != '_') ) + { + const llwchar* p = cur; + while( isalnum( *p ) || (*p == '_') ) + { + p++; + } + S32 seg_len = p - cur; + if( seg_len > 0 ) + { + LLWString word( cur, 0, seg_len ); + word_token_map_t::iterator map_iter = mWordTokenMap.find(word); + if( map_iter != mWordTokenMap.end() ) + { + LLKeywordToken* cur_token = map_iter->second; + S32 seg_start = cur - base; + S32 seg_end = seg_start + seg_len; + + // llinfos << "Seg: [" << word.c_str() << "]" << llendl; + + + LLTextSegment* text_segment = new LLTextSegment( cur_token->getColor(), seg_start, seg_end ); + text_segment->setToken( cur_token ); + insertSegment( seg_list, text_segment, text_len); + } + cur += seg_len; + continue; + } + } + + if( *cur && *cur != '\n' ) + { + cur++; + } + } + } +} + +void LLKeywords::insertSegment(std::vector* seg_list, LLTextSegment* new_segment, S32 text_len ) +{ + LLTextSegment* last = seg_list->back(); + S32 new_seg_end = new_segment->getEnd(); + + if( new_segment->getStart() == last->getStart() ) + { + *last = *new_segment; + delete new_segment; + } + else + { + last->setEnd( new_segment->getStart() ); + seg_list->push_back( new_segment ); + } + + if( new_seg_end < text_len ) + { + seg_list->push_back( new LLTextSegment( LLColor3(0,0,0), new_seg_end, text_len ) ); + } +} + +#ifdef _DEBUG +void LLKeywords::dump() +{ + llinfos << "LLKeywords" << llendl; + + + llinfos << "LLKeywords::sWordTokenMap" << llendl; + word_token_map_t::iterator word_token_iter = mWordTokenMap.begin(); + while( word_token_iter != mWordTokenMap.end() ) + { + LLKeywordToken* word_token = word_token_iter->second; + word_token->dump(); + ++word_token_iter; + } + + llinfos << "LLKeywords::sLineTokenList" << llendl; + for (token_list_t::iterator iter = mLineTokenList.begin(); + iter != mLineTokenList.end(); ++iter) + { + LLKeywordToken* line_token = *iter; + line_token->dump(); + } + + + llinfos << "LLKeywords::sDelimiterTokenList" << llendl; + for (token_list_t::iterator iter = mDelimiterTokenList.begin(); + iter != mDelimiterTokenList.end(); ++iter) + { + LLKeywordToken* delimiter_token = *iter; + delimiter_token->dump(); + } +} + +void LLKeywordToken::dump() +{ + llinfos << "[" << + mColor.mV[VX] << ", " << + mColor.mV[VY] << ", " << + mColor.mV[VZ] << "] [" << + mToken.c_str() << "]" << + llendl; +} + +#endif // DEBUG diff --git a/indra/llui/llkeywords.h b/indra/llui/llkeywords.h new file mode 100644 index 0000000000..fcf70b77b1 --- /dev/null +++ b/indra/llui/llkeywords.h @@ -0,0 +1,91 @@ +/** + * @file llkeywords.h + * @brief Keyword list for LSL + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLKEYWORDS_H +#define LL_LLKEYWORDS_H + + +#include "llstring.h" +#include "v3color.h" +#include +#include +#include + +class LLTextSegment; + + +class LLKeywordToken +{ +public: + enum TOKEN_TYPE { WORD, LINE, TWO_SIDED_DELIMITER, ONE_SIDED_DELIMITER }; + + LLKeywordToken( TOKEN_TYPE type, const LLColor3& color, const LLWString& token, const LLWString& tool_tip ) + : + mType( type ), + mToken( token ), + mColor( color ), + mToolTip( tool_tip ) + { + } + + S32 getLength() { return mToken.size(); } + BOOL isHead(const llwchar* s); + const LLColor3& getColor() { return mColor; } + TOKEN_TYPE getType() { return mType; } + const LLWString& getToolTip() { return mToolTip; } + +#ifdef _DEBUG + void dump(); +#endif + +private: + TOKEN_TYPE mType; +public: + LLWString mToken; + LLColor3 mColor; +private: + LLWString mToolTip; +}; + +class LLKeywords +{ +public: + LLKeywords(); + ~LLKeywords(); + + BOOL loadFromFile(const LLString& filename); + BOOL isLoaded() { return mLoaded; } + + void findSegments(std::vector *seg_list, const LLWString& text ); + +#ifdef _DEBUG + void dump(); +#endif + + // Add the token as described + void addToken(LLKeywordToken::TOKEN_TYPE type, + const LLString& key, + const LLColor3& color, + const LLString& tool_tip = LLString::null); + +private: + LLColor3 readColor(const LLString& s); + void insertSegment(std::vector *seg_list, LLTextSegment* new_segment, S32 text_len); + +private: + BOOL mLoaded; +public: + typedef std::map word_token_map_t; + word_token_map_t mWordTokenMap; +private: + typedef std::deque token_list_t; + token_list_t mLineTokenList; + token_list_t mDelimiterTokenList; +}; + +#endif // LL_LLKEYWORDS_H diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp new file mode 100644 index 0000000000..41049fdf1f --- /dev/null +++ b/indra/llui/lllineeditor.cpp @@ -0,0 +1,2301 @@ +/** + * @file lllineeditor.cpp + * @brief LLLineEditor base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Text editor widget to let users enter a single line. + +#include "linden_common.h" + +#include "lllineeditor.h" + +#include "audioengine.h" +#include "llmath.h" +#include "llfontgl.h" +#include "llgl.h" +#include "sound_ids.h" +#include "lltimer.h" + +//#include "llclipboard.h" +#include "llcontrol.h" +#include "llbutton.h" +#include "llfocusmgr.h" +#include "llkeyboard.h" +#include "llrect.h" +#include "llresmgr.h" +#include "llstring.h" +#include "llwindow.h" +#include "llui.h" +#include "lluictrlfactory.h" +#include "llclipboard.h" + +// +// Imported globals +// + +// +// Constants +// + +const S32 UI_LINEEDITOR_CURSOR_THICKNESS = 2; +const S32 UI_LINEEDITOR_H_PAD = 2; +const S32 UI_LINEEDITOR_V_PAD = 1; +const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds +const S32 SCROLL_INCREMENT_ADD = 0; // make space for typing +const S32 SCROLL_INCREMENT_DEL = 4; // make space for baskspacing +const F32 AUTO_SCROLL_TIME = 0.05f; +const F32 LABEL_HPAD = 5.f; + +// This is a friend class of and is only used by LLLineEditor +class LLLineEditorRollback +{ +public: + LLLineEditorRollback( LLLineEditor* ed ) + : + mCursorPos( ed->mCursorPos ), + mScrollHPos( ed->mScrollHPos ), + mIsSelecting( ed->mIsSelecting ), + mSelectionStart( ed->mSelectionStart ), + mSelectionEnd( ed->mSelectionEnd ) + { + mText = ed->getText(); + } + + void doRollback( LLLineEditor* ed ) + { + ed->mCursorPos = mCursorPos; + ed->mScrollHPos = mScrollHPos; + ed->mIsSelecting = mIsSelecting; + ed->mSelectionStart = mSelectionStart; + ed->mSelectionEnd = mSelectionEnd; + ed->mText = mText; + } + + LLString getText() { return mText; } + +private: + LLString mText; + S32 mCursorPos; + S32 mScrollHPos; + BOOL mIsSelecting; + S32 mSelectionStart; + S32 mSelectionEnd; +}; + + +// +// Member functions +// + +LLLineEditor::LLLineEditor(const LLString& name, const LLRect& rect, + const LLString& default_text, const LLFontGL* font, + S32 max_length_bytes, + void (*commit_callback)(LLUICtrl* caller, void* user_data ), + void (*keystroke_callback)(LLLineEditor* caller, void* user_data ), + void (*focus_lost_callback)(LLLineEditor* caller, void* user_data ), + void* userdata, + LLLinePrevalidateFunc prevalidate_func, + LLViewBorder::EBevel border_bevel, + LLViewBorder::EStyle border_style, + S32 border_thickness) + : + LLUICtrl( name, rect, TRUE, commit_callback, userdata, FOLLOWS_TOP | FOLLOWS_LEFT ), + mMaxLengthChars(max_length_bytes), + mMaxLengthBytes(max_length_bytes), + mCursorPos( 0 ), + mScrollHPos( 0 ), + mBorderLeft(0), + mBorderRight(0), + mCommitOnFocusLost( TRUE ), + mKeystrokeCallback( keystroke_callback ), + mFocusLostCallback( focus_lost_callback ), + mIsSelecting( FALSE ), + mSelectionStart( 0 ), + mSelectionEnd( 0 ), + mLastSelectionX(-1), + mLastSelectionY(-1), + mPrevalidateFunc( prevalidate_func ), + mCursorColor( LLUI::sColorsGroup->getColor( "TextCursorColor" ) ), + mFgColor( LLUI::sColorsGroup->getColor( "TextFgColor" ) ), + mReadOnlyFgColor( LLUI::sColorsGroup->getColor( "TextFgReadOnlyColor" ) ), + mTentativeFgColor( LLUI::sColorsGroup->getColor( "TextFgTentativeColor" ) ), + mWriteableBgColor( LLUI::sColorsGroup->getColor( "TextBgWriteableColor" ) ), + mReadOnlyBgColor( LLUI::sColorsGroup->getColor( "TextBgReadOnlyColor" ) ), + mFocusBgColor( LLUI::sColorsGroup->getColor( "TextBgFocusColor" ) ), + mBorderThickness( border_thickness ), + mIgnoreArrowKeys( FALSE ), + mIgnoreTab( TRUE ), + mDrawAsterixes( FALSE ), + mHandleEditKeysDirectly( FALSE ), + mSelectAllonFocusReceived( FALSE ), + mPassDelete(FALSE), + mReadOnly(FALSE) +{ + llassert( max_length_bytes > 0 ); + + if (font) + { + mGLFont = font; + } + else + { + mGLFont = LLFontGL::sSansSerifSmall; + } + + mMinHPixels = mBorderThickness + UI_LINEEDITOR_H_PAD + mBorderLeft; + mMaxHPixels = mRect.getWidth() - mMinHPixels - mBorderThickness - mBorderRight; + + mScrollTimer.reset(); + + setText(default_text); + + setCursor(mText.length()); + + // Scalable UI somehow made these rectangles off-by-one. + // I don't know why. JC + LLRect border_rect(0, mRect.getHeight()-1, mRect.getWidth()-1, 0); + mBorder = new LLViewBorder( "line ed border", border_rect, border_bevel, border_style, mBorderThickness ); + addChild( mBorder ); + mBorder->setFollows(FOLLOWS_LEFT|FOLLOWS_RIGHT|FOLLOWS_TOP|FOLLOWS_BOTTOM); +} + + +LLLineEditor::~LLLineEditor() +{ + mFocusLostCallback = NULL; + mCommitOnFocusLost = FALSE; + + gFocusMgr.releaseFocusIfNeeded( this ); + + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } +} + +//virtual +EWidgetType LLLineEditor::getWidgetType() const +{ + return WIDGET_TYPE_LINE_EDITOR; +} + +//virtual +LLString LLLineEditor::getWidgetTag() const +{ + return LL_LINE_EDITOR_TAG; +} + +void LLLineEditor::onFocusLost() +{ + if( mFocusLostCallback ) + { + mFocusLostCallback( this, mCallbackUserData ); + } + + if( mCommitOnFocusLost ) + { + onCommit(); + } + + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } + + getWindow()->showCursorFromMouseMove(); +} + +void LLLineEditor::onCommit() +{ + LLUICtrl::onCommit(); + selectAll(); +} + +void LLLineEditor::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLUICtrl::reshape(width, height, called_from_parent ); + + mMaxHPixels = mRect.getWidth() - 2 * (mBorderThickness + UI_LINEEDITOR_H_PAD) + 1 - mBorderRight; +} + + +void LLLineEditor::setEnabled(BOOL enabled) +{ + mReadOnly = !enabled; + setTabStop(!mReadOnly); +} + + +void LLLineEditor::setMaxTextLength(S32 max_text_length) +{ + S32 max_len = llmax(0, max_text_length); + mMaxLengthBytes = max_len; + mMaxLengthChars = max_len; +} + +void LLLineEditor::setBorderWidth(S32 left, S32 right) +{ + mBorderLeft = llclamp(left, 0, mRect.getWidth()); + mBorderRight = llclamp(right, 0, mRect.getWidth()); + mMinHPixels = mBorderThickness + UI_LINEEDITOR_H_PAD + mBorderLeft; + mMaxHPixels = mRect.getWidth() - mMinHPixels - mBorderThickness - mBorderRight; +} + +void LLLineEditor::setLabel(const LLString &new_label) +{ + mLabel = new_label; +} + +void LLLineEditor::setText(const LLString &new_text) +{ + // If new text is identical, don't copy and don't move insertion point + if (mText.getString() == new_text) + { + return; + } + + // Check to see if entire field is selected. + S32 len = mText.length(); + BOOL allSelected = (len > 0) && (( mSelectionStart == 0 && mSelectionEnd == len ) + || ( mSelectionStart == len && mSelectionEnd == 0 )); + + LLString truncated_utf8 = new_text; + if (truncated_utf8.size() > (U32)mMaxLengthBytes) + { + utf8str_truncate(truncated_utf8, mMaxLengthBytes); + } + mText.assign(truncated_utf8); + mText.truncate(mMaxLengthChars); + + if (allSelected) + { + // ...keep whole thing selected + selectAll(); + } + else + { + // try to preserve insertion point, but deselect text + deselect(); + } + setCursor(llmin((S32)mText.length(), getCursor())); +} + + +// Picks a new cursor position based on the actual screen size of text being drawn. +void LLLineEditor::setCursorAtLocalPos( S32 local_mouse_x ) +{ + const llwchar* wtext = mText.getWString().c_str(); + LLWString asterix_text; + if (mDrawAsterixes) + { + for (S32 i = 0; i < mText.length(); i++) + { + asterix_text += '*'; + } + wtext = asterix_text.c_str(); + } + + S32 cursor_pos = + mScrollHPos + + mGLFont->charFromPixelOffset( + wtext, mScrollHPos, + (F32)(local_mouse_x - mMinHPixels), + (F32)(mMaxHPixels - mMinHPixels + 1)); // min-max range is inclusive + setCursor(cursor_pos); +} + +void LLLineEditor::setCursor( S32 pos ) +{ + S32 old_cursor_pos = getCursor(); + mCursorPos = llclamp( pos, 0, mText.length()); + + S32 pixels_after_scroll = findPixelNearestPos(); + if( pixels_after_scroll > mMaxHPixels ) + { + S32 width_chars_to_left = mGLFont->getWidth(mText.getWString().c_str(), 0, mScrollHPos); + S32 last_visible_char = mGLFont->maxDrawableChars(mText.getWString().c_str(), llmax(0.f, (F32)(mMaxHPixels - mMinHPixels + width_chars_to_left))); + S32 min_scroll = mGLFont->firstDrawableChar(mText.getWString().c_str(), (F32)(mMaxHPixels - mMinHPixels), mText.length(), getCursor()); + if (old_cursor_pos == last_visible_char) + { + mScrollHPos = llmin(mText.length(), llmax(min_scroll, mScrollHPos + SCROLL_INCREMENT_ADD)); + } + else + { + mScrollHPos = min_scroll; + } + } + else if (getCursor() < mScrollHPos) + { + if (old_cursor_pos == mScrollHPos) + { + mScrollHPos = llmax(0, llmin(getCursor(), mScrollHPos - SCROLL_INCREMENT_DEL)); + } + else + { + mScrollHPos = getCursor(); + } + } +} + + +void LLLineEditor::setCursorToEnd() +{ + setCursor(mText.length()); + deselect(); +} + +BOOL LLLineEditor::canDeselect() +{ + return hasSelection(); +} + + +void LLLineEditor::deselect() +{ + mSelectionStart = 0; + mSelectionEnd = 0; + mIsSelecting = FALSE; +} + + +void LLLineEditor::startSelection() +{ + mIsSelecting = TRUE; + mSelectionStart = getCursor(); + mSelectionEnd = getCursor(); +} + +void LLLineEditor::endSelection() +{ + if( mIsSelecting ) + { + mIsSelecting = FALSE; + mSelectionEnd = getCursor(); + } +} + +BOOL LLLineEditor::canSelectAll() +{ + return TRUE; +} + +void LLLineEditor::selectAll() +{ + mSelectionStart = mText.length(); + mSelectionEnd = 0; + setCursor(mSelectionEnd); + //mScrollHPos = 0; + mIsSelecting = TRUE; +} + + +BOOL LLLineEditor::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + setFocus( TRUE ); + + if (mSelectionEnd == 0 && mSelectionStart == mText.length()) + { + // if everything is selected, handle this as a normal click to change insertion point + handleMouseDown(x, y, mask); + } + else + { + // otherwise select everything + selectAll(); + } + + // We don't want handleMouseUp() to "finish" the selection (and thereby + // set mSelectionEnd to where the mouse is), so we finish the selection + // here. + mIsSelecting = FALSE; + + // delay cursor flashing + mKeystrokeTimer.reset(); + + return TRUE; +} + +BOOL LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (x < mBorderLeft || x > (mRect.getWidth() - mBorderRight)) + { + return LLUICtrl::handleMouseDown(x, y, mask); + } + if (mSelectAllonFocusReceived + && gFocusMgr.getKeyboardFocus() != this) + { + setFocus( TRUE ); + } + else + { + setFocus( TRUE ); + + if (mask & MASK_SHIFT) + { + // Handle selection extension + S32 old_cursor_pos = getCursor(); + setCursorAtLocalPos(x); + + if (hasSelection()) + { + /* Mac-like behavior - extend selection towards the cursor + if (getCursor() < mSelectionStart + && getCursor() < mSelectionEnd) + { + // ...left of selection + mSelectionStart = llmax(mSelectionStart, mSelectionEnd); + mSelectionEnd = getCursor(); + } + else if (getCursor() > mSelectionStart + && getCursor() > mSelectionEnd) + { + // ...right of selection + mSelectionStart = llmin(mSelectionStart, mSelectionEnd); + mSelectionEnd = getCursor(); + } + else + { + mSelectionEnd = getCursor(); + } + */ + // Windows behavior + mSelectionEnd = getCursor(); + } + else + { + mSelectionStart = old_cursor_pos; + mSelectionEnd = getCursor(); + } + // assume we're starting a drag select + mIsSelecting = TRUE; + } + else + { + // Move cursor and deselect for regular click + setCursorAtLocalPos( x ); + deselect(); + startSelection(); + } + + gFocusMgr.setMouseCapture( this, &LLLineEditor::onMouseCaptureLost ); + } + + // delay cursor flashing + mKeystrokeTimer.reset(); + + return TRUE; +} + + +BOOL LLLineEditor::handleHover(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + if (gFocusMgr.getMouseCapture() != this && (x < mBorderLeft || x > (mRect.getWidth() - mBorderRight))) + { + return LLUICtrl::handleHover(x, y, mask); + } + + if( getVisible() ) + { + if( (gFocusMgr.getMouseCapture() == this) && mIsSelecting ) + { + if (x != mLastSelectionX || y != mLastSelectionY) + { + mLastSelectionX = x; + mLastSelectionY = y; + } + // Scroll if mouse cursor outside of bounds + if (mScrollTimer.hasExpired()) + { + S32 increment = llround(mScrollTimer.getElapsedTimeF32() / AUTO_SCROLL_TIME); + mScrollTimer.reset(); + mScrollTimer.setTimerExpirySec(AUTO_SCROLL_TIME); + if( (x < mMinHPixels) && (mScrollHPos > 0 ) ) + { + // Scroll to the left + mScrollHPos = llclamp(mScrollHPos - increment, 0, mText.length()); + } + else + if( (x > mMaxHPixels) && (mCursorPos < (S32)mText.length()) ) + { + // If scrolling one pixel would make a difference... + S32 pixels_after_scrolling_one_char = findPixelNearestPos(1); + if( pixels_after_scrolling_one_char >= mMaxHPixels ) + { + // ...scroll to the right + mScrollHPos = llclamp(mScrollHPos + increment, 0, mText.length()); + } + } + } + + setCursorAtLocalPos( x ); + mSelectionEnd = getCursor(); + + // delay cursor flashing + mKeystrokeTimer.reset(); + + getWindow()->setCursor(UI_CURSOR_IBEAM); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; + handled = TRUE; + } + + if( !handled ) + { + getWindow()->setCursor(UI_CURSOR_IBEAM); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl; + handled = TRUE; + } + } + + return handled; +} + + +BOOL LLLineEditor::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + if( gFocusMgr.getMouseCapture() == this ) + { + gFocusMgr.setMouseCapture( NULL, NULL ); + handled = TRUE; + } + + if (!handled && (x < mBorderLeft || x > (mRect.getWidth() - mBorderRight))) + { + return LLUICtrl::handleMouseUp(x, y, mask); + } + + if( mIsSelecting ) + { + setCursorAtLocalPos( x ); + mSelectionEnd = getCursor(); + + handled = TRUE; + } + + if( handled ) + { + // delay cursor flashing + mKeystrokeTimer.reset(); + } + + return handled; +} + + +// Remove a single character from the text +void LLLineEditor::removeChar() +{ + if( getCursor() > 0 ) + { + mText.erase(getCursor() - 1, 1); + + setCursor(getCursor() - 1); + } + else + { + reportBadKeystroke(); + } +} + + +void LLLineEditor::addChar(const llwchar uni_char) +{ + llwchar new_c = uni_char; + if (hasSelection()) + { + deleteSelection(); + } + else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + mText.erase(getCursor(), 1); + } + + S32 length_chars = mText.length(); + S32 cur_bytes = mText.getString().size();; + S32 new_bytes = wchar_utf8_length(new_c); + + BOOL allow_char = TRUE; + + // Inserting character + if (length_chars == mMaxLengthChars) + { + allow_char = FALSE; + } + if ((new_bytes + cur_bytes) > mMaxLengthBytes) + { + allow_char = FALSE; + } + + if (allow_char) + { + // Will we need to scroll? + LLWString w_buf; + w_buf.assign(1, new_c); + + mText.insert(getCursor(), w_buf); + setCursor(getCursor() + 1); + } + else + { + reportBadKeystroke(); + } + + getWindow()->hideCursorUntilMouseMove(); +} + +// Extends the selection box to the new cursor position +void LLLineEditor::extendSelection( S32 new_cursor_pos ) +{ + if( !mIsSelecting ) + { + startSelection(); + } + + setCursor(new_cursor_pos); + mSelectionEnd = getCursor(); +} + + +void LLLineEditor::setSelection(S32 start, S32 end) +{ + S32 len = mText.length(); + + mIsSelecting = TRUE; + + // JC, yes, this seems odd, but I think you have to presume a + // selection dragged from the end towards the start. + mSelectionStart = llclamp(end, 0, len); + mSelectionEnd = llclamp(start, 0, len); + setCursor(start); +} + +S32 LLLineEditor::prevWordPos(S32 cursorPos) const +{ + const LLWString& wtext = mText.getWString(); + while( (cursorPos > 0) && (wtext[cursorPos-1] == ' ') ) + { + cursorPos--; + } + while( (cursorPos > 0) && isPartOfWord( wtext[cursorPos-1] ) ) + { + cursorPos--; + } + return cursorPos; +} + +S32 LLLineEditor::nextWordPos(S32 cursorPos) const +{ + const LLWString& wtext = mText.getWString(); + while( (cursorPos < getLength()) && isPartOfWord( wtext[cursorPos+1] ) ) + { + cursorPos++; + } + while( (cursorPos < getLength()) && (wtext[cursorPos+1] == ' ') ) + { + cursorPos++; + } + return cursorPos; +} + + +BOOL LLLineEditor::handleSelectionKey(KEY key, MASK mask) +{ + BOOL handled = FALSE; + + if( mask & MASK_SHIFT ) + { + handled = TRUE; + + switch( key ) + { + case KEY_LEFT: + if (mIgnoreArrowKeys) + { + handled = FALSE; + break; + } + if( 0 < getCursor() ) + { + S32 cursorPos = getCursor() - 1; + if( mask & MASK_CONTROL ) + { + cursorPos = prevWordPos(cursorPos); + } + extendSelection( cursorPos ); + } + else + { + reportBadKeystroke(); + } + break; + + case KEY_RIGHT: + if (mIgnoreArrowKeys) + { + handled = FALSE; + break; + } + if( getCursor() < mText.length()) + { + S32 cursorPos = getCursor() + 1; + if( mask & MASK_CONTROL ) + { + cursorPos = nextWordPos(cursorPos); + } + extendSelection( cursorPos ); + } + else + { + reportBadKeystroke(); + } + break; + + case KEY_PAGE_UP: + case KEY_HOME: + if (mIgnoreArrowKeys) + { + handled = FALSE; + break; + } + extendSelection( 0 ); + break; + + case KEY_PAGE_DOWN: + case KEY_END: + { + if (mIgnoreArrowKeys) + { + handled = FALSE; + break; + } + S32 len = mText.length(); + if( len ) + { + extendSelection( len ); + } + break; + } + + default: + handled = FALSE; + break; + } + } + + if (!handled && mHandleEditKeysDirectly) + { + if( (MASK_CONTROL & mask) && ('A' == key) ) + { + if( canSelectAll() ) + { + selectAll(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + } + + + return handled; +} + +void LLLineEditor::deleteSelection() +{ + if( !mReadOnly && hasSelection() ) + { + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + S32 selection_length = abs( mSelectionStart - mSelectionEnd ); + + mText.erase(left_pos, selection_length); + deselect(); + setCursor(left_pos); + } +} + +BOOL LLLineEditor::canCut() +{ + return !mReadOnly && !mDrawAsterixes && hasSelection(); +} + +// cut selection to clipboard +void LLLineEditor::cut() +{ + if( canCut() ) + { + // Prepare for possible rollback + LLLineEditorRollback rollback( this ); + + + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + S32 length = abs( mSelectionStart - mSelectionEnd ); + gClipboard.copyFromSubstring( mText.getWString(), left_pos, length ); + deleteSelection(); + + // Validate new string and rollback the if needed. + BOOL need_to_rollback = ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) ); + if( need_to_rollback ) + { + rollback.doRollback( this ); + reportBadKeystroke(); + } + else + if( mKeystrokeCallback ) + { + mKeystrokeCallback( this, mCallbackUserData ); + } + } +} + +BOOL LLLineEditor::canCopy() +{ + return !mDrawAsterixes && hasSelection(); +} + + +// copy selection to clipboard +void LLLineEditor::copy() +{ + if( canCopy() ) + { + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + S32 length = abs( mSelectionStart - mSelectionEnd ); + gClipboard.copyFromSubstring( mText.getWString(), left_pos, length ); + } +} + +BOOL LLLineEditor::canPaste() +{ + return !mReadOnly && gClipboard.canPasteString(); +} + + +// paste from clipboard +void LLLineEditor::paste() +{ + if (canPaste()) + { + LLWString paste = gClipboard.getPasteWString(); + if (!paste.empty()) + { + // Prepare for possible rollback + LLLineEditorRollback rollback(this); + + // Delete any selected characters + if (hasSelection()) + { + deleteSelection(); + } + + // Clean up string (replace tabs and returns and remove characters that our fonts don't support.) + LLWString clean_string(paste); + LLWString::replaceTabsWithSpaces(clean_string, 1); + //clean_string = wstring_detabify(paste, 1); + LLWString::replaceChar(clean_string, '\n', ' '); + + // Insert the string + + //check to see that the size isn't going to be larger than the + //max number of characters or bytes + U32 available_bytes = mMaxLengthBytes - wstring_utf8_length(mText); + size_t available_chars = mMaxLengthChars - mText.length(); + + if ( available_bytes < (U32) wstring_utf8_length(clean_string) ) + { + llwchar current_symbol = clean_string[0]; + U32 wchars_that_fit = 0; + U32 total_bytes = wchar_utf8_length(current_symbol); + + //loop over the "wide" characters (symbols) + //and check to see how large (in bytes) each symbol is. + while ( total_bytes <= available_bytes ) + { + //while we still have available bytes + //"accept" the current symbol and check the size + //of the next one + current_symbol = clean_string[++wchars_that_fit]; + total_bytes += wchar_utf8_length(current_symbol); + } + + clean_string = clean_string.substr(0, wchars_that_fit); + reportBadKeystroke(); + } + else if (available_chars < clean_string.length()) + { + // We can't insert all the characters. Insert as many as possible + // but make a noise to alert the user. JC + clean_string = clean_string.substr(0, available_chars); + reportBadKeystroke(); + } + + mText.insert(getCursor(), clean_string); + setCursor(llmin(mMaxLengthChars, getCursor() + (S32)clean_string.length())); + deselect(); + + // Validate new string and rollback the if needed. + BOOL need_to_rollback = ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) ); + if( need_to_rollback ) + { + rollback.doRollback( this ); + reportBadKeystroke(); + } + else + if( mKeystrokeCallback ) + { + mKeystrokeCallback( this, mCallbackUserData ); + } + } + } +} + + +BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask) +{ + BOOL handled = FALSE; + + switch( key ) + { + case KEY_INSERT: + if (mask == MASK_NONE) + { + gKeyboard->toggleInsertMode(); + } + + handled = TRUE; + break; + + case KEY_BACKSPACE: + if (!mReadOnly) + { + //llinfos << "Handling backspace" << llendl; + if( hasSelection() ) + { + deleteSelection(); + } + else + if( 0 < getCursor() ) + { + removeChar(); + } + else + { + reportBadKeystroke(); + } + } + handled = TRUE; + break; + + case KEY_PAGE_UP: + case KEY_HOME: + if (!mIgnoreArrowKeys) + { + setCursor(0); + handled = TRUE; + } + break; + + case KEY_PAGE_DOWN: + case KEY_END: + if (!mIgnoreArrowKeys) + { + S32 len = mText.length(); + if( len ) + { + setCursor(len); + } + handled = TRUE; + } + break; + + case KEY_LEFT: + if (!mIgnoreArrowKeys) + { + if( hasSelection() ) + { + setCursor(llmin( getCursor() - 1, mSelectionStart, mSelectionEnd )); + } + else + if( 0 < getCursor() ) + { + S32 cursorPos = getCursor() - 1; + if( mask & MASK_CONTROL ) + { + cursorPos = prevWordPos(cursorPos); + } + setCursor(cursorPos); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + break; + + case KEY_RIGHT: + if (!mIgnoreArrowKeys) + { + if (hasSelection()) + { + setCursor(llmax(getCursor() + 1, mSelectionStart, mSelectionEnd)); + } + else + if (getCursor() < mText.length()) + { + S32 cursorPos = getCursor() + 1; + if( mask & MASK_CONTROL ) + { + cursorPos = nextWordPos(cursorPos); + } + setCursor(cursorPos); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + break; + + default: + break; + } + + if( !handled && mHandleEditKeysDirectly ) + { + // Standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system. + if( KEY_DELETE == key ) + { + if( canDoDelete() ) + { + doDelete(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + else + if( MASK_CONTROL & mask ) + { + if( 'C' == key ) + { + if( canCopy() ) + { + copy(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + else + if( 'V' == key ) + { + if( canPaste() ) + { + paste(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + else + if( 'X' == key ) + { + if( canCut() ) + { + cut(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + } + } + return handled; +} + + +BOOL LLLineEditor::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent ) +{ + BOOL handled = FALSE; + BOOL selection_modified = FALSE; + + if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible()) + { + LLLineEditorRollback rollback( this ); + + if( !handled ) + { + handled = handleSelectionKey( key, mask ); + selection_modified = handled; + } + + // Handle most keys only if the text editor is writeable. + if ( !mReadOnly ) + { + if( !handled ) + { + handled = handleSpecialKey( key, mask ); + } + } + + if( handled ) + { + mKeystrokeTimer.reset(); + + // Most keystrokes will make the selection box go away, but not all will. + if( !selection_modified && + KEY_SHIFT != key && + KEY_CONTROL != key && + KEY_ALT != key && + KEY_CAPSLOCK ) + { + deselect(); + } + + BOOL need_to_rollback = FALSE; + + // If read-only, don't allow changes + need_to_rollback |= (mReadOnly && (mText.getString() == rollback.getText())); + + // Validate new string and rollback the keystroke if needed. + need_to_rollback |= (mPrevalidateFunc && !mPrevalidateFunc(mText.getWString())); + + if (need_to_rollback) + { + rollback.doRollback(this); + + reportBadKeystroke(); + } + + // Notify owner if requested + if (!need_to_rollback && handled) + { + if (mKeystrokeCallback) + { + mKeystrokeCallback(this, mCallbackUserData); + } + } + } + } + + return handled; +} + + +BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent) +{ + if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL + { + return FALSE; + } + + BOOL handled = FALSE; + + if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible() && !mReadOnly) + { + handled = TRUE; + + LLLineEditorRollback rollback( this ); + + addChar(uni_char); + + mKeystrokeTimer.reset(); + + deselect(); + + BOOL need_to_rollback = FALSE; + + // Validate new string and rollback the keystroke if needed. + need_to_rollback |= ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) ); + + if( need_to_rollback ) + { + rollback.doRollback( this ); + + reportBadKeystroke(); + } + + // Notify owner if requested + if( !need_to_rollback && handled ) + { + if( mKeystrokeCallback ) + { + // HACK! The only usage of this callback doesn't do anything with the character. + // We'll have to do something about this if something ever changes! - Doug + mKeystrokeCallback( this, mCallbackUserData ); + } + } + } + return handled; +} + + +BOOL LLLineEditor::canDoDelete() +{ + return ( !mReadOnly && (!mPassDelete || (hasSelection() || (getCursor() < mText.length()))) ); +} + +void LLLineEditor::doDelete() +{ + if (canDoDelete()) + { + // Prepare for possible rollback + LLLineEditorRollback rollback( this ); + + if (hasSelection()) + { + deleteSelection(); + } + else if ( getCursor() < mText.length()) + { + setCursor(getCursor() + 1); + removeChar(); + } + + // Validate new string and rollback the if needed. + BOOL need_to_rollback = ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) ); + if( need_to_rollback ) + { + rollback.doRollback( this ); + reportBadKeystroke(); + } + else + { + if( mKeystrokeCallback ) + { + mKeystrokeCallback( this, mCallbackUserData ); + } + } + } +} + + +void LLLineEditor::draw() +{ + if( !getVisible() ) + { + return; + } + + S32 text_len = mText.length(); + + LLString saved_text; + if (mDrawAsterixes) + { + saved_text = mText.getString(); + LLString text; + for (S32 i = 0; i < mText.length(); i++) + { + text += '*'; + } + mText = text; + } + + // draw rectangle for the background + LLRect background( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + background.stretch( -mBorderThickness ); + + LLColor4 bg_color = mReadOnlyBgColor; + + // drawing solids requires texturing be disabled + { + LLGLSNoTexture no_texture; + // draw background for text + if( !mReadOnly ) + { + if( gFocusMgr.getKeyboardFocus() == this ) + { + bg_color = mFocusBgColor; + } + else + { + bg_color = mWriteableBgColor; + } + } + gl_rect_2d(background, bg_color); + } + + // draw text + + S32 cursor_bottom = background.mBottom + 1; + S32 cursor_top = background.mTop - 1; + + LLColor4 text_color; + if (!mReadOnly) + { + if (!mTentative) + { + text_color = mFgColor; + } + else + { + text_color = mTentativeFgColor; + } + } + else + { + text_color = mReadOnlyFgColor; + } + LLColor4 label_color = mTentativeFgColor; + + S32 rendered_text = 0; + F32 rendered_pixels_right = (F32)mMinHPixels; + F32 text_bottom = (F32)background.mBottom + (F32)UI_LINEEDITOR_V_PAD; + + if( (gFocusMgr.getKeyboardFocus() == this) && hasSelection() ) + { + S32 select_left; + S32 select_right; + if( mSelectionStart < getCursor() ) + { + select_left = mSelectionStart; + select_right = getCursor(); + } + else + { + select_left = getCursor(); + select_right = mSelectionStart; + } + + if( select_left > mScrollHPos ) + { + // unselected, left side + rendered_text = mGLFont->render( + mText, mScrollHPos, + rendered_pixels_right, text_bottom, + text_color, + LLFontGL::LEFT, LLFontGL::BOTTOM, + LLFontGL::NORMAL, + select_left - mScrollHPos, + mMaxHPixels - llround(rendered_pixels_right), + &rendered_pixels_right); + } + + if( (rendered_pixels_right < (F32)mMaxHPixels) && (rendered_text < text_len) ) + { + LLColor4 color(1.f - bg_color.mV[0], 1.f - bg_color.mV[1], 1.f - bg_color.mV[2], 1.f); + // selected middle + S32 width = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos + rendered_text, select_right - mScrollHPos - rendered_text); + width = llmin(width, mMaxHPixels - llround(rendered_pixels_right)); + gl_rect_2d(llround(rendered_pixels_right), cursor_top, llround(rendered_pixels_right)+width, cursor_bottom, color); + + rendered_text += mGLFont->render( + mText, mScrollHPos + rendered_text, + rendered_pixels_right, text_bottom, + LLColor4( 1.f - text_color.mV[0], 1.f - text_color.mV[1], 1.f - text_color.mV[2], 1 ), + LLFontGL::LEFT, LLFontGL::BOTTOM, + LLFontGL::NORMAL, + select_right - mScrollHPos - rendered_text, + mMaxHPixels - llround(rendered_pixels_right), + &rendered_pixels_right); + } + + if( (rendered_pixels_right < (F32)mMaxHPixels) && (rendered_text < text_len) ) + { + // unselected, right side + mGLFont->render( + mText, mScrollHPos + rendered_text, + rendered_pixels_right, text_bottom, + text_color, + LLFontGL::LEFT, LLFontGL::BOTTOM, + LLFontGL::NORMAL, + S32_MAX, + mMaxHPixels - llround(rendered_pixels_right), + &rendered_pixels_right); + } + } + else + { + mGLFont->render( + mText, mScrollHPos, + rendered_pixels_right, text_bottom, + text_color, + LLFontGL::LEFT, LLFontGL::BOTTOM, + LLFontGL::NORMAL, + S32_MAX, + mMaxHPixels - llround(rendered_pixels_right), + &rendered_pixels_right); + } + + // If we're editing... + if( gFocusMgr.getKeyboardFocus() == this) + { + // (Flash the cursor every half second) + if (gShowTextEditCursor && !mReadOnly) + { + F32 elapsed = mKeystrokeTimer.getElapsedTimeF32(); + if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) ) + { + S32 cursor_left = findPixelNearestPos(); + cursor_left -= UI_LINEEDITOR_CURSOR_THICKNESS / 2; + S32 cursor_right = cursor_left + UI_LINEEDITOR_CURSOR_THICKNESS; + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) + { + const LLWString space(utf8str_to_wstring(LLString(" "))); + S32 wswidth = mGLFont->getWidth(space.c_str()); + S32 width = mGLFont->getWidth(mText.getWString().c_str(), getCursor(), 1) + 1; + cursor_right = cursor_left + llmax(wswidth, width); + } + // Use same color as text for the Cursor + gl_rect_2d(cursor_left, cursor_top, + cursor_right, cursor_bottom, text_color); + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) + { + mGLFont->render(mText, getCursor(), (F32)(cursor_left + UI_LINEEDITOR_CURSOR_THICKNESS / 2), text_bottom, + LLColor4( 1.f - text_color.mV[0], 1.f - text_color.mV[1], 1.f - text_color.mV[2], 1 ), + LLFontGL::LEFT, LLFontGL::BOTTOM, + LLFontGL::NORMAL, + 1); + } + } + } + + // Draw children (border) + //mBorder->setVisible(TRUE); + mBorder->setKeyboardFocusHighlight( TRUE ); + LLView::draw(); + mBorder->setKeyboardFocusHighlight( FALSE ); + //mBorder->setVisible(FALSE); + } + else // does not have keyboard input + { + // draw label if no text provided + if (0 == mText.length()) + { + mGLFont->render(mLabel.getWString(), 0, + LABEL_HPAD, (F32)text_bottom, + label_color, + LLFontGL::LEFT, LLFontGL::BOTTOM, + LLFontGL::NORMAL, + S32_MAX, + mMaxHPixels - llround(rendered_pixels_right), + &rendered_pixels_right, FALSE); + } + // Draw children (border) + LLView::draw(); + } + + if (mDrawAsterixes) + { + mText = saved_text; + } +} + + +// Returns the local screen space X coordinate associated with the text cursor position. +S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset) +{ + S32 dpos = getCursor() - mScrollHPos + cursor_offset; + S32 result = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos, dpos) + mMinHPixels; + return result; +} + +void LLLineEditor::reportBadKeystroke() +{ + make_ui_sound("UISndBadKeystroke"); +} + +//virtual +void LLLineEditor::clear() +{ + mText.clear(); + setCursor(0); +} + +//virtual +void LLLineEditor::onTabInto() +{ + selectAll(); +} + +//virtual +BOOL LLLineEditor::acceptsTextInput() const +{ + return TRUE; +} + +// Start or stop the editor from accepting text-editing keystrokes +void LLLineEditor::setFocus( BOOL new_state ) +{ + BOOL old_state = hasFocus(); + + // getting focus when we didn't have it before, and we want to select all + if (!old_state && new_state && mSelectAllonFocusReceived) + { + selectAll(); + // We don't want handleMouseUp() to "finish" the selection (and thereby + // set mSelectionEnd to where the mouse is), so we finish the selection + // here. + mIsSelecting = FALSE; + } + + if( new_state ) + { + gEditMenuHandler = this; + + // Don't start the cursor flashing right away + mKeystrokeTimer.reset(); + } + else + { + // Not really needed, since loss of keyboard focus should take care of this, + // but limited paranoia is ok. + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } + + endSelection(); + } + + LLUICtrl::setFocus( new_state ); +} + +//virtual +void LLLineEditor::setRect(const LLRect& rect) +{ + LLUICtrl::setRect(rect); + if (mBorder) + { + LLRect border_rect = mBorder->getRect(); + // Scalable UI somehow made these rectangles off-by-one. + // I don't know why. JC + border_rect.setOriginAndSize(border_rect.mLeft, border_rect.mBottom, + rect.getWidth()-1, rect.getHeight()-1); + mBorder->setRect(border_rect); + } +} + +// Limits what characters can be used to [1234567890.-] with [-] only valid in the first position. +// Does NOT ensure that the string is a well-formed number--that's the job of post-validation--for +// the simple reasons that intermediate states may be invalid even if the final result is valid. +// +// static +BOOL LLLineEditor::prevalidateFloat(const LLWString &str) +{ + LLLocale locale(LLLocale::USER_LOCALE); + + BOOL success = TRUE; + LLWString trimmed = str; + LLWString::trim(trimmed); + S32 len = trimmed.length(); + if( 0 < len ) + { + // May be a comma or period, depending on the locale + char decimal_point = gResMgr->getDecimalPoint(); + + S32 i = 0; + + // First character can be a negative sign + if( '-' == trimmed[0] ) + { + i++; + } + + for( ; i < len; i++ ) + { + if( (decimal_point != trimmed[i] ) && !isdigit( trimmed[i] ) ) + { + success = FALSE; + break; + } + } + } + + return success; +} + +//static +BOOL LLLineEditor::isPartOfWord(llwchar c) { return (c == '_') || isalnum(c); } + +// static +BOOL LLLineEditor::postvalidateFloat(const LLString &str) +{ + LLLocale locale(LLLocale::USER_LOCALE); + + BOOL success = TRUE; + BOOL has_decimal = FALSE; + BOOL has_digit = FALSE; + + LLWString trimmed = utf8str_to_wstring(str); + LLWString::trim(trimmed); + S32 len = trimmed.length(); + if( 0 < len ) + { + S32 i = 0; + + // First character can be a negative sign + if( '-' == trimmed[0] ) + { + i++; + } + + // May be a comma or period, depending on the locale + char decimal_point = gResMgr->getDecimalPoint(); + + for( ; i < len; i++ ) + { + if( decimal_point == trimmed[i] ) + { + if( has_decimal ) + { + // can't have two + success = FALSE; + break; + } + else + { + has_decimal = TRUE; + } + } + else + if( isdigit( trimmed[i] ) ) + { + has_digit = TRUE; + } + else + { + success = FALSE; + break; + } + } + } + + // Gotta have at least one + success = has_digit; + + return success; +} + +// Limits what characters can be used to [1234567890-] with [-] only valid in the first position. +// Does NOT ensure that the string is a well-formed number--that's the job of post-validation--for +// the simple reasons that intermediate states may be invalid even if the final result is valid. +// +// static +BOOL LLLineEditor::prevalidateInt(const LLWString &str) +{ + LLLocale locale(LLLocale::USER_LOCALE); + + BOOL success = TRUE; + LLWString trimmed = str; + LLWString::trim(trimmed); + S32 len = trimmed.length(); + if( 0 < len ) + { + S32 i = 0; + + // First character can be a negative sign + if( '-' == trimmed[0] ) + { + i++; + } + + for( ; i < len; i++ ) + { + if( !isdigit( trimmed[i] ) ) + { + success = FALSE; + break; + } + } + } + + return success; +} + +// static +BOOL LLLineEditor::prevalidatePositiveS32(const LLWString &str) +{ + LLLocale locale(LLLocale::USER_LOCALE); + + LLWString trimmed = str; + LLWString::trim(trimmed); + S32 len = trimmed.length(); + BOOL success = TRUE; + if(0 < len) + { + if(('-' == trimmed[0]) || ('0' == trimmed[0])) + { + success = FALSE; + } + S32 i = 0; + while(success && (i < len)) + { + if(!isdigit(trimmed[i++])) + { + success = FALSE; + } + } + } + if (success) + { + S32 val = strtol(wstring_to_utf8str(trimmed).c_str(), NULL, 10); + if (val <= 0) + { + success = FALSE; + } + } + return success; +} + +BOOL LLLineEditor::prevalidateNonNegativeS32(const LLWString &str) +{ + LLLocale locale(LLLocale::USER_LOCALE); + + LLWString trimmed = str; + LLWString::trim(trimmed); + S32 len = trimmed.length(); + BOOL success = TRUE; + if(0 < len) + { + if('-' == trimmed[0]) + { + success = FALSE; + } + S32 i = 0; + while(success && (i < len)) + { + if(!isdigit(trimmed[i++])) + { + success = FALSE; + } + } + } + if (success) + { + S32 val = strtol(wstring_to_utf8str(trimmed).c_str(), NULL, 10); + if (val < 0) + { + success = FALSE; + } + } + return success; +} + +BOOL LLLineEditor::prevalidateAlphaNum(const LLWString &str) +{ + LLLocale locale(LLLocale::USER_LOCALE); + + BOOL rv = TRUE; + S32 len = str.length(); + if(len == 0) return rv; + while(len--) + { + if( !isalnum(str[len]) ) + { + rv = FALSE; + break; + } + } + return rv; +} + +// static +BOOL LLLineEditor::prevalidateAlphaNumSpace(const LLWString &str) +{ + LLLocale locale(LLLocale::USER_LOCALE); + + BOOL rv = TRUE; + S32 len = str.length(); + if(len == 0) return rv; + while(len--) + { + if(!(isalnum(str[len]) || (' ' == str[len]))) + { + rv = FALSE; + break; + } + } + return rv; +} + +// static +BOOL LLLineEditor::prevalidatePrintableNotPipe(const LLWString &str) +{ + BOOL rv = TRUE; + S32 len = str.length(); + if(len == 0) return rv; + while(len--) + { + if('|' == str[len]) + { + rv = FALSE; + break; + } + if(!((' ' == str[len]) || isalnum(str[len]) || ispunct(str[len]))) + { + rv = FALSE; + break; + } + } + return rv; +} + + +// static +BOOL LLLineEditor::prevalidatePrintableNoSpace(const LLWString &str) +{ + BOOL rv = TRUE; + S32 len = str.length(); + if(len == 0) return rv; + while(len--) + { + if(iswspace(str[len])) + { + rv = FALSE; + break; + } + if( !(isalnum(str[len]) || ispunct(str[len]) ) ) + { + rv = FALSE; + break; + } + } + return rv; +} + +// static +BOOL LLLineEditor::prevalidateASCII(const LLWString &str) +{ + BOOL rv = TRUE; + S32 len = str.length(); + while(len--) + { + if (str[len] < 0x20 || str[len] > 0x7f) + { + rv = FALSE; + break; + } + } + return rv; +} + +//static +void LLLineEditor::onMouseCaptureLost( LLMouseHandler* old_captor ) +{ + LLLineEditor* self = (LLLineEditor*) old_captor; + self->endSelection(); +} + + +void LLLineEditor::setSelectAllonFocusReceived(BOOL b) +{ + mSelectAllonFocusReceived = b; +} + + +void LLLineEditor::setKeystrokeCallback(void (*keystroke_callback)(LLLineEditor* caller, void* user_data)) +{ + mKeystrokeCallback = keystroke_callback; +} + +void LLLineEditor::setFocusLostCallback(void (*keystroke_callback)(LLLineEditor* caller, void* user_data)) +{ + mFocusLostCallback = keystroke_callback; +} + +// virtual +LLXMLNodePtr LLLineEditor::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + node->createChild("max_length", TRUE)->setIntValue(mMaxLengthBytes); + + node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mGLFont)); + + if (mBorder) + { + LLString bevel; + switch(mBorder->getBevel()) + { + default: + case LLViewBorder::BEVEL_NONE: bevel = "none"; break; + case LLViewBorder::BEVEL_IN: bevel = "in"; break; + case LLViewBorder::BEVEL_OUT: bevel = "out"; break; + case LLViewBorder::BEVEL_BRIGHT:bevel = "bright"; break; + } + node->createChild("bevel_style", TRUE)->setStringValue(bevel); + + LLString style; + switch(mBorder->getStyle()) + { + default: + case LLViewBorder::STYLE_LINE: style = "line"; break; + case LLViewBorder::STYLE_TEXTURE: style = "texture"; break; + } + node->createChild("border_style", TRUE)->setStringValue(style); + + node->createChild("border_thickness", TRUE)->setIntValue(mBorder->getBorderWidth()); + } + + if (!mLabel.empty()) + { + node->createChild("label", TRUE)->setStringValue(mLabel.getString()); + } + + node->createChild("select_all_on_focus_received", TRUE)->setBoolValue(mSelectAllonFocusReceived); + + node->createChild("handle_edit_keys_directly", TRUE)->setBoolValue(mHandleEditKeysDirectly ); + + addColorXML(node, mCursorColor, "cursor_color", "TextCursorColor"); + addColorXML(node, mFgColor, "text_color", "TextFgColor"); + addColorXML(node, mReadOnlyFgColor, "text_readonly_color", "TextFgReadOnlyColor"); + addColorXML(node, mTentativeFgColor, "text_tentative_color", "TextFgTentativeColor"); + addColorXML(node, mReadOnlyBgColor, "bg_readonly_color", "TextBgReadOnlyColor"); + addColorXML(node, mWriteableBgColor, "bg_writeable_color", "TextBgWriteableColor"); + addColorXML(node, mFocusBgColor, "bg_focus_color", "TextBgFocusColor"); + + node->createChild("select_on_focus", TRUE)->setBoolValue(mSelectAllonFocusReceived ); + + return node; +} + +// static +LLView* LLLineEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("line_editor"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + S32 max_text_length = 128; + node->getAttributeS32("max_length", max_text_length); + + LLFontGL* font = LLView::selectFont(node); + + LLString text = node->getTextContents().substr(0, max_text_length - 1); + + LLViewBorder::EBevel bevel_style = LLViewBorder::BEVEL_IN; + LLViewBorder::getBevelFromAttribute(node, bevel_style); + + LLViewBorder::EStyle border_style = LLViewBorder::STYLE_LINE; + LLString border_string; + node->getAttributeString("border_style", border_string); + LLString::toLower(border_string); + + if (border_string == "texture") + { + border_style = LLViewBorder::STYLE_TEXTURE; + } + + S32 border_thickness = 1; + node->getAttributeS32("border_thickness", border_thickness); + + LLUICtrlCallback commit_callback = NULL; + + LLLineEditor* line_editor = new LLLineEditor(name, + rect, + text, + font, + max_text_length, + commit_callback, + NULL, + NULL, + NULL, + NULL, + bevel_style, + border_style, + border_thickness); + + LLString label; + if(node->getAttributeString("label", label)) + { + line_editor->setLabel(label); + } + BOOL select_all_on_focus_received = FALSE; + if (node->getAttributeBOOL("select_all_on_focus_received", select_all_on_focus_received)) + { + line_editor->setSelectAllonFocusReceived(select_all_on_focus_received); + } + BOOL handle_edit_keys_directly = FALSE; + if (node->getAttributeBOOL("handle_edit_keys_directly", handle_edit_keys_directly)) + { + line_editor->setHandleEditKeysDirectly(handle_edit_keys_directly); + } + + line_editor->setColorParameters(node); + + if(node->hasAttribute("select_on_focus")) + { + BOOL selectall = FALSE; + node->getAttributeBOOL("select_on_focus", selectall); + line_editor->setSelectAllonFocusReceived(selectall); + } + + LLString prevalidate; + if(node->getAttributeString("prevalidate", prevalidate)) + { + LLString::toLower(prevalidate); + + if ("ascii" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidateASCII ); + } + else if ("float" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidateFloat ); + } + else if ("int" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidateInt ); + } + else if ("positive_s32" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidatePositiveS32 ); + } + else if ("non_negative_s32" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidateNonNegativeS32 ); + } + else if ("alpha_num" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidateAlphaNum ); + } + else if ("alpha_num_space" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidateAlphaNumSpace ); + } + else if ("printable_not_pipe" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidatePrintableNotPipe ); + } + else if ("printable_no_space" == prevalidate) + { + line_editor->setPrevalidate( LLLineEditor::prevalidatePrintableNoSpace ); + } + } + + line_editor->initFromXML(node, parent); + + return line_editor; +} + +void LLLineEditor::setColorParameters(LLXMLNodePtr node) +{ + LLColor4 color; + if (LLUICtrlFactory::getAttributeColor(node,"cursor_color", color)) + { + setCursorColor(color); + } + if(node->hasAttribute("text_color")) + { + LLUICtrlFactory::getAttributeColor(node,"text_color", color); + setFgColor(color); + } + if(node->hasAttribute("text_readonly_color")) + { + LLUICtrlFactory::getAttributeColor(node,"text_readonly_color", color); + setReadOnlyFgColor(color); + } + if (LLUICtrlFactory::getAttributeColor(node,"text_tentative_color", color)) + { + setTentativeFgColor(color); + } + if(node->hasAttribute("bg_readonly_color")) + { + LLUICtrlFactory::getAttributeColor(node,"bg_readonly_color", color); + setReadOnlyBgColor(color); + } + if(node->hasAttribute("bg_writeable_color")) + { + LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color); + setWriteableBgColor(color); + } +} + +void LLLineEditor::setValue(const LLSD& value ) +{ + setText(value.asString()); +} + +LLSD LLLineEditor::getValue() const +{ + LLString str = getText(); + LLSD ret(str); + return ret; +} + +BOOL LLLineEditor::setTextArg( const LLString& key, const LLString& text ) +{ + mText.setArg(key, text); + return TRUE; +} + +BOOL LLLineEditor::setLabelArg( const LLString& key, const LLString& text ) +{ + mLabel.setArg(key, text); + return TRUE; +} + +LLSearchEditor::LLSearchEditor(const LLString& name, + const LLRect& rect, + S32 max_length_bytes, + void (*search_callback)(const LLString& search_string, void* user_data), + void* userdata) + : + LLUICtrl(name, rect, TRUE, NULL, userdata), + mSearchCallback(search_callback) +{ + LLRect search_edit_rect(0, mRect.getHeight(), mRect.getWidth(), 0); + mSearchEdit = new LLLineEditor("search edit", + search_edit_rect, + LLString::null, + NULL, + max_length_bytes, + NULL, + onSearchEdit, + NULL, + this); + // TODO: this should be translatable + mSearchEdit->setLabel("Type here to search"); + mSearchEdit->setFollowsAll(); + mSearchEdit->setSelectAllonFocusReceived(TRUE); + + addChild(mSearchEdit); + + S32 btn_width = rect.getHeight(); // button is square, and as tall as search editor + LLRect clear_btn_rect(rect.getWidth() - btn_width, rect.getHeight(), rect.getWidth(), 0); + mClearSearchButton = new LLButton("clear search", + clear_btn_rect, + "closebox.tga", + "UIImgBtnCloseInactiveUUID", + LLString::null, + onClearSearch, + this, + NULL, + LLString::null); + mClearSearchButton->setFollowsRight(); + mClearSearchButton->setFollowsTop(); + mClearSearchButton->setImageColor(LLUI::sColorsGroup->getColor("TextFgTentativeColor")); + mClearSearchButton->setTabStop(FALSE); + mSearchEdit->addChild(mClearSearchButton); + + mSearchEdit->setBorderWidth(0, btn_width); +} + +LLSearchEditor::~LLSearchEditor() +{ +} + +//virtual +EWidgetType LLSearchEditor::getWidgetType() const +{ + return WIDGET_TYPE_SEARCH_EDITOR; +} + +//virtual +LLString LLSearchEditor::getWidgetTag() const +{ + return LL_SEARCH_EDITOR_TAG; +} + +//virtual +void LLSearchEditor::setValue(const LLSD& value ) +{ + mSearchEdit->setValue(value); +} + +//virtual +LLSD LLSearchEditor::getValue() const +{ + return mSearchEdit->getValue(); +} + +//virtual +BOOL LLSearchEditor::setTextArg( const LLString& key, const LLString& text ) +{ + return mSearchEdit->setTextArg(key, text); +} + +//virtual +BOOL LLSearchEditor::setLabelArg( const LLString& key, const LLString& text ) +{ + return mSearchEdit->setLabelArg(key, text); +} + +//virtual +void LLSearchEditor::clear() +{ + if (mSearchEdit) + { + mSearchEdit->clear(); + } +} + + +void LLSearchEditor::draw() +{ + mClearSearchButton->setVisible(!mSearchEdit->getWText().empty()); + + LLUICtrl::draw(); +} + +void LLSearchEditor::setText(const LLString &new_text) +{ + mSearchEdit->setText(new_text); +} + +//static +void LLSearchEditor::onSearchEdit(LLLineEditor* caller, void* user_data ) +{ + LLSearchEditor* search_editor = (LLSearchEditor*)user_data; + if (search_editor->mSearchCallback) + { + search_editor->mSearchCallback(caller->getText(), search_editor->mCallbackUserData); + } +} + +//static +void LLSearchEditor::onClearSearch(void* user_data) +{ + LLSearchEditor* search_editor = (LLSearchEditor*)user_data; + + search_editor->setText(LLString::null); + if (search_editor->mSearchCallback) + { + search_editor->mSearchCallback(LLString::null, search_editor->mCallbackUserData); + } +} + +// static +LLView* LLSearchEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("search_editor"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + S32 max_text_length = 128; + node->getAttributeS32("max_length", max_text_length); + + LLString text = node->getValue().substr(0, max_text_length - 1); + + LLSearchEditor* search_editor = new LLSearchEditor(name, + rect, + max_text_length, + NULL, NULL); + + search_editor->setText(text); + + search_editor->initFromXML(node, parent); + + return search_editor; +} diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h new file mode 100644 index 0000000000..1df5dd88f7 --- /dev/null +++ b/indra/llui/lllineeditor.h @@ -0,0 +1,298 @@ +/** + * @file lllineeditor.h + * @brief LLLineEditor base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Text editor widget to let users enter/edit a single line. +// +// +// Features: +// Text entry of a single line (text, delete, left and right arrow, insert, return). +// Callbacks either on every keystroke or just on the return key. +// Focus (allow multiple text entry widgets) +// Clipboard (cut, copy, and paste) +// Horizontal scrolling to allow strings longer than widget size allows +// Pre-validation (limit which keys can be used) + + +#ifndef LL_LLLINEEDITOR_H +#define LL_LLLINEEDITOR_H + +#include "v4color.h" +#include "llframetimer.h" + +#include "lleditmenuhandler.h" +#include "lluictrl.h" +#include "lluistring.h" +#include "llviewborder.h" + +class LLFontGL; +class LLLineEditorRollback; +class LLButton; + +typedef BOOL (*LLLinePrevalidateFunc)(const LLWString &wstr); + +// +// Classes +// +class LLLineEditor +: public LLUICtrl, public LLEditMenuHandler +{ + friend class LLLineEditorRollback; + +public: + LLLineEditor(const LLString& name, + const LLRect& rect, + const LLString& default_text = LLString::null, + const LLFontGL* glfont = NULL, + S32 max_length_bytes = 254, + void (*commit_callback)(LLUICtrl* caller, void* user_data) = NULL, + void (*keystroke_callback)(LLLineEditor* caller, void* user_data) = NULL, + void (*focus_lost_callback)(LLLineEditor* caller, void* user_data) = NULL, + void* userdata = NULL, + LLLinePrevalidateFunc prevalidate_func = NULL, + LLViewBorder::EBevel border_bevel = LLViewBorder::BEVEL_IN, + LLViewBorder::EStyle border_style = LLViewBorder::STYLE_LINE, + S32 border_thickness = 1); + + virtual ~LLLineEditor(); + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + virtual LLXMLNodePtr getXML(bool save_children = true) const; + void setColorParameters(LLXMLNodePtr node); + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + // mousehandler overrides + /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleDoubleClick(S32 x,S32 y,MASK mask); + /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent ); + /*virtual*/ BOOL handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent); + + // LLEditMenuHandler overrides + virtual void cut(); + virtual BOOL canCut(); + + virtual void copy(); + virtual BOOL canCopy(); + + virtual void paste(); + virtual BOOL canPaste(); + + virtual void doDelete(); + virtual BOOL canDoDelete(); + + virtual void selectAll(); + virtual BOOL canSelectAll(); + + virtual void deselect(); + virtual BOOL canDeselect(); + + // view overrides + virtual void draw(); + virtual void reshape(S32 width,S32 height,BOOL called_from_parent=TRUE); + virtual void onFocusLost(); + virtual void setEnabled(BOOL enabled); + + // UI control overrides + virtual void clear(); + virtual void onTabInto(); + virtual void setFocus( BOOL b ); + virtual void setRect(const LLRect& rect); + virtual BOOL acceptsTextInput() const; + virtual void onCommit(); + + // assumes UTF8 text + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + virtual BOOL setTextArg( const LLString& key, const LLString& text ); + virtual BOOL setLabelArg( const LLString& key, const LLString& text ); + + void setLabel(const LLString &new_label); + void setText(const LLString &new_text); + + const LLString& getText() const { return mText.getString(); } + const LLWString& getWText() const { return mText.getWString(); } + S32 getLength() const { return mText.length(); } + + S32 getCursor() { return mCursorPos; } + void setCursor( S32 pos ); + void setCursorToEnd(); + + // Selects characters 'start' to 'end'. + void setSelection(S32 start, S32 end); + + void setCommitOnFocusLost( BOOL b ) { mCommitOnFocusLost = b; } + + void setCursorColor(const LLColor4& c) { mCursorColor = c; } + const LLColor4& getCursorColor() const { return mCursorColor; } + + void setFgColor( const LLColor4& c ) { mFgColor = c; } + void setReadOnlyFgColor( const LLColor4& c ) { mReadOnlyFgColor = c; } + void setTentativeFgColor(const LLColor4& c) { mTentativeFgColor = c; } + void setWriteableBgColor( const LLColor4& c ) { mWriteableBgColor = c; } + void setReadOnlyBgColor( const LLColor4& c ) { mReadOnlyBgColor = c; } + void setFocusBgColor(const LLColor4& c) { mFocusBgColor = c; } + + const LLColor4& getFgColor() const { return mFgColor; } + const LLColor4& getReadOnlyFgColor() const { return mReadOnlyFgColor; } + const LLColor4& getTentativeFgColor() const { return mTentativeFgColor; } + const LLColor4& getWriteableBgColor() const { return mWriteableBgColor; } + const LLColor4& getReadOnlyBgColor() const { return mReadOnlyBgColor; } + const LLColor4& getFocusBgColor() const { return mFocusBgColor; } + + void setIgnoreArrowKeys(BOOL b) { mIgnoreArrowKeys = b; } + void setIgnoreTab(BOOL b) { mIgnoreTab = b; } + void setPassDelete(BOOL b) { mPassDelete = b; } + + void setDrawAsterixes(BOOL b) { mDrawAsterixes = b; } + + // get the cursor position of the beginning/end of the prev/next word in the text + S32 prevWordPos(S32 cursorPos) const; + S32 nextWordPos(S32 cursorPos) const; + + BOOL hasSelection() { return (mSelectionStart != mSelectionEnd); } + void startSelection(); + void endSelection(); + void extendSelection(S32 new_cursor_pos); + void deleteSelection(); + + void setHandleEditKeysDirectly( BOOL b ) { mHandleEditKeysDirectly = b; } + void setSelectAllonFocusReceived(BOOL b); + + void setKeystrokeCallback(void (*keystroke_callback)(LLLineEditor* caller, void* user_data)); + void setFocusLostCallback(void (*keystroke_callback)(LLLineEditor* caller, void* user_data)); + + void setMaxTextLength(S32 max_text_length); + void setBorderWidth(S32 left, S32 right); + + static BOOL isPartOfWord(llwchar c); + // Prevalidation controls which keystrokes can affect the editor + void setPrevalidate( BOOL (*func)(const LLWString &) ) { mPrevalidateFunc = func; } + static BOOL prevalidateFloat(const LLWString &str ); + static BOOL prevalidateInt(const LLWString &str ); + static BOOL prevalidatePositiveS32(const LLWString &str); + static BOOL prevalidateNonNegativeS32(const LLWString &str); + static BOOL prevalidateAlphaNum(const LLWString &str ); + static BOOL prevalidateAlphaNumSpace(const LLWString &str ); + static BOOL prevalidatePrintableNotPipe(const LLWString &str); + static BOOL prevalidatePrintableNoSpace(const LLWString &str); + static BOOL prevalidateASCII(const LLWString &str); + + static BOOL postvalidateFloat(const LLString &str); + + static void onMouseCaptureLost( LLMouseHandler* old_captor ); + +protected: + void removeChar(); + void addChar(const llwchar c); + void setCursorAtLocalPos(S32 local_mouse_x); + + S32 findPixelNearestPos(S32 cursor_offset = 0); + void reportBadKeystroke(); + + BOOL handleSpecialKey(KEY key, MASK mask); + BOOL handleSelectionKey(KEY key, MASK mask); + BOOL handleControlKey(KEY key, MASK mask); + S32 handleCommitKey(KEY key, MASK mask); + +protected: + LLUIString mText; // The string being edited. + LLUIString mLabel; // text label that is visible when no user text provided + + LLViewBorder* mBorder; + const LLFontGL* mGLFont; + S32 mMaxLengthChars; // Max number of characters + S32 mMaxLengthBytes; // Max length of the UTF8 string. + S32 mCursorPos; // I-beam is just after the mCursorPos-th character. + S32 mScrollHPos; // Horizontal offset from the start of mText. Used for scrolling. + LLFrameTimer mScrollTimer; + S32 mMinHPixels; + S32 mMaxHPixels; + S32 mBorderLeft; + S32 mBorderRight; + + BOOL mCommitOnFocusLost; + + void (*mKeystrokeCallback)( LLLineEditor* caller, void* userdata ); + void (*mFocusLostCallback)( LLLineEditor* caller, void* userdata ); + + BOOL mIsSelecting; // Selection for clipboard operations + S32 mSelectionStart; + S32 mSelectionEnd; + S32 mLastSelectionX; + S32 mLastSelectionY; + + S32 (*mPrevalidateFunc)(const LLWString &str); + + LLFrameTimer mKeystrokeTimer; + + LLColor4 mCursorColor; + + LLColor4 mFgColor; + LLColor4 mReadOnlyFgColor; + LLColor4 mTentativeFgColor; + LLColor4 mWriteableBgColor; + LLColor4 mReadOnlyBgColor; + LLColor4 mFocusBgColor; + + S32 mBorderThickness; + + BOOL mIgnoreArrowKeys; + BOOL mIgnoreTab; + BOOL mDrawAsterixes; + + BOOL mHandleEditKeysDirectly; // If true, the standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system + BOOL mSelectAllonFocusReceived; + BOOL mPassDelete; + + BOOL mReadOnly; +}; + + +class LLSearchEditor : public LLUICtrl +{ +friend class LLLineEditorRollback; + +public: + LLSearchEditor(const LLString& name, + const LLRect& rect, + S32 max_length_bytes, + void (*search_callback)(const LLString& search_string, void* user_data), + void* userdata); + + virtual ~LLSearchEditor(); + + /*virtual*/ void draw(); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + void setText(const LLString &new_text); + + void setSearchCallback(void (*search_callback)(const LLString& search_string, void* user_data), void* data) { mSearchCallback = search_callback; mCallbackUserData = data; } + + // LLUICtrl interface + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + virtual BOOL setTextArg( const LLString& key, const LLString& text ); + virtual BOOL setLabelArg( const LLString& key, const LLString& text ); + virtual void clear(); + + +protected: + LLLineEditor* mSearchEdit; + LLButton* mClearSearchButton; + + void (*mSearchCallback)(const LLString& search_string, void* user_data); + + static void onSearchEdit(LLLineEditor* caller, void* user_data ); + static void onClearSearch(void* user_data); +}; + +#endif // LL_LINEEDITOR_ diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp new file mode 100644 index 0000000000..de06c34c44 --- /dev/null +++ b/indra/llui/llmenugl.cpp @@ -0,0 +1,4341 @@ +/** + * @file llmenugl.cpp + * @brief LLMenuItemGL base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//***************************************************************************** +// +// This file contains the opengl based menu implementation. +// +// NOTES: A menu label is split into 4 columns. The left column, the +// label colum, the accelerator column, and the right column. The left +// column is used for displaying boolean values for toggle and check +// controls. The right column is used for submenus. +// +//***************************************************************************** + +//#include "llviewerprecompiledheaders.h" +#include "linden_common.h" + +#include "llmenugl.h" + +#include "llmath.h" +#include "llgl.h" +#include "llfocusmgr.h" +#include "llfont.h" +#include "llcoord.h" +#include "llwindow.h" +#include "llcriticaldamp.h" +#include "lluictrlfactory.h" + +#include "llfontgl.h" +#include "llresmgr.h" +#include "llui.h" + +#include "llglheaders.h" +#include "llstl.h" + +#include "v2math.h" +#include +#include + +// static +LLView *LLMenuGL::sDefaultMenuContainer = NULL; + +S32 MENU_BAR_HEIGHT = 0; +S32 MENU_BAR_WIDTH = 0; + +///============================================================================ +/// Local function declarations, constants, enums, and typedefs +///============================================================================ + +const LLString SEPARATOR_NAME("separator"); +const LLString TEAROFF_SEPARATOR_LABEL( "~~~~~~~~~~~" ); +const LLString SEPARATOR_LABEL( "-----------" ); +const LLString VERTICAL_SEPARATOR_LABEL( "|" ); + +const S32 LABEL_BOTTOM_PAD_PIXELS = 2; + +const U32 LEFT_PAD_PIXELS = 3; +const U32 LEFT_WIDTH_PIXELS = 15; +const U32 LEFT_PLAIN_PIXELS = LEFT_PAD_PIXELS + LEFT_WIDTH_PIXELS; + +const U32 RIGHT_PAD_PIXELS = 2; +const U32 RIGHT_WIDTH_PIXELS = 15; +const U32 RIGHT_PLAIN_PIXELS = RIGHT_PAD_PIXELS + RIGHT_WIDTH_PIXELS; + +const U32 ACCEL_PAD_PIXELS = 10; +const U32 PLAIN_PAD_PIXELS = LEFT_PAD_PIXELS + LEFT_WIDTH_PIXELS + RIGHT_PAD_PIXELS + RIGHT_WIDTH_PIXELS; + +const U32 BRIEF_PAD_PIXELS = 2; + +const U32 SEPARATOR_HEIGHT_PIXELS = 8; +const S32 TEAROFF_SEPARATOR_HEIGHT_PIXELS = 10; +const S32 MENU_ITEM_PADDING = 4; + +const LLString BOOLEAN_TRUE_PREFIX( "X" ); +const LLString BRANCH_SUFFIX( ">" ); +const LLString ARROW_UP ("^^^^^^^"); +const LLString ARROW_DOWN("vvvvvvv"); + +const F32 MAX_MOUSE_SLOPE_SUB_MENU = 0.9f; + +const S32 PIE_GESTURE_ACTIVATE_DISTANCE = 10; + +LLColor4 LLMenuItemGL::sEnabledColor( 0.0f, 0.0f, 0.0f, 1.0f ); +LLColor4 LLMenuItemGL::sDisabledColor( 0.5f, 0.5f, 0.5f, 1.0f ); +LLColor4 LLMenuItemGL::sHighlightBackground( 0.0f, 0.0f, 0.7f, 1.0f ); +LLColor4 LLMenuItemGL::sHighlightForeground( 1.0f, 1.0f, 1.0f, 1.0f ); +BOOL LLMenuItemGL::sDropShadowText = TRUE; +LLColor4 LLMenuGL::sDefaultBackgroundColor( 0.25f, 0.25f, 0.25f, 0.75f ); + +LLViewHandle LLMenuHolderGL::sItemLastSelectedHandle; +LLFrameTimer LLMenuHolderGL::sItemActivationTimer; +//LLColor4 LLMenuGL::sBackgroundColor( 0.8f, 0.8f, 0.0f, 1.0f ); + +const S32 PIE_CENTER_SIZE = 20; // pixels, radius of center hole +const F32 PIE_SCALE_FACTOR = 1.7f; // scale factor for pie menu when mouse is initially down +const F32 PIE_SHRINK_TIME = 0.2f; // time of transition between unbounded and bounded display of pie menu + +const F32 ACTIVATE_HIGHLIGHT_TIME = 0.3f; + +///============================================================================ +/// Class LLMenuItemGL +///============================================================================ + +// Default constructor +LLMenuItemGL::LLMenuItemGL( const LLString& name, const LLString& label, KEY key, MASK mask ) : + LLView( name, TRUE ), + mJumpKey(KEY_NONE), + mAcceleratorKey( key ), + mAcceleratorMask( mask ), + mAllowKeyRepeat(FALSE), + mHighlight( FALSE ), + mGotHover( FALSE ), + mBriefItem( FALSE ), + mFont( LLFontGL::sSansSerif ), + mStyle(LLFontGL::NORMAL), + mDrawTextDisabled( FALSE ) +{ + setLabel( label ); +} + +// virtual +LLXMLNodePtr LLMenuItemGL::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLView::getXML(); + + node->createChild("type", TRUE)->setStringValue(getType()); + + node->createChild("label", TRUE)->setStringValue(mLabel); + + if (mAcceleratorKey != KEY_NONE) + { + std::stringstream out; + if (mAcceleratorMask & MASK_CONTROL) + { + out << "control|"; + } + if (mAcceleratorMask & MASK_ALT) + { + out << "alt|"; + } + if (mAcceleratorMask & MASK_SHIFT) + { + out << "shift|"; + } + out << LLKeyboard::stringFromKey(mAcceleratorKey); + + node->createChild("shortcut", TRUE)->setStringValue(out.str()); + } + + return node; +} + +BOOL LLMenuItemGL::handleKey(KEY key, MASK mask, BOOL called_from_parent) +{ + // modified from LLView::handleKey + // ignore visibility, as keyboard accelerators should still trigger menu items + // even when they are not visible + // also, ignore enabled flag for self, as that can change based on menu callbacks + BOOL handled = FALSE; + + if( called_from_parent ) + { + // Downward traversal + if (mEnabled) + { + handled = childrenHandleKey( key, mask ) != NULL; + } + } + + if( !handled ) + { + handled = handleKeyHere( key, mask, called_from_parent ); + } + + return handled; +} + +BOOL LLMenuItemGL::handleAcceleratorKey(KEY key, MASK mask) +{ + if( mEnabled && (!gKeyboard->getKeyRepeated(key) || mAllowKeyRepeat) && (key == mAcceleratorKey) && (mask == mAcceleratorMask) ) + { + doIt(); + return TRUE; + } + return FALSE; +} + +BOOL LLMenuItemGL::handleHover(S32 x, S32 y, MASK mask) +{ + mGotHover = TRUE; + getWindow()->setCursor(UI_CURSOR_ARROW); + return TRUE; +} + +void LLMenuItemGL::setBriefItem(BOOL b) +{ + mBriefItem = b; +} + +// This function checks to see if the accelerator key is already in use; +// if not, it will be added to the list +BOOL LLMenuItemGL::addToAcceleratorList(std::list *listp) +{ + LLKeyBinding *accelerator = NULL; + + if (mAcceleratorKey != KEY_NONE) + { + std::list::iterator list_it; + for (list_it = listp->begin(); list_it != listp->end(); ++list_it) + { + accelerator = *list_it; + if ((accelerator->mKey == mAcceleratorKey) && (accelerator->mMask == mAcceleratorMask)) + { + //FIXME: get calling code to throw up warning or route warning messages back to app-provided output + // LLString warning; + // warning.append("Duplicate key binding <"); + // appendAcceleratorString( warning ); + // warning.append("> for menu items:\n "); + // warning.append(accelerator->mName); + // warning.append("\n "); + // warning.append(mLabel); + + // llwarns << warning << llendl; + // LLAlertDialog::modalAlert(warning); + return FALSE; + } + } + if (!accelerator) + { + accelerator = new LLKeyBinding; + if (accelerator) + { + accelerator->mKey = mAcceleratorKey; + accelerator->mMask = mAcceleratorMask; +// accelerator->mName = mLabel; + } + listp->push_back(accelerator);//addData(accelerator); + } + } + return TRUE; +} + +// This function appends the character string representation of +// the current accelerator key and mask to the provided string. +void LLMenuItemGL::appendAcceleratorString( LLString& st ) +{ + // break early if this is a silly thing to do. + if( KEY_NONE == mAcceleratorKey ) + { + return; + } + + // Append any masks +#ifdef LL_DARWIN + // Standard Mac names for modifier keys in menu equivalents + // We could use the symbol characters, but they only exist in certain fonts. + if( mAcceleratorMask & MASK_CONTROL ) + st.append( "Cmd-" ); // Symbol would be "\xE2\x8C\x98" + if( mAcceleratorMask & MASK_ALT ) + st.append( "Opt-" ); // Symbol would be "\xE2\x8C\xA5" + if( mAcceleratorMask & MASK_SHIFT ) + st.append( "Shift-" ); // Symbol would be "\xE2\x8C\xA7" +#else + if( mAcceleratorMask & MASK_CONTROL ) + st.append( "Ctrl-" ); + if( mAcceleratorMask & MASK_ALT ) + st.append( "Alt-" ); + if( mAcceleratorMask & MASK_SHIFT ) + st.append( "Shift-" ); +#endif + + LLString keystr = LLKeyboard::stringFromKey( mAcceleratorKey ); + if ((mAcceleratorMask & (MASK_CONTROL|MASK_ALT|MASK_SHIFT)) && + (keystr[0] == '-' || keystr[0] == '=')) + { + st.append( " " ); + } + st.append( keystr ); +} + +void LLMenuItemGL::setJumpKey(KEY key) +{ + mJumpKey = LLStringOps::toUpper((char)key); +} + +KEY LLMenuItemGL::getJumpKey() +{ + return mJumpKey; +} + + +// set the font used by all of the menu objects +void LLMenuItemGL::setFont(LLFontGL* font) +{ + mFont = font; +} + +// returns the height in pixels for the current font. +U32 LLMenuItemGL::getNominalHeight( void ) +{ + return llround(mFont->getLineHeight()) + MENU_ITEM_PADDING; +} + +// functions to control the color scheme +void LLMenuItemGL::setEnabledColor( const LLColor4& color ) +{ + sEnabledColor = color; +} + +void LLMenuItemGL::setDisabledColor( const LLColor4& color ) +{ + sDisabledColor = color; +} + +void LLMenuItemGL::setHighlightBGColor( const LLColor4& color ) +{ + sHighlightBackground = color; +} + +void LLMenuItemGL::setHighlightFGColor( const LLColor4& color ) +{ + sHighlightForeground = color; +} + + +// change the label +void LLMenuItemGL::setLabel( const LLString& label ) +{ + mLabel = label; +} + +// Get the parent menu for this item +LLMenuGL* LLMenuItemGL::getMenu() +{ + return (LLMenuGL*) getParent(); +} + + +// getNominalWidth() - returns the normal width of this control in +// pixels - this is used for calculating the widest item, as well as +// for horizontal arrangement. +U32 LLMenuItemGL::getNominalWidth( void ) +{ + U32 width; + + if (mBriefItem) + { + width = BRIEF_PAD_PIXELS; + } + else + { + width = PLAIN_PAD_PIXELS; + } + + if( KEY_NONE != mAcceleratorKey ) + { + width += ACCEL_PAD_PIXELS; + LLString temp; + appendAcceleratorString( temp ); + width += mFont->getWidth( temp ); + } + width += mFont->getWidth( mLabel.getWString().c_str() ); + return width; +} + +// called to rebuild the draw label +void LLMenuItemGL::buildDrawLabel( void ) +{ + mDrawAccelLabel.clear(); + LLString st = mDrawAccelLabel.getString(); + appendAcceleratorString( st ); + mDrawAccelLabel = st; +} + +// set the hover status (called by it's menu) + void LLMenuItemGL::setHighlight( BOOL highlight ) +{ + mHighlight = highlight; +} + +// determine if this object is active +BOOL LLMenuItemGL::isActive( void ) const +{ + return FALSE; +} + +BOOL LLMenuItemGL::handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ) +{ + if (mHighlight && + getMenu()->getVisible() && + (!getMenu()->getTornOff() || ((LLFloater*)getMenu()->getParent())->hasFocus())) + { + if (key == KEY_UP) + { + getMenu()->highlightPrevItem(this); + return TRUE; + } + else if (key == KEY_DOWN) + { + getMenu()->highlightNextItem(this); + return TRUE; + } + else if (key == KEY_RETURN && mask == MASK_NONE) + { + doIt(); + if (!getMenu()->getTornOff()) + { + ((LLMenuHolderGL*)getMenu()->getParent())->hideMenus(); + } + return TRUE; + } + } + + return FALSE; +} + +BOOL LLMenuItemGL::handleMouseUp( S32 x, S32 y, MASK ) +{ + //llinfos << mLabel.c_str() << " handleMouseUp " << x << "," << y + // << llendl; + if (mEnabled) + { + doIt(); + mHighlight = FALSE; + make_ui_sound("UISndClickRelease"); + return TRUE; + } + else + { + return FALSE; + } +} + +void LLMenuItemGL::draw( void ) +{ + // *FIX: This can be optimized by using switches. Want to avoid + // that until the functionality is finalized. + + // HACK: Brief items don't highlight. Pie menu takes care of it. JC + if( mHighlight && !mBriefItem) + { + glColor4fv( sHighlightBackground.mV ); + gl_rect_2d( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + } + + LLColor4 color; + + U8 font_style = mStyle; + if (LLMenuItemGL::sDropShadowText && getEnabled() && !mDrawTextDisabled ) + { + font_style |= LLFontGL::DROP_SHADOW; + } + + if ( mHighlight ) + { + color = sHighlightForeground; + } + else if( getEnabled() && !mDrawTextDisabled ) + { + color = sEnabledColor; + } + else + { + color = sDisabledColor; + } + + // Draw the text on top. + if (mBriefItem) + { + mFont->render( mLabel, 0, BRIEF_PAD_PIXELS / 2, 0, color, + LLFontGL::LEFT, LLFontGL::BOTTOM, font_style ); + } + else + { + if( !mDrawBoolLabel.empty() ) + { + mFont->render( mDrawBoolLabel.getWString(), 0, (F32)LEFT_PAD_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f) + 1.f, color, + LLFontGL::LEFT, LLFontGL::BOTTOM, font_style, S32_MAX, S32_MAX, NULL, FALSE ); + } + mFont->render( mLabel.getWString(), 0, (F32)LEFT_PLAIN_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f) + 1.f, color, + LLFontGL::LEFT, LLFontGL::BOTTOM, font_style, S32_MAX, S32_MAX, NULL, FALSE ); + if( !mDrawAccelLabel.empty() ) + { + mFont->render( mDrawAccelLabel.getWString(), 0, (F32)mRect.mRight - (F32)RIGHT_PLAIN_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f) + 1.f, color, + LLFontGL::RIGHT, LLFontGL::BOTTOM, font_style, S32_MAX, S32_MAX, NULL, FALSE ); + } + if( !mDrawBranchLabel.empty() ) + { + mFont->render( mDrawBranchLabel.getWString(), 0, (F32)mRect.mRight - (F32)RIGHT_PAD_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f) + 1.f, color, + LLFontGL::RIGHT, LLFontGL::BOTTOM, font_style, S32_MAX, S32_MAX, NULL, FALSE ); + } + } + + // underline navigation key + BOOL draw_jump_key = gKeyboard->currentMask(FALSE) == MASK_ALT && + (!getMenu()->getHighlightedItem() || !getMenu()->getHighlightedItem()->isActive()) && + (!getMenu()->getTornOff()); + if (draw_jump_key) + { + LLString upper_case_label = mLabel.getString(); + LLString::toUpper(upper_case_label); + std::string::size_type offset = upper_case_label.find(mJumpKey); + if (offset != std::string::npos) + { + S32 x_begin = LEFT_PLAIN_PIXELS + mFont->getWidth(mLabel, 0, offset); + S32 x_end = LEFT_PLAIN_PIXELS + mFont->getWidth(mLabel, 0, offset + 1); + gl_line_2d(x_begin, (MENU_ITEM_PADDING / 2) + 1, x_end, (MENU_ITEM_PADDING / 2) + 1); + } + } + + // clear got hover every frame + mGotHover = FALSE; +} + +BOOL LLMenuItemGL::setLabelArg( const LLString& key, const LLString& text ) +{ + mLabel.setArg(key, text); + return TRUE; +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemSeparatorGL +// +// This class represents a separator. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemSeparatorGL : public LLMenuItemGL +{ +public: + LLMenuItemSeparatorGL( const LLString &name = SEPARATOR_NAME ); + + virtual LLString getType() const { return "separator"; } + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_MENU_ITEM_SEPARATOR; } + virtual LLString getWidgetTag() const { return LL_MENU_ITEM_SEPARATOR_GL_TAG; } + + // doIt() - do the primary funcationality of the menu item. + virtual void doIt( void ) {} + + virtual void draw( void ); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + + virtual U32 getNominalHeight( void ) { return SEPARATOR_HEIGHT_PIXELS; } +}; + +LLMenuItemSeparatorGL::LLMenuItemSeparatorGL( const LLString &name ) : + LLMenuItemGL( SEPARATOR_NAME, SEPARATOR_LABEL ) +{ +} + +void LLMenuItemSeparatorGL::draw( void ) +{ + glColor4fv( sDisabledColor.mV ); + const S32 y = mRect.getHeight() / 2; + const S32 PAD = 6; + gl_line_2d( PAD, y, mRect.getWidth() - PAD, y ); +} + +BOOL LLMenuItemSeparatorGL::handleMouseDown(S32 x, S32 y, MASK mask) +{ + LLMenuGL* parent_menu = getMenu(); + if (y > mRect.getHeight() / 2) + { + return parent_menu->handleMouseDown(x + mRect.mLeft, mRect.mTop + 1, mask); + } + else + { + return parent_menu->handleMouseDown(x + mRect.mLeft, mRect.mBottom - 1, mask); + } +} + +BOOL LLMenuItemSeparatorGL::handleMouseUp(S32 x, S32 y, MASK mask) +{ + LLMenuGL* parent_menu = getMenu(); + if (y > mRect.getHeight() / 2) + { + return parent_menu->handleMouseUp(x + mRect.mLeft, mRect.mTop + 1, mask); + } + else + { + return parent_menu->handleMouseUp(x + mRect.mLeft, mRect.mBottom - 1, mask); + } +} + +BOOL LLMenuItemSeparatorGL::handleHover(S32 x, S32 y, MASK mask) +{ + LLMenuGL* parent_menu = getMenu(); + if (y > mRect.getHeight() / 2) + { + parent_menu->highlightPrevItem(this, FALSE); + return FALSE; + } + else + { + parent_menu->highlightNextItem(this, FALSE); + return FALSE; + } +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemVerticalSeparatorGL +// +// This class represents a vertical separator. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemVerticalSeparatorGL +: public LLMenuItemSeparatorGL +{ +public: + LLMenuItemVerticalSeparatorGL( void ); + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_MENU_SEPARATOR_VERTICAL; } + virtual LLString getWidgetTag() const { return LL_MENU_ITEM_VERTICAL_SEPARATOR_GL_TAG; } + + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask) { return FALSE; } +}; + +LLMenuItemVerticalSeparatorGL::LLMenuItemVerticalSeparatorGL( void ) +{ + setLabel( VERTICAL_SEPARATOR_LABEL ); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemTearOffGL +// +// This class represents a separator. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +LLMenuItemTearOffGL::LLMenuItemTearOffGL(LLViewHandle parent_floater_handle) : + LLMenuItemGL("tear off", TEAROFF_SEPARATOR_LABEL), + mParentHandle(parent_floater_handle) +{ +} + +EWidgetType LLMenuItemTearOffGL::getWidgetType() const +{ + return WIDGET_TYPE_TEAROFF_MENU; +} + +LLString LLMenuItemTearOffGL::getWidgetTag() const +{ + return LL_MENU_ITEM_TEAR_OFF_GL_TAG; +} + +void LLMenuItemTearOffGL::doIt() +{ + if (getMenu()->getTornOff()) + { + LLTearOffMenu* torn_off_menu = (LLTearOffMenu*)(getMenu()->getParent()); + torn_off_menu->close(); + } + else + { + // transfer keyboard focus and highlight to first real item in list + if (getHighlight()) + { + getMenu()->highlightNextItem(this); + } + + // grab menu holder before this menu is parented to a floater + LLMenuHolderGL* menu_holder = ((LLMenuHolderGL*)getMenu()->getParent()); + getMenu()->arrange(); + + LLFloater* parent_floater = LLFloater::getFloaterByHandle(mParentHandle); + LLFloater* tear_off_menu = LLTearOffMenu::create(getMenu()); + if (parent_floater && tear_off_menu) + { + parent_floater->addDependentFloater(tear_off_menu, FALSE); + } + + // hide menus + // only do it if the menu is open, not being triggered via accelerator + if (getMenu()->getVisible()) + { + menu_holder->hideMenus(); + } + + // give focus to torn off menu because it will have been taken away + // when parent menu closes + tear_off_menu->setFocus(TRUE); + } +} + +void LLMenuItemTearOffGL::draw() +{ + if( mHighlight && !mBriefItem) + { + glColor4fv( sHighlightBackground.mV ); + gl_rect_2d( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + } + + if (mEnabled) + { + glColor4fv( sEnabledColor.mV ); + } + else + { + glColor4fv( sDisabledColor.mV ); + } + const S32 y = mRect.getHeight() / 3; + const S32 PAD = 6; + gl_line_2d( PAD, y, mRect.getWidth() - PAD, y ); + gl_line_2d( PAD, y * 2, mRect.getWidth() - PAD, y * 2 ); +} + +U32 LLMenuItemTearOffGL::getNominalHeight( void ) { return TEAROFF_SEPARATOR_HEIGHT_PIXELS; } + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemBlankGL +// +// This class represents a blank, non-functioning item. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemBlankGL : public LLMenuItemGL +{ +public: + LLMenuItemBlankGL( void ); + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_MENU_ITEM_BLANK; } + virtual LLString getWidgetTag() const { return LL_MENU_ITEM_BLANK_GL_TAG; } + + // doIt() - do the primary funcationality of the menu item. + virtual void doIt( void ) {} + + virtual void draw( void ) {} +}; + +LLMenuItemBlankGL::LLMenuItemBlankGL( void ) +: LLMenuItemGL( "", "" ) +{ + mEnabled = FALSE; +} + +///============================================================================ +/// Class LLMenuItemCallGL +///============================================================================ + +LLMenuItemCallGL::LLMenuItemCallGL( const LLString& name, + const LLString& label, + menu_callback clicked_cb, + enabled_callback enabled_cb, + void* user_data, + KEY key, MASK mask, + BOOL enabled, + on_disabled_callback on_disabled_cb) : + LLMenuItemGL( name, label, key, mask ), + mCallback( clicked_cb ), + mEnabledCallback( enabled_cb ), + mLabelCallback(NULL), + mUserData( user_data ), + mOnDisabledCallback(on_disabled_cb) +{ + if(!enabled) setEnabled(FALSE); +} + +LLMenuItemCallGL::LLMenuItemCallGL( const LLString& name, + menu_callback clicked_cb, + enabled_callback enabled_cb, + void* user_data, + KEY key, MASK mask, + BOOL enabled, + on_disabled_callback on_disabled_cb) : + LLMenuItemGL( name, name, key, mask ), + mCallback( clicked_cb ), + mEnabledCallback( enabled_cb ), + mLabelCallback(NULL), + mUserData( user_data ), + mOnDisabledCallback(on_disabled_cb) +{ + if(!enabled) setEnabled(FALSE); +} + +LLMenuItemCallGL::LLMenuItemCallGL(const LLString& name, + const LLString& label, + menu_callback clicked_cb, + enabled_callback enabled_cb, + label_callback label_cb, + void* user_data, + KEY key, MASK mask, + BOOL enabled, + on_disabled_callback on_disabled_cb) : + LLMenuItemGL(name, label, key, mask), + mCallback(clicked_cb), + mEnabledCallback(enabled_cb), + mLabelCallback(label_cb), + mUserData(user_data), + mOnDisabledCallback(on_disabled_cb) +{ + if(!enabled) setEnabled(FALSE); +} + +LLMenuItemCallGL::LLMenuItemCallGL(const LLString& name, + menu_callback clicked_cb, + enabled_callback enabled_cb, + label_callback label_cb, + void* user_data, + KEY key, MASK mask, + BOOL enabled, + on_disabled_callback on_disabled_cb) : + LLMenuItemGL(name, name, key, mask), + mCallback(clicked_cb), + mEnabledCallback(enabled_cb), + mLabelCallback(label_cb), + mUserData(user_data), + mOnDisabledCallback(on_disabled_cb) +{ + if(!enabled) setEnabled(FALSE); +} + +void LLMenuItemCallGL::setEnabledControl(LLString enabled_control, LLView *context) +{ + // Register new listener + if (!enabled_control.empty()) + { + LLControlBase *control = context->findControl(enabled_control); + if (control) + { + LLSD state = control->registerListener(this, "ENABLED"); + setEnabled(state); + } + else + { + context->addBoolControl(enabled_control, mEnabled); + control = context->findControl(enabled_control); + control->registerListener(this, "ENABLED"); + } + } +} + +void LLMenuItemCallGL::setVisibleControl(LLString enabled_control, LLView *context) +{ + // Register new listener + if (!enabled_control.empty()) + { + LLControlBase *control = context->findControl(enabled_control); + if (control) + { + LLSD state = control->registerListener(this, "VISIBLE"); + setVisible(state); + } + else + { + context->addBoolControl(enabled_control, mEnabled); + control = context->findControl(enabled_control); + control->registerListener(this, "VISIBLE"); + } + } +} + +// virtual +bool LLMenuItemCallGL::handleEvent(LLPointer event, const LLSD& userdata) +{ + if (userdata.asString() == "ENABLED" && event->desc() == "value_changed") + { + LLSD state = event->getValue(); + setEnabled(state); + return TRUE; + } + if (userdata.asString() == "VISIBLE" && event->desc() == "value_changed") + { + LLSD state = event->getValue(); + setVisible(state); + return TRUE; + } + return LLMenuItemGL::handleEvent(event, userdata); +} + +// virtual +LLXMLNodePtr LLMenuItemCallGL::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLMenuItemGL::getXML(); + + // Contents + + std::vector listeners = mDispatcher->getListeners(); + std::vector::iterator itor; + for (itor = listeners.begin(); itor != listeners.end(); ++itor) + { + LLString listener_name = findEventListener((LLSimpleListener*)itor->listener); + if (!listener_name.empty()) + { + LLXMLNodePtr child_node = node->createChild("on_click", FALSE); + child_node->createChild("function", TRUE)->setStringValue(listener_name); + child_node->createChild("filter", TRUE)->setStringValue(itor->filter.asString()); + child_node->createChild("userdata", TRUE)->setStringValue(itor->userdata.asString()); + } + } + + return node; +} + +// doIt() - Call the callback provided +void LLMenuItemCallGL::doIt( void ) +{ + // RN: menu item can be deleted in callback, so beware + getMenu()->setItemLastSelected( this ); + + if( mCallback ) + { + mCallback( mUserData ); + } + LLPointer fired_event = new LLEvent(this); + fireEvent(fired_event, "on_click"); +} + +EWidgetType LLMenuItemCallGL::getWidgetType() const +{ + return WIDGET_TYPE_MENU_ITEM_CALL; +} + +LLString LLMenuItemCallGL::getWidgetTag() const +{ + return LL_MENU_ITEM_CALL_GL_TAG; +} + +void LLMenuItemCallGL::buildDrawLabel( void ) +{ + LLPointer fired_event = new LLEvent(this); + fireEvent(fired_event, "on_build"); + if( mEnabledCallback ) + { + setEnabled( mEnabledCallback( mUserData ) ); + } + if(mLabelCallback) + { + LLString label; + mLabelCallback(label, mUserData); + mLabel = label; + } + LLMenuItemGL::buildDrawLabel(); +} + +BOOL LLMenuItemCallGL::handleAcceleratorKey( KEY key, MASK mask ) +{ + if( (!gKeyboard->getKeyRepeated(key) || mAllowKeyRepeat) && (key == mAcceleratorKey) && (mask == mAcceleratorMask) ) + { + LLPointer fired_event = new LLEvent(this); + fireEvent(fired_event, "on_build"); + if( mEnabledCallback ) + { + setEnabled( mEnabledCallback( mUserData ) ); + } + if( !mEnabled ) + { + if( mOnDisabledCallback ) + { + mOnDisabledCallback( mUserData ); + } + } + } + return LLMenuItemGL::handleAcceleratorKey(key, mask); +} + +///============================================================================ +/// Class LLMenuItemCheckGL +///============================================================================ + +LLMenuItemCheckGL::LLMenuItemCheckGL ( const LLString& name, + const LLString& label, + menu_callback clicked_cb, + enabled_callback enabled_cb, + check_callback check_cb, + void* user_data, + KEY key, MASK mask ) : + LLMenuItemCallGL( name, label, clicked_cb, enabled_cb, user_data, key, mask ), + mCheckCallback( check_cb ), + mChecked(FALSE) +{ +} + +LLMenuItemCheckGL::LLMenuItemCheckGL ( const LLString& name, + menu_callback clicked_cb, + enabled_callback enabled_cb, + check_callback check_cb, + void* user_data, + KEY key, MASK mask ) : + LLMenuItemCallGL( name, name, clicked_cb, enabled_cb, user_data, key, mask ), + mCheckCallback( check_cb ), + mChecked(FALSE) +{ +} + +LLMenuItemCheckGL::LLMenuItemCheckGL ( const LLString& name, + const LLString& label, + menu_callback clicked_cb, + enabled_callback enabled_cb, + LLString control_name, + LLView *context, + void* user_data, + KEY key, MASK mask ) : + LLMenuItemCallGL( name, label, clicked_cb, enabled_cb, user_data, key, mask ), + mCheckCallback( NULL ) +{ + setControlName(control_name, context); +} + +void LLMenuItemCheckGL::setCheckedControl(LLString checked_control, LLView *context) +{ + // Register new listener + if (!checked_control.empty()) + { + LLControlBase *control = context->findControl(checked_control); + if (control) + { + LLSD state = control->registerListener(this, "CHECKED"); + mChecked = state; + } + else + { + context->addBoolControl(checked_control, mChecked); + control = context->findControl(checked_control); + control->registerListener(this, "CHECKED"); + } + } +} + +// virtual +bool LLMenuItemCheckGL::handleEvent(LLPointer event, const LLSD& userdata) +{ + if (userdata.asString() == "CHECKED" && event->desc() == "value_changed") + { + LLSD state = event->getValue(); + mChecked = state; + if(mChecked) + { + mDrawBoolLabel = BOOLEAN_TRUE_PREFIX; + } + else + { + mDrawBoolLabel.clear(); + } + return TRUE; + } + return LLMenuItemCallGL::handleEvent(event, userdata); +} + +// virtual +LLXMLNodePtr LLMenuItemCheckGL::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLMenuItemCallGL::getXML(); + return node; +} + +EWidgetType LLMenuItemCheckGL::getWidgetType() const +{ + return WIDGET_TYPE_MENU_ITEM_CHECK; +} + +LLString LLMenuItemCheckGL::getWidgetTag() const +{ + return LL_MENU_ITEM_CHECK_GL_TAG; +} + +// called to rebuild the draw label +void LLMenuItemCheckGL::buildDrawLabel( void ) +{ + if(mChecked || (mCheckCallback && mCheckCallback( mUserData ) ) ) + { + mDrawBoolLabel = BOOLEAN_TRUE_PREFIX; + } + else + { + mDrawBoolLabel.clear(); + } + LLMenuItemCallGL::buildDrawLabel(); +} + + +///============================================================================ +/// Class LLMenuItemToggleGL +///============================================================================ + +LLMenuItemToggleGL::LLMenuItemToggleGL( const LLString& name, const LLString& label, BOOL* toggle, + KEY key, MASK mask ) : + LLMenuItemGL( name, label, key, mask ), + mToggle( toggle ) +{ +} + +LLMenuItemToggleGL::LLMenuItemToggleGL( const LLString& name, BOOL* toggle, + KEY key, MASK mask ) : + LLMenuItemGL( name, name, key, mask ), + mToggle( toggle ) +{ +} + + +// called to rebuild the draw label +void LLMenuItemToggleGL::buildDrawLabel( void ) +{ + if( *mToggle ) + { + mDrawBoolLabel = BOOLEAN_TRUE_PREFIX; + } + else + { + mDrawBoolLabel.clear(); + } + mDrawAccelLabel.clear(); + LLString st = mDrawAccelLabel; + appendAcceleratorString( st ); + mDrawAccelLabel = st; +} + +// doIt() - do the primary funcationality of the menu item. +void LLMenuItemToggleGL::doIt( void ) +{ + getMenu()->setItemLastSelected( this ); + //llinfos << "LLMenuItemToggleGL::doIt " << mLabel.c_str() << llendl; + *mToggle = !(*mToggle); + buildDrawLabel(); +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemBranchGL +// +// The LLMenuItemBranchGL represents a menu item that has a +// sub-menu. This is used to make cascading menus. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemBranchGL : public LLMenuItemGL +{ +protected: + LLMenuGL* mBranch; + +public: + LLMenuItemBranchGL( const LLString& name, const LLString& label, LLMenuGL* branch, + KEY key = KEY_NONE, MASK mask = MASK_NONE ); + virtual LLXMLNodePtr getXML(bool save_children = true) const; + + virtual LLView* getChildByName(const LLString& name, BOOL recurse) const; + + virtual LLString getType() const { return "menu"; } + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + + virtual BOOL handleAcceleratorKey(KEY key, MASK mask); + + // check if we've used these accelerators already + virtual BOOL addToAcceleratorList(std::list *listp); + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + // doIt() - do the primary funcationality of the menu item. + virtual void doIt( void ); + + virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); + + // set the hover status (called by it's menu) and if the object is + // active. This is used for behavior transfer. + virtual void setHighlight( BOOL highlight ); + + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + + virtual BOOL isActive() const { return !mBranch->getTornOff() && mBranch->getVisible(); } + + LLMenuGL *getBranch() const { return mBranch; } + + virtual void updateBranchParent( LLView* parentp ); + + // LLView Functionality + virtual void onVisibilityChange( BOOL curVisibilityIn ); + + virtual void draw(); + + virtual void setEnabledSubMenus(BOOL enabled); +}; + +LLMenuItemBranchGL::LLMenuItemBranchGL( const LLString& name, const LLString& label, LLMenuGL* branch, + KEY key, MASK mask ) : + LLMenuItemGL( name, label, key, mask ), + mBranch( branch ) +{ + mBranch->setVisible( FALSE ); + mBranch->setParentMenuItem(this); +} + +// virtual +LLView* LLMenuItemBranchGL::getChildByName(const LLString& name, BOOL recurse) const +{ + if (mBranch->getName() == name) + { + return mBranch; + } + // Always recurse on branches + return mBranch->getChildByName(name, recurse); +} + +EWidgetType LLMenuItemBranchGL::getWidgetType() const +{ + return WIDGET_TYPE_MENU_ITEM_BRANCH; +} + +LLString LLMenuItemBranchGL::getWidgetTag() const +{ + return LL_MENU_ITEM_BRANCH_GL_TAG; +} + +// virtual +BOOL LLMenuItemBranchGL::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (mEnabled) + { + doIt(); + make_ui_sound("UISndClickRelease"); + } + return FALSE; +} + +BOOL LLMenuItemBranchGL::handleAcceleratorKey(KEY key, MASK mask) +{ + return mBranch->handleAcceleratorKey(key, mask); +} + +// virtual +LLXMLNodePtr LLMenuItemBranchGL::getXML(bool save_children) const +{ + if (mBranch) + { + return mBranch->getXML(); + } + + return LLMenuItemGL::getXML(); +} + + +// This function checks to see if the accelerator key is already in use; +// if not, it will be added to the list +BOOL LLMenuItemBranchGL::addToAcceleratorList(std::list *listp) +{ + U32 item_count = mBranch->getItemCount(); + LLMenuItemGL *item; + + while (item_count--) + { + if ((item = mBranch->getItem(item_count))) + { + return item->addToAcceleratorList(listp); + } + } + return FALSE; +} + + +// called to rebuild the draw label +void LLMenuItemBranchGL::buildDrawLabel( void ) +{ + mDrawAccelLabel.clear(); + LLString st = mDrawAccelLabel; + appendAcceleratorString( st ); + mDrawAccelLabel = st; + mDrawBranchLabel = BRANCH_SUFFIX; +} + +// doIt() - do the primary functionality of the menu item. +void LLMenuItemBranchGL::doIt( void ) +{ + if (mBranch->getTornOff()) + { + gFloaterView->bringToFront((LLFloater*)mBranch->getParent()); + // this might not be necessary, as torn off branches don't get focus and hence no highligth + mBranch->highlightNextItem(NULL); + } + else if( !mBranch->getVisible() ) + { + mBranch->arrange(); + + LLRect rect = mBranch->getRect(); + // calculate root-view relative position for branch menu + S32 left = mRect.mRight; + S32 top = mRect.mTop - mRect.mBottom; + + localPointToOtherView(left, top, &left, &top, mBranch->getParent()); + + rect.setLeftTopAndSize( left, top, + rect.getWidth(), rect.getHeight() ); + + if (mBranch->getCanTearOff()) + { + rect.translate(0, TEAROFF_SEPARATOR_HEIGHT_PIXELS); + } + mBranch->setRect( rect ); + S32 x = 0; + S32 y = 0; + mBranch->localPointToOtherView( 0, 0, &x, &y, mBranch->getParent() ); + S32 delta_x = 0; + S32 delta_y = 0; + if( y < 0 ) + { + delta_y = -y; + } + + S32 window_width = mBranch->getParent()->getRect().getWidth(); + if( x > window_width - rect.getWidth() ) + { + // move sub-menu over to left side + delta_x = llmax(-x, (-1 * (rect.getWidth() + mRect.getWidth()))); + } + mBranch->translate( delta_x, delta_y ); + mBranch->setVisible( TRUE ); + } +} + +BOOL LLMenuItemBranchGL::handleKey(KEY key, MASK mask, BOOL called_from_parent) +{ + BOOL handled = FALSE; + if (called_from_parent) + { + handled = mBranch->handleKey(key, mask, called_from_parent); + } + + if (!handled) + { + handled = LLMenuItemGL::handleKey(key, mask, called_from_parent); + } + + return handled; +} + +// set the hover status (called by it's menu) +void LLMenuItemBranchGL::setHighlight( BOOL highlight ) +{ + BOOL auto_open = mEnabled && (!mBranch->getVisible() || mBranch->getTornOff()); + // torn off menus don't open sub menus on hover unless they have focus + if (getMenu()->getTornOff() && !((LLFloater*)getMenu()->getParent())->hasFocus()) + { + auto_open = FALSE; + } + // don't auto open torn off sub-menus (need to explicitly active menu item to give them focus) + if (mBranch->getTornOff()) + { + auto_open = FALSE; + } + + mHighlight = highlight; + if( highlight ) + { + if(auto_open) + { + doIt(); + } + } + else + { + if (mBranch->getTornOff()) + { + ((LLFloater*)mBranch->getParent())->setFocus(FALSE); + mBranch->clearHoverItem(); + } + else + { + mBranch->setVisible( FALSE ); + } + } +} + +void LLMenuItemBranchGL::setEnabledSubMenus(BOOL enabled) +{ + mBranch->setEnabledSubMenus(enabled); +} + +void LLMenuItemBranchGL::draw() +{ + LLMenuItemGL::draw(); + if (mBranch->getVisible() && !mBranch->getTornOff()) + { + mHighlight = TRUE; + } +} + +void LLMenuItemBranchGL::updateBranchParent(LLView* parentp) +{ + if (mBranch->getParent() == NULL) + { + // make the branch menu a sibling of my parent menu + mBranch->updateParent(parentp); + } +} + +void LLMenuItemBranchGL::onVisibilityChange( BOOL curVisibilityIn ) +{ + if (curVisibilityIn == FALSE && mBranch->getVisible() && !mBranch->getTornOff()) + { + mBranch->setVisible(FALSE); + } +} + +BOOL LLMenuItemBranchGL::handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ) +{ + if (getMenu()->getVisible() && mBranch->getVisible() && key == KEY_LEFT) + { + BOOL handled = mBranch->clearHoverItem(); + if (handled && getMenu()->getTornOff()) + { + ((LLFloater*)getMenu()->getParent())->setFocus(TRUE); + } + return handled; + } + + if (mHighlight && + getMenu()->getVisible() && + // ignore keystrokes on background torn-off menus + (!getMenu()->getTornOff() || ((LLFloater*)getMenu()->getParent())->hasFocus()) && + key == KEY_RIGHT && !mBranch->getHighlightedItem()) + { + LLMenuItemGL* itemp = mBranch->highlightNextItem(NULL); + if (itemp) + { + return TRUE; + } + } + + return LLMenuItemGL::handleKeyHere(key, mask, called_from_parent); +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemBranchDownGL +// +// The LLMenuItemBranchDownGL represents a menu item that has a +// sub-menu. This is used to make menu bar menus. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemBranchDownGL : public LLMenuItemBranchGL +{ +protected: + +public: + LLMenuItemBranchDownGL( const LLString& name, const LLString& label, LLMenuGL* branch, + KEY key = KEY_NONE, MASK mask = MASK_NONE ); + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_MENU_ITEM_BRANCH_DOWN; } + virtual LLString getWidgetTag() const { return LL_MENU_ITEM_BRANCH_DOWN_GL_TAG; } + + virtual LLString getType() const { return "menu"; } + + // returns the normal width of this control in pixels - this is + // used for calculating the widest item, as well as for horizontal + // arrangement. + virtual U32 getNominalWidth( void ); + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + // doIt() - do the primary funcationality of the menu item. + virtual void doIt( void ); + + // set the hover status (called by it's menu) and if the object is + // active. This is used for behavior transfer. + virtual void setHighlight( BOOL highlight ); + + // determine if this object is active + virtual BOOL isActive( void ) const; + + // LLView functionality + virtual BOOL handleMouseDown( S32 x, S32 y, MASK mask ); + virtual BOOL handleMouseUp( S32 x, S32 y, MASK mask ) {return FALSE; } + virtual void draw( void ); + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + + virtual BOOL handleAcceleratorKey(KEY key, MASK mask); +}; + +LLMenuItemBranchDownGL::LLMenuItemBranchDownGL( const LLString& name, + const LLString& label, + LLMenuGL* branch, + KEY key, MASK mask ) : + LLMenuItemBranchGL( name, label, branch, key, mask ) +{ +} + +// returns the normal width of this control in pixels - this is used +// for calculating the widest item, as well as for horizontal +// arrangement. +U32 LLMenuItemBranchDownGL::getNominalWidth( void ) +{ + U32 width = LEFT_PAD_PIXELS + LEFT_WIDTH_PIXELS + RIGHT_PAD_PIXELS; + width += mFont->getWidth( mLabel.getWString().c_str() ); + return width; +} + +// called to rebuild the draw label +void LLMenuItemBranchDownGL::buildDrawLabel( void ) +{ + mDrawAccelLabel.clear(); + LLString st = mDrawAccelLabel; + appendAcceleratorString( st ); + mDrawAccelLabel = st; +} + +// doIt() - do the primary funcationality of the menu item. +void LLMenuItemBranchDownGL::doIt( void ) +{ + if( mBranch->getVisible() && !mBranch->getTornOff() ) + { + mBranch->setVisible( FALSE ); + } + else + { + if (mBranch->getTornOff()) + { + gFloaterView->bringToFront((LLFloater*)mBranch->getParent()); + } + else + { + // We're showing the drop-down menu, so patch up its labels/rects + mBranch->arrange(); + + LLRect rect = mBranch->getRect(); + S32 left = 0; + S32 top = mRect.mBottom; + localPointToOtherView(left, top, &left, &top, mBranch->getParent()); + + rect.setLeftTopAndSize( left, top, + rect.getWidth(), rect.getHeight() ); + mBranch->setRect( rect ); + S32 x = 0; + S32 y = 0; + mBranch->localPointToScreen( 0, 0, &x, &y ); + S32 delta_x = 0; + + LLCoordScreen window_size; + LLWindow* windowp = getWindow(); + windowp->getSize(&window_size); + + S32 window_width = window_size.mX; + if( x > window_width - rect.getWidth() ) + { + delta_x = (window_width - rect.getWidth()) - x; + } + mBranch->translate( delta_x, 0 ); + + //FIXME: get menuholder lookup working more generically + // hide existing menus + if (!mBranch->getTornOff()) + { + ((LLMenuHolderGL*)mBranch->getParent())->hideMenus(); + } + + mBranch->setVisible( TRUE ); + } + } +} + +// set the hover status (called by it's menu) +void LLMenuItemBranchDownGL::setHighlight( BOOL highlight ) +{ + mHighlight = highlight; + if( !highlight) + { + if (mBranch->getTornOff()) + { + ((LLFloater*)mBranch->getParent())->setFocus(FALSE); + mBranch->clearHoverItem(); + } + else + { + mBranch->setVisible( FALSE ); + } + } +} + +// determine if this object is active +// which, for branching menus, means the branch is open and has "focus" +BOOL LLMenuItemBranchDownGL::isActive( void ) const +{ + if (mBranch->getTornOff()) + { + return ((LLFloater*)mBranch->getParent())->hasFocus(); + } + else + { + return mBranch->getVisible(); + } +} + +BOOL LLMenuItemBranchDownGL::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + doIt(); + make_ui_sound("UISndClick"); + return TRUE; +} + + +BOOL LLMenuItemBranchDownGL::handleAcceleratorKey(KEY key, MASK mask) +{ + BOOL branch_visible = mBranch->getVisible(); + BOOL handled = mBranch->handleAcceleratorKey(key, mask); + if (handled && !branch_visible) + { + // flash this menu entry because we triggered an invisible menu item + LLMenuHolderGL::setActivatedItem(this); + } + + return handled; +} + +BOOL LLMenuItemBranchDownGL::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + if (mHighlight && getMenu()->getVisible() && mBranch->getVisible()) + { + if (key == KEY_LEFT) + { + LLMenuItemGL* itemp = getMenu()->highlightPrevItem(this); + if (itemp) + { + itemp->doIt(); + } + + return TRUE; + } + else if (key == KEY_RIGHT) + { + LLMenuItemGL* itemp = getMenu()->highlightNextItem(this); + if (itemp) + { + itemp->doIt(); + } + + return TRUE; + } + else if (key == KEY_DOWN) + { + if (!mBranch->getTornOff()) + { + mBranch->setVisible(TRUE); + } + mBranch->highlightNextItem(NULL); + return TRUE; + } + else if (key == KEY_UP) + { + if (!mBranch->getTornOff()) + { + mBranch->setVisible(TRUE); + } + mBranch->highlightPrevItem(NULL); + return TRUE; + } + } + + return FALSE; +} + +void LLMenuItemBranchDownGL::draw( void ) +{ + if( mHighlight ) + { + glColor4fv( sHighlightBackground.mV ); + gl_rect_2d( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + } + + U8 font_style = mStyle; + if (LLMenuItemGL::sDropShadowText && getEnabled() && !mDrawTextDisabled ) + { + font_style |= LLFontGL::DROP_SHADOW; + } + + LLColor4 color; + if (mHighlight) + { + color = sHighlightForeground; + } + else if( mEnabled ) + { + color = sEnabledColor; + } + else + { + color = sDisabledColor; + } + mFont->render( mLabel.getWString(), 0, (F32)mRect.getWidth() / 2.f, (F32)LABEL_BOTTOM_PAD_PIXELS, color, + LLFontGL::HCENTER, LLFontGL::BOTTOM, font_style ); + // if branching menu is closed clear out highlight + if (mHighlight && ((!mBranch->getVisible() /*|| mBranch->getTornOff()*/) && !mGotHover)) + { + setHighlight(FALSE); + } + + // underline navigation key + BOOL draw_jump_key = gKeyboard->currentMask(FALSE) == MASK_ALT && + (!getMenu()->getHighlightedItem() || !getMenu()->getHighlightedItem()->isActive()) && + (!getMenu()->getTornOff()); // torn off menus don't use jump keys, too complicated + + if (draw_jump_key) + { + LLString upper_case_label = mLabel.getString(); + LLString::toUpper(upper_case_label); + std::string::size_type offset = upper_case_label.find(mJumpKey); + if (offset != std::string::npos) + { + S32 x_offset = llround((F32)mRect.getWidth() / 2.f - mFont->getWidthF32(mLabel.getString(), 0, S32_MAX) / 2.f); + S32 x_begin = x_offset + mFont->getWidth(mLabel, 0, offset); + S32 x_end = x_offset + mFont->getWidth(mLabel, 0, offset + 1); + gl_line_2d(x_begin, LABEL_BOTTOM_PAD_PIXELS, x_end, LABEL_BOTTOM_PAD_PIXELS); + } + } + + // reset every frame so that we only show highlight + // when we get hover events on that frame + mGotHover = FALSE; +} + +///============================================================================ +/// Class LLMenuGL +///============================================================================ + +// Default constructor +LLMenuGL::LLMenuGL( const LLString& name, const LLString& label, LLViewHandle parent_floater_handle ) +: LLUICtrl( name, LLRect(), FALSE, NULL, NULL ), + mBackgroundColor( sDefaultBackgroundColor ), + mBgVisible( TRUE ), + mParentMenuItem( NULL ), + mLabel( label ), + mDropShadowed( TRUE ), + mHorizontalLayout( FALSE ), + mKeepFixedSize( FALSE ), + mLastMouseX(0), + mLastMouseY(0), + mMouseVelX(0), + mMouseVelY(0), + mTornOff(FALSE), + mTearOffItem(NULL), + mSpilloverBranch(NULL), + mSpilloverMenu(NULL), + mParentFloaterHandle(parent_floater_handle), + mJumpKey(KEY_NONE) +{ + mFadeTimer.stop(); + setCanTearOff(TRUE, parent_floater_handle); + setTabStop(FALSE); +} + +LLMenuGL::LLMenuGL( const LLString& label, LLViewHandle parent_floater_handle ) +: LLUICtrl( label, LLRect(), FALSE, NULL, NULL ), + mBackgroundColor( sDefaultBackgroundColor ), + mBgVisible( TRUE ), + mParentMenuItem( NULL ), + mLabel( label ), + mDropShadowed( TRUE ), + mHorizontalLayout( FALSE ), + mKeepFixedSize( FALSE ), + mLastMouseX(0), + mLastMouseY(0), + mMouseVelX(0), + mMouseVelY(0), + mTornOff(FALSE), + mTearOffItem(NULL), + mSpilloverBranch(NULL), + mSpilloverMenu(NULL), + mParentFloaterHandle(parent_floater_handle), + mJumpKey(KEY_NONE) +{ + mFadeTimer.stop(); + setCanTearOff(TRUE, parent_floater_handle); + setTabStop(FALSE); +} + +// Destroys the object +LLMenuGL::~LLMenuGL( void ) +{ + // delete the branch, as it might not be in view hierarchy + // leave the menu, because it is always in view hierarchy + delete mSpilloverBranch; + mJumpKeys.clear(); +} + +void LLMenuGL::setCanTearOff(BOOL tear_off, LLViewHandle parent_floater_handle ) +{ + if (tear_off && mTearOffItem == NULL) + { + mTearOffItem = new LLMenuItemTearOffGL(parent_floater_handle); + mItems.insert(mItems.begin(), mTearOffItem); + addChildAtEnd(mTearOffItem); + arrange(); + } + else if (!tear_off && mTearOffItem != NULL) + { + mItems.remove(mTearOffItem); + removeChild(mTearOffItem); + delete mTearOffItem; + mTearOffItem = NULL; + arrange(); + } +} + +// virtual +LLXMLNodePtr LLMenuGL::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLView::getXML(); + + // Attributes + + node->createChild("opaque", TRUE)->setBoolValue(mBgVisible); + + node->createChild("drop_shadow", TRUE)->setBoolValue(mDropShadowed); + + node->createChild("tear_off", TRUE)->setBoolValue((mTearOffItem != NULL)); + + if (mBgVisible) + { + // TomY TODO: this should save out the color control name + node->createChild("color", TRUE)->setFloatValue(4, mBackgroundColor.mV); + } + + // Contents + item_list_t::const_iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + LLView* child = (*item_iter); + LLMenuItemGL* item = (LLMenuItemGL*)child; + + LLXMLNodePtr child_node = item->getXML(); + + node->addChild(child_node); + } + + return node; +} + +void LLMenuGL::parseChildXML(LLXMLNodePtr child, LLView *parent, LLUICtrlFactory *factory) +{ + if (child->hasName(LL_MENU_GL_TAG)) + { + // SUBMENU + LLMenuGL *submenu = (LLMenuGL*)LLMenuGL::fromXML(child, parent, factory); + appendMenu(submenu); + if (LLMenuGL::sDefaultMenuContainer != NULL) + { + submenu->updateParent(LLMenuGL::sDefaultMenuContainer); + } + else + { + submenu->updateParent(parent); + } + } + else if (child->hasName(LL_MENU_ITEM_CALL_GL_TAG) || + child->hasName(LL_MENU_ITEM_CHECK_GL_TAG) || + child->hasName(LL_MENU_ITEM_SEPARATOR_GL_TAG)) + { + LLMenuItemGL *item = NULL; + + LLString type; + LLString item_name; + LLString source_label; + LLString item_label; + KEY jump_key = KEY_NONE; + + child->getAttributeString("type", type); + child->getAttributeString("name", item_name); + child->getAttributeString("label", source_label); + + // parse jump key out of label + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("_"); + tokenizer tokens(source_label, sep); + tokenizer::iterator token_iter; + S32 token_count = 0; + for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + item_label += (*token_iter); + if (token_count > 0) + { + jump_key = (*token_iter).c_str()[0]; + } + ++token_count; + } + + + if (child->hasName(LL_MENU_ITEM_SEPARATOR_GL_TAG)) + { + appendSeparator(item_name); + } + else + { + // ITEM + if (child->hasName(LL_MENU_ITEM_CALL_GL_TAG) || + child->hasName(LL_MENU_ITEM_CHECK_GL_TAG)) + { + MASK mask = 0; + LLString shortcut; + child->getAttributeString("shortcut", shortcut); + if (shortcut.find("control") != shortcut.npos) + { + mask |= MASK_CONTROL; + } + if (shortcut.find("alt") != shortcut.npos) + { + mask |= MASK_ALT; + } + if (shortcut.find("shift") != shortcut.npos) + { + mask |= MASK_SHIFT; + } + S32 pipe_pos = shortcut.rfind("|"); + LLString key_str = shortcut.substr(pipe_pos+1); + + KEY key = KEY_NONE; + LLKeyboard::keyFromString(key_str, &key); + + LLMenuItemCallGL *new_item; + LLXMLNodePtr call_child; + + if (child->hasName(LL_MENU_ITEM_CHECK_GL_TAG)) + { + LLString control_name; + child->getAttributeString("control_name", control_name); + + new_item = new LLMenuItemCheckGL(item_name, item_label, 0, 0, control_name, parent, 0, key, mask); + + for (call_child = child->getFirstChild(); call_child.notNull(); call_child = call_child->getNextSibling()) + { + if (call_child->hasName("on_check")) + { + LLString callback_name; + LLString control_name = ""; + if (call_child->hasAttribute("function")) + { + call_child->getAttributeString("function", callback_name); + + control_name = callback_name; + + LLString callback_data = item_name; + if (call_child->hasAttribute("userdata")) + { + call_child->getAttributeString("userdata", callback_data); + if (!callback_data.empty()) + { + control_name = llformat("%s(%s)", callback_name.c_str(), callback_data.c_str()); + } + } + + LLSD userdata; + userdata["control"] = control_name; + userdata["data"] = callback_data; + + LLSimpleListener* callback = parent->getListenerByName(callback_name); + + if (!callback) continue; + + new_item->addListener(callback, "on_build", userdata); + } + else if (call_child->hasAttribute("control")) + { + call_child->getAttributeString("control", control_name); + } + else + { + continue; + } + LLControlBase *control = parent->findControl(control_name); + if (!control) + { + parent->addBoolControl(control_name, FALSE); + } + ((LLMenuItemCheckGL*)new_item)->setCheckedControl(control_name, parent); + } + } + } + else + { + new_item = new LLMenuItemCallGL(item_name, item_label, 0, 0, 0, 0, key, mask); + } + + for (call_child = child->getFirstChild(); call_child.notNull(); call_child = call_child->getNextSibling()) + { + if (call_child->hasName("on_click")) + { + LLString callback_name; + call_child->getAttributeString("function", callback_name); + + LLString callback_data = item_name; + if (call_child->hasAttribute("userdata")) + { + call_child->getAttributeString("userdata", callback_data); + } + + LLSimpleListener* callback = parent->getListenerByName(callback_name); + + if (!callback) continue; + + new_item->addListener(callback, "on_click", callback_data); + } + if (call_child->hasName("on_enable")) + { + LLString callback_name; + LLString control_name = ""; + if (call_child->hasAttribute("function")) + { + call_child->getAttributeString("function", callback_name); + + control_name = callback_name; + + LLString callback_data = ""; + if (call_child->hasAttribute("userdata")) + { + call_child->getAttributeString("userdata", callback_data); + if (!callback_data.empty()) + { + control_name = llformat("%s(%s)", callback_name.c_str(), callback_data.c_str()); + } + } + + LLSD userdata; + userdata["control"] = control_name; + userdata["data"] = callback_data; + + LLSimpleListener* callback = parent->getListenerByName(callback_name); + + if (!callback) continue; + + new_item->addListener(callback, "on_build", userdata); + } + else if (call_child->hasAttribute("control")) + { + call_child->getAttributeString("control", control_name); + } + else + { + continue; + } + new_item->setEnabledControl(control_name, parent); + } + if (call_child->hasName("on_visible")) + { + LLString callback_name; + LLString control_name = ""; + if (call_child->hasAttribute("function")) + { + call_child->getAttributeString("function", callback_name); + + control_name = callback_name; + + LLString callback_data = ""; + if (call_child->hasAttribute("userdata")) + { + call_child->getAttributeString("userdata", callback_data); + if (!callback_data.empty()) + { + control_name = llformat("%s(%s)", callback_name.c_str(), callback_data.c_str()); + } + } + + LLSD userdata; + userdata["control"] = control_name; + userdata["data"] = callback_data; + + LLSimpleListener* callback = parent->getListenerByName(callback_name); + + if (!callback) continue; + + new_item->addListener(callback, "on_build", userdata); + } + else if (call_child->hasAttribute("control")) + { + call_child->getAttributeString("control", control_name); + } + else + { + continue; + } + new_item->setVisibleControl(control_name, parent); + } + } + item = new_item; + item->setLabel(item_label); + item->setJumpKey(jump_key); + } + + if (item != NULL) + { + append(item); + } + } + } +} + +// static +LLView* LLMenuGL::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("menu"); + node->getAttributeString("name", name); + + LLString label = name; + node->getAttributeString("label", label); + + // parse jump key out of label + LLString new_menu_label; + + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("_"); + tokenizer tokens(label, sep); + tokenizer::iterator token_iter; + + KEY jump_key = KEY_NONE; + S32 token_count = 0; + for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + new_menu_label += (*token_iter); + if (token_count > 0) + { + jump_key = (*token_iter).c_str()[0]; + } + ++token_count; + } + + BOOL opaque = FALSE; + node->getAttributeBOOL("opaque", opaque); + + LLMenuGL *menu = new LLMenuGL(name, new_menu_label); + + menu->setJumpKey(jump_key); + + BOOL tear_off = FALSE; + node->getAttributeBOOL("tear_off", tear_off); + menu->setCanTearOff(tear_off); + + if (node->hasAttribute("drop_shadow")) + { + BOOL drop_shadow = FALSE; + node->getAttributeBOOL("drop_shadow", drop_shadow); + menu->setDropShadowed(drop_shadow); + } + + menu->setBackgroundVisible(opaque); + LLColor4 color(0,0,0,1); + if (opaque && LLUICtrlFactory::getAttributeColor(node,"color", color)) + { + menu->setBackgroundColor(color); + } + + BOOL create_jump_keys = FALSE; + node->getAttributeBOOL("create_jump_keys", create_jump_keys); + + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + menu->parseChildXML(child, parent, factory); + } + + if (create_jump_keys) + { + menu->createJumpKeys(); + } + return menu; +} + +// control the color scheme +void LLMenuGL::setDefaultBackgroundColor( const LLColor4& color ) +{ + sDefaultBackgroundColor = color; +} + +void LLMenuGL::setBackgroundColor( const LLColor4& color ) +{ + mBackgroundColor = color; +} + +// rearrange the child rects so they fit the shape of the menu. +void LLMenuGL::arrange( void ) +{ + // calculate the height & width, and set our rect based on that + // information. + LLRect initial_rect = mRect; + + U32 width = 0, height = MENU_ITEM_PADDING; + + cleanupSpilloverBranch(); + + if( mItems.size() ) + { + U32 max_width = (getParent() != NULL) ? getParent()->getRect().getWidth() : U32_MAX; + U32 max_height = (getParent() != NULL) ? getParent()->getRect().getHeight() : U32_MAX; + //FIXME: create the item first and then ask for its dimensions? + S32 spillover_item_width = PLAIN_PAD_PIXELS + LLFontGL::sSansSerif->getWidth( "More" ); + S32 spillover_item_height = llround(LLFontGL::sSansSerif->getLineHeight()) + MENU_ITEM_PADDING; + + if (mHorizontalLayout) + { + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if ((*item_iter)->getVisible()) + { + if (!getTornOff() && width + (*item_iter)->getNominalWidth() > max_width - spillover_item_width) + { + // no room for any more items + createSpilloverBranch(); + + item_list_t::iterator spillover_iter; + for (spillover_iter = item_iter; spillover_iter != mItems.end(); ++spillover_iter) + { + LLMenuItemGL* itemp = (*spillover_iter); + removeChild(itemp); + mSpilloverMenu->append(itemp); + } + mItems.erase(item_iter, mItems.end()); + + mItems.push_back(mSpilloverBranch); + addChild(mSpilloverBranch); + height = llmax(height, mSpilloverBranch->getNominalHeight()); + width += mSpilloverBranch->getNominalWidth(); + + break; + } + else + { + // track our rect + height = llmax(height, (*item_iter)->getNominalHeight()); + width += (*item_iter)->getNominalWidth(); + } + } + } + } + else + { + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if ((*item_iter)->getVisible()) + { + if (!getTornOff() && height + (*item_iter)->getNominalHeight() > max_height - spillover_item_height) + { + // no room for any more items + createSpilloverBranch(); + + item_list_t::iterator spillover_iter; + for (spillover_iter= item_iter; spillover_iter != mItems.end(); ++spillover_iter) + { + LLMenuItemGL* itemp = (*spillover_iter); + removeChild(itemp); + mSpilloverMenu->append(itemp); + } + mItems.erase(item_iter, mItems.end()); + mItems.push_back(mSpilloverBranch); + addChild(mSpilloverBranch); + height += mSpilloverBranch->getNominalHeight(); + width = llmax( width, mSpilloverBranch->getNominalWidth() ); + + break; + } + else + { + // track our rect + height += (*item_iter)->getNominalHeight(); + width = llmax( width, (*item_iter)->getNominalWidth() ); + } + } + } + } + + mRect.mRight = mRect.mLeft + width; + mRect.mTop = mRect.mBottom + height; + + S32 cur_height = (S32)llmin(max_height, height); + S32 cur_width = 0; + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if ((*item_iter)->getVisible()) + { + // setup item rect to hold label + LLRect rect; + if (mHorizontalLayout) + { + rect.setLeftTopAndSize( cur_width, height, (*item_iter)->getNominalWidth(), height); + cur_width += (*item_iter)->getNominalWidth(); + } + else + { + rect.setLeftTopAndSize( 0, cur_height, width, (*item_iter)->getNominalHeight()); + cur_height -= (*item_iter)->getNominalHeight(); + } + (*item_iter)->setRect( rect ); + (*item_iter)->buildDrawLabel(); + } + } + } + if (mKeepFixedSize) + { + reshape(initial_rect.getWidth(), initial_rect.getHeight()); + } +} + +void LLMenuGL::createSpilloverBranch() +{ + if (!mSpilloverBranch) + { + // should be NULL but delete anyway + delete mSpilloverMenu; + // technically, you can't tear off spillover menus, but we're passing the handle + // along just to be safe + mSpilloverMenu = new LLMenuGL("More", "More", mParentFloaterHandle); + mSpilloverMenu->updateParent(getParent()); + // Inherit colors + mSpilloverMenu->setBackgroundColor( mBackgroundColor ); + mSpilloverMenu->setCanTearOff(FALSE); + + mSpilloverBranch = new LLMenuItemBranchGL("More", "More", mSpilloverMenu); + mSpilloverBranch->setFontStyle(LLFontGL::ITALIC); + } +} + +void LLMenuGL::cleanupSpilloverBranch() +{ + if (mSpilloverBranch && mSpilloverBranch->getParent() == this) + { + // head-recursion to propagate items back up to root menu + mSpilloverMenu->cleanupSpilloverBranch(); + + removeChild(mSpilloverBranch); + + item_list_t::iterator found_iter = std::find(mItems.begin(), mItems.end(), mSpilloverBranch); + if (found_iter != mItems.end()) + { + mItems.erase(found_iter); + } + + // pop off spillover items + while (mSpilloverMenu->getItemCount()) + { + LLMenuItemGL* itemp = mSpilloverMenu->getItem(0); + mSpilloverMenu->removeChild(itemp); + mSpilloverMenu->mItems.erase(mSpilloverMenu->mItems.begin()); + // put them at the end of our own list + mItems.push_back(itemp); + addChild(itemp); + } + } +} + +void LLMenuGL::createJumpKeys() +{ + mJumpKeys.clear(); + + std::set unique_words; + std::set shared_words; + + item_list_t::iterator item_it; + typedef boost::tokenizer > tokenizer; + boost::char_separator sep(" "); + + for(item_it = mItems.begin(); item_it != mItems.end(); ++item_it) + { + LLString uppercase_label = (*item_it)->getLabel(); + LLString::toUpper(uppercase_label); + + tokenizer tokens(uppercase_label, sep); + tokenizer::iterator token_iter; + for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + if (unique_words.find(*token_iter) != unique_words.end()) + { + // this word exists in more than one menu instance + shared_words.insert(*token_iter); + } + else + { + // we have a new word, keep track of it + unique_words.insert(*token_iter); + } + } + } + + // pre-assign specified jump keys + for(item_it = mItems.begin(); item_it != mItems.end(); ++item_it) + { + KEY jump_key = (*item_it)->getJumpKey(); + if(jump_key != KEY_NONE) + { + if (mJumpKeys.find(jump_key) == mJumpKeys.end()) + { + mJumpKeys.insert(std::pair(jump_key, (*item_it))); + } + else + { + // this key is already spoken for, + // so we need to reassign it below + (*item_it)->setJumpKey(KEY_NONE); + } + } + } + + for(item_it = mItems.begin(); item_it != mItems.end(); ++item_it) + { + // skip over items that already have assigned jump keys + if ((*item_it)->getJumpKey() != KEY_NONE) + { + continue; + } + LLString uppercase_label = (*item_it)->getLabel(); + LLString::toUpper(uppercase_label); + + tokenizer tokens(uppercase_label, sep); + tokenizer::iterator token_iter; + + BOOL found_key = FALSE; + for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + LLString uppercase_word = *token_iter; + + // this word is not shared with other menu entries... + if (shared_words.find(*token_iter) == shared_words.end()) + { + S32 i; + for(i = 0; i < (S32)uppercase_word.size(); i++) + { + char jump_key = uppercase_word[i]; + + if (LLStringOps::isDigit(jump_key) || LLStringOps::isUpper(jump_key) && + mJumpKeys.find(jump_key) == mJumpKeys.end()) + { + mJumpKeys.insert(std::pair(jump_key, (*item_it))); + (*item_it)->setJumpKey(jump_key); + found_key = TRUE; + break; + } + } + } + if (found_key) + { + break; + } + } + } +} + +// remove all items on the menu +void LLMenuGL::empty( void ) +{ + mItems.clear(); + + deleteAllChildren(); + +} + +// Adjust rectangle of the menu +void LLMenuGL::setLeftAndBottom(S32 left, S32 bottom) +{ + mRect.mLeft = left; + mRect.mBottom = bottom; + arrange(); +} + +void LLMenuGL::handleJumpKey(KEY key) +{ + navigation_key_map_t::iterator found_it = mJumpKeys.find(key); + if(found_it != mJumpKeys.end() && found_it->second->getEnabled()) + { + clearHoverItem(); + // force highlight to close old menus and open and sub-menus + found_it->second->setHighlight(TRUE); + found_it->second->doIt(); + if (!found_it->second->isActive() && !getTornOff()) + { + // parent is a menu holder, because this is not a menu bar + ((LLMenuHolderGL*)getParent())->hideMenus(); + } + } +} + + +// Add the menu item to this menu. +BOOL LLMenuGL::append( LLMenuItemGL* item ) +{ + mItems.push_back( item ); + addChild( item ); + arrange(); + return TRUE; +} + +// add a separator to this menu +BOOL LLMenuGL::appendSeparator( const LLString &separator_name ) +{ + LLMenuItemGL* separator = new LLMenuItemSeparatorGL(separator_name); + return append( separator ); +} + +// add a menu - this will create a cascading menu +BOOL LLMenuGL::appendMenu( LLMenuGL* menu ) +{ + if( menu == this ) + { + llerrs << "** Attempt to attach menu to itself. This is certainly " + << "a logic error." << llendl; + } + BOOL success = TRUE; + + LLMenuItemBranchGL* branch = NULL; + branch = new LLMenuItemBranchGL( menu->getName(), menu->getLabel(), menu ); + branch->setJumpKey(menu->getJumpKey()); + success &= append( branch ); + + // Inherit colors + menu->setBackgroundColor( mBackgroundColor ); + + return success; +} + +void LLMenuGL::setEnabledSubMenus(BOOL enable) +{ + setEnabled(enable); + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + (*item_iter)->setEnabledSubMenus( enable ); + } +} + +// setItemEnabled() - pass the label and the enable flag for a menu +// item. TRUE will make sure it's enabled, FALSE will disable it. +void LLMenuGL::setItemEnabled( const LLString& name, BOOL enable ) +{ + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if( (*item_iter)->getName() == name ) + { + (*item_iter)->setEnabled( enable ); + (*item_iter)->setEnabledSubMenus( enable ); + break; + } + } +} + +void LLMenuGL::setItemVisible( const LLString& name, BOOL visible ) +{ + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if( (*item_iter)->getName() == name ) + { + (*item_iter)->setVisible( visible ); + break; + } + } +} + +void LLMenuGL::setItemLastSelected(LLMenuItemGL* item) +{ + if (getVisible()) + { + LLMenuHolderGL::setActivatedItem(item); + } + + // fix the checkmarks + item->buildDrawLabel(); +} + +// Set whether drop shadowed +void LLMenuGL::setDropShadowed( const BOOL shadowed ) +{ + mDropShadowed = shadowed; +} + +void LLMenuGL::setTornOff(BOOL torn_off) +{ + mTornOff = torn_off; +} + +U32 LLMenuGL::getItemCount() +{ + return mItems.size(); +} + +LLMenuItemGL* LLMenuGL::getItem(S32 number) +{ + if (number >= 0 && number < (S32)mItems.size()) + { + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if (number == 0) + { + return (*item_iter); + } + number--; + } + } + return NULL; +} + +LLMenuItemGL* LLMenuGL::getHighlightedItem() +{ + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if ((*item_iter)->getHighlight()) + { + return (*item_iter); + } + } + return NULL; +} + +LLMenuItemGL* LLMenuGL::highlightNextItem(LLMenuItemGL* cur_item, BOOL skip_disabled) +{ + // highlighting first item on a torn off menu is the + // same as giving focus to it + if (!cur_item && getTornOff()) + { + ((LLFloater*)getParent())->setFocus(TRUE); + } + + item_list_t::iterator cur_item_iter; + for (cur_item_iter = mItems.begin(); cur_item_iter != mItems.end(); ++cur_item_iter) + { + if( (*cur_item_iter) == cur_item) + { + break; + } + } + + item_list_t::iterator next_item_iter; + if (cur_item_iter == mItems.end()) + { + next_item_iter = mItems.begin(); + } + else + { + next_item_iter = cur_item_iter; + next_item_iter++; + if (next_item_iter == mItems.end()) + { + next_item_iter = mItems.begin(); + } + } + + // when first highlighting a menu, skip over tear off menu item + if (mTearOffItem && !cur_item) + { + // we know the first item is the tear off menu item + cur_item_iter = mItems.begin(); + next_item_iter++; + if (next_item_iter == mItems.end()) + { + next_item_iter = mItems.begin(); + } + } + + while(1) + { + // skip separators and disabled items + if ((*next_item_iter)->getEnabled() && (*next_item_iter)->getName() != SEPARATOR_NAME) + { + if (cur_item) + { + cur_item->setHighlight(FALSE); + } + (*next_item_iter)->setHighlight(TRUE); + return (*next_item_iter); + } + + + if (!skip_disabled || next_item_iter == cur_item_iter) + { + break; + } + + next_item_iter++; + if (next_item_iter == mItems.end()) + { + if (cur_item_iter == mItems.end()) + { + break; + } + next_item_iter = mItems.begin(); + } + } + + return NULL; +} + +LLMenuItemGL* LLMenuGL::highlightPrevItem(LLMenuItemGL* cur_item, BOOL skip_disabled) +{ + // highlighting first item on a torn off menu is the + // same as giving focus to it + if (!cur_item && getTornOff()) + { + ((LLFloater*)getParent())->setFocus(TRUE); + } + + item_list_t::reverse_iterator cur_item_iter; + for (cur_item_iter = mItems.rbegin(); cur_item_iter != mItems.rend(); ++cur_item_iter) + { + if( (*cur_item_iter) == cur_item) + { + break; + } + } + + item_list_t::reverse_iterator prev_item_iter; + if (cur_item_iter == mItems.rend()) + { + prev_item_iter = mItems.rbegin(); + } + else + { + prev_item_iter = cur_item_iter; + prev_item_iter++; + if (prev_item_iter == mItems.rend()) + { + prev_item_iter = mItems.rbegin(); + } + } + + while(1) + { + // skip separators and disabled items + if ((*prev_item_iter)->getEnabled() && (*prev_item_iter)->getName() != SEPARATOR_NAME) + { + if (cur_item) + { + cur_item->setHighlight(FALSE); + } + (*prev_item_iter)->setHighlight(TRUE); + return (*prev_item_iter); + } + + if (!skip_disabled || prev_item_iter == cur_item_iter) + { + break; + } + + prev_item_iter++; + if (prev_item_iter == mItems.rend()) + { + if (cur_item_iter == mItems.rend()) + { + break; + } + + prev_item_iter = mItems.rbegin(); + } + } + + return NULL; +} + +void LLMenuGL::buildDrawLabels() +{ + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + (*item_iter)->buildDrawLabel(); + } +} + +void LLMenuGL::updateParent(LLView* parentp) +{ + if (getParent()) + { + getParent()->removeChild(this); + } + parentp->addChild(this); + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + (*item_iter)->updateBranchParent(parentp); + } +} + +// LLView functionality +BOOL LLMenuGL::handleKey( KEY key, MASK mask, BOOL called_from_parent ) +{ + BOOL handled = FALSE; + + // Pass down even if not visible + if( mEnabled && called_from_parent ) + { + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if (viewp->handleKey(key, mask, TRUE)) + { + handled = TRUE; + break; + } + } + } + + if( !handled ) + { + handled = handleKeyHere( key, mask, called_from_parent ); + if (handled && LLView::sDebugKeys) + { + llinfos << "Key handled by " << getName() << llendl; + } + } + + return handled; +} + +BOOL LLMenuGL::handleAcceleratorKey(KEY key, MASK mask) +{ + // Pass down even if not visible + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + LLMenuItemGL* itemp = *item_iter; + if (itemp->handleAcceleratorKey(key, mask)) + { + return TRUE; + } + } + + return FALSE; +} + +BOOL LLMenuGL::handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ) +{ + if (key < KEY_SPECIAL && getVisible() && getEnabled() && mask == MASK_ALT) + { + if (getTornOff()) + { + // torn off menus do not handle jump keys (for now, the interaction is complex) + return FALSE; + } + handleJumpKey(key); + return TRUE; + } + return FALSE; +} + +BOOL LLMenuGL::handleHover( S32 x, S32 y, MASK mask ) +{ + // leave submenu in place if slope of mouse < MAX_MOUSE_SLOPE_SUB_MENU + S32 mouse_delta_x = x - mLastMouseX; + S32 mouse_delta_y = y - mLastMouseY; + LLVector2 mouse_dir((F32)mouse_delta_x, (F32)mouse_delta_y); + mouse_dir.normVec(); + LLVector2 mouse_avg_dir((F32)mMouseVelX, (F32)mMouseVelY); + mouse_avg_dir.normVec(); + F32 interp = 0.5f * (llclamp(mouse_dir * mouse_avg_dir, 0.f, 1.f)); + mMouseVelX = llround(lerp((F32)mouse_delta_x, (F32)mMouseVelX, interp)); + mMouseVelY = llround(lerp((F32)mouse_delta_y, (F32)mMouseVelY, interp)); + mLastMouseX = x; + mLastMouseY = y; + + // don't change menu focus unless mouse is moving or alt key is not held down + if ((gKeyboard->currentMask(FALSE) != MASK_ALT || + llabs(mMouseVelX) > 0 || + llabs(mMouseVelY) > 0) && + (!mHasSelection || + //(mouse_delta_x == 0 && mouse_delta_y == 0) || + (mMouseVelX < 0) || + llabs((F32)mMouseVelY) / llabs((F32)mMouseVelX) > MAX_MOUSE_SLOPE_SUB_MENU)) + { + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + if (!viewp->pointInView(local_x, local_y) && ((LLMenuItemGL*)viewp)->getHighlight()) + { + // moving mouse always highlights new item + if (mouse_delta_x != 0 || mouse_delta_y != 0) + { + ((LLMenuItemGL*)viewp)->setHighlight(FALSE); + } + } + } + + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + //RN: always call handleHover to track mGotHover status + // but only set highlight when mouse is moving + if( viewp->getVisible() && + viewp->getEnabled() && + viewp->pointInView(local_x, local_y) && + viewp->handleHover(local_x, local_y, mask)) + { + // moving mouse always highlights new item + if (mouse_delta_x != 0 || mouse_delta_y != 0) + { + ((LLMenuItemGL*)viewp)->setHighlight(TRUE); + } + mHasSelection = TRUE; + } + } + } + getWindow()->setCursor(UI_CURSOR_ARROW); + return TRUE; +} + +BOOL LLMenuGL::handleMouseUp( S32 x, S32 y, MASK mask ) +{ + if( LLView::childrenHandleMouseUp( x, y, mask ) ) + { + if (!getTornOff()) + { + ((LLMenuHolderGL*)getParent())->hideMenus(); + } + } + + return TRUE; +} + +void LLMenuGL::draw( void ) +{ + if (mDropShadowed && !mTornOff) + { + gl_drop_shadow(0, mRect.getHeight(), mRect.getWidth(), 0, + LLUI::sColorsGroup->getColor("ColorDropShadow"), + LLUI::sConfigGroup->getS32("DropShadowFloater") ); + } + + LLColor4 bg_color = mBackgroundColor; + + if( mBgVisible ) + { + gl_rect_2d( 0, mRect.getHeight(), mRect.getWidth(), 0, mBackgroundColor ); + } + LLView::draw(); +} + +void LLMenuGL::drawBackground(LLMenuItemGL* itemp, LLColor4& color) +{ + glColor4fv( color.mV ); + LLRect item_rect = itemp->getRect(); + gl_rect_2d( 0, item_rect.getHeight(), item_rect.getWidth(), 0); +} + +void LLMenuGL::setVisible(BOOL visible) +{ + if (visible != getVisible()) + { + if (!visible) + { + mFadeTimer.start(); + clearHoverItem(); + } + else + { + mHasSelection = FALSE; + mFadeTimer.stop(); + } + + //gViewerWindow->finishFastFrame(); + LLView::setVisible(visible); + } +} + +LLMenuGL* LLMenuGL::getChildMenuByName(const LLString& name, BOOL recurse) const +{ + LLView* view = getChildByName(name, recurse); + if (view) + { + if (view->getWidgetType() == WIDGET_TYPE_MENU_ITEM_BRANCH) + { + LLMenuItemBranchGL *branch = (LLMenuItemBranchGL *)view; + return branch->getBranch(); + } + if (view->getWidgetType() == WIDGET_TYPE_MENU || view->getWidgetType() == WIDGET_TYPE_PIE_MENU) + { + return (LLMenuGL*)view; + } + } + llwarns << "Child Menu " << name << " not found in menu " << mName << llendl; + return NULL; +} + +BOOL LLMenuGL::clearHoverItem(BOOL include_active) +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLMenuItemGL* itemp = (LLMenuItemGL*)*child_it; + if (itemp->getHighlight() && (include_active || !itemp->isActive())) + { + itemp->setHighlight(FALSE); + return TRUE; + } + } + return FALSE; +} + +void hide_top_view( LLView* view ) +{ + if( view ) view->setVisible( FALSE ); +} + + +// static +void LLMenuGL::showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y) +{ + const S32 HPAD = 2; + LLRect rect = menu->getRect(); + //LLView* cur_view = spawning_view; + S32 left = x + HPAD; + S32 top = y; + spawning_view->localPointToOtherView(left, top, &left, &top, menu->getParent()); + rect.setLeftTopAndSize( left, top, + rect.getWidth(), rect.getHeight() ); + + + //rect.setLeftTopAndSize(x + HPAD, y, rect.getWidth(), rect.getHeight()); + menu->setRect( rect ); + + S32 bottom; + left = rect.mLeft; + bottom = rect.mBottom; + //menu->getParent()->localPointToScreen( rect.mLeft, rect.mBottom, + // &left, &bottom ); + S32 delta_x = 0; + S32 delta_y = 0; + if( bottom < 0 ) + { + delta_y = -bottom; + } + + S32 parent_width = menu->getParent()->getRect().getWidth(); + if( left > parent_width - rect.getWidth() ) + { + // At this point, we need to move the context menu to the + // other side of the mouse. + //delta_x = (window_width - rect.getWidth()) - x; + delta_x = -(rect.getWidth() + 2 * HPAD); + } + menu->translate( delta_x, delta_y ); + menu->setVisible( TRUE ); +} + +//----------------------------------------------------------------------------- +// class LLPieMenuBranch +// A branch to another pie menu +//----------------------------------------------------------------------------- +class LLPieMenuBranch : public LLMenuItemGL +{ +public: + LLPieMenuBranch(const LLString& name, const LLString& label, LLPieMenu* branch, + enabled_callback ecb, void* user_data); + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_PIE_MENU_BRANCH; } + virtual LLString getWidgetTag() const { return LL_PIE_MENU_BRANCH_TAG; } + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + // doIt() - do the primary funcationality of the menu item. + virtual void doIt( void ); + + LLPieMenu* getBranch() { return mBranch; } + +protected: + LLPieMenu* mBranch; + enabled_callback mEnabledCallback; + void* mUserData; +}; + +LLPieMenuBranch::LLPieMenuBranch(const LLString& name, + const LLString& label, + LLPieMenu* branch, + enabled_callback ecb, + void* user_data) +: LLMenuItemGL( name, label, KEY_NONE, MASK_NONE ), + mBranch( branch ), + mEnabledCallback( ecb ), + mUserData(user_data) +{ + mBranch->hide(FALSE); + mBranch->setParentMenuItem(this); +} + +// called to rebuild the draw label +void LLPieMenuBranch::buildDrawLabel( void ) +{ + if(mEnabledCallback) + { + setEnabled(mEnabledCallback(mUserData)); + mDrawTextDisabled = FALSE; + } + else + { + // default enablement is this -- if any of the subitems are + // enabled, this item is enabled. JC + U32 sub_count = mBranch->getItemCount(); + U32 i; + BOOL any_enabled = FALSE; + for (i = 0; i < sub_count; i++) + { + LLMenuItemGL* item = mBranch->getItem(i); + item->buildDrawLabel(); + if (item->getEnabled() && !item->getDrawTextDisabled() ) + { + any_enabled = TRUE; + break; + } + } + mDrawTextDisabled = !any_enabled; + setEnabled(TRUE); + } + + mDrawAccelLabel.clear(); + LLString st = mDrawAccelLabel; + appendAcceleratorString( st ); + mDrawAccelLabel = st; + + // No special branch suffix + mDrawBranchLabel.clear(); +} + +// doIt() - do the primary funcationality of the menu item. +void LLPieMenuBranch::doIt( void ) +{ + LLPieMenu *parent = (LLPieMenu *)getParent(); + + LLRect rect = parent->getRect(); + S32 center_x; + S32 center_y; + parent->localPointToScreen(rect.getWidth() / 2, rect.getHeight() / 2, ¢er_x, ¢er_y); + + parent->hide(TRUE); + mBranch->show( center_x, center_y, FALSE ); +} + +//----------------------------------------------------------------------------- +// class LLPieMenu +// A circular menu of items, icons, etc. +//----------------------------------------------------------------------------- +LLPieMenu::LLPieMenu(const LLString& name, const LLString& label) +: LLMenuGL(name, label), + mFirstMouseDown(FALSE), + mUseInfiniteRadius(FALSE), + mHoverItem(NULL), + mHoverThisFrame(FALSE), + mOuterRingAlpha(1.f), + mCurRadius(0.f), + mRightMouseDown(FALSE) +{ + LLMenuGL::setVisible(FALSE); + setCanTearOff(FALSE); +} + +LLPieMenu::LLPieMenu(const LLString& name) +: LLMenuGL(name, name), + mFirstMouseDown(FALSE), + mUseInfiniteRadius(FALSE), + mHoverItem(NULL), + mHoverThisFrame(FALSE), + mOuterRingAlpha(1.f), + mCurRadius(0.f), + mRightMouseDown(FALSE) +{ + LLMenuGL::setVisible(FALSE); + setCanTearOff(FALSE); +} + +// virtual +LLPieMenu::~LLPieMenu() +{ } + + +EWidgetType LLPieMenu::getWidgetType() const +{ + return WIDGET_TYPE_PIE_MENU; +} + +LLString LLPieMenu::getWidgetTag() const +{ + return LL_PIE_MENU_TAG; +} + +void LLPieMenu::initXML(LLXMLNodePtr node, LLView *context, LLUICtrlFactory *factory) +{ + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + if (child->hasName(LL_PIE_MENU_TAG)) + { + // SUBMENU + LLString name("menu"); + child->getAttributeString("name", name); + LLString label(name); + child->getAttributeString("label", label); + + LLPieMenu *submenu = new LLPieMenu(name, label); + appendMenu(submenu); + submenu->initXML(child, context, factory); + } + else + { + parseChildXML(child, context, factory); + } + } +} + +// virtual +void LLPieMenu::setVisible(BOOL visible) +{ + if (!visible) + { + hide(FALSE); + } +} + +BOOL LLPieMenu::handleHover( S32 x, S32 y, MASK mask ) +{ + // This is mostly copied from the llview class, but it continues + // the hover handle code after a hover handler has been found. + BOOL handled = FALSE; + + // If we got a hover event, we've already moved the cursor + // for any menu shifts, so subsequent mouseup messages will be in the + // correct position. No need to correct them. + //mShiftHoriz = 0; + //mShiftVert = 0; + + // release mouse capture after short period of visibility if we're using a finite boundary + // so that right click outside of boundary will trigger new pie menu + if (gFocusMgr.getMouseCapture() == this && + !mRightMouseDown && + mShrinkBorderTimer.getStarted() && + mShrinkBorderTimer.getElapsedTimeF32() >= PIE_SHRINK_TIME) + { + gFocusMgr.setMouseCapture(NULL, NULL); + mUseInfiniteRadius = FALSE; + } + + LLMenuItemGL *item = pieItemFromXY( x, y ); + + if (item && item->getEnabled()) + { + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; + handled = TRUE; + + if (item != mHoverItem) + { + BOOL active = FALSE; + if (mHoverItem) + { + active = mHoverItem->isActive(); + mHoverItem->setHighlight( FALSE ); + } + mHoverItem = item; + mHoverItem->setHighlight( TRUE ); + + switch(pieItemIndexFromXY(x, y)) + { + case 0: + make_ui_sound("UISndPieMenuSliceHighlight0"); + break; + case 1: + make_ui_sound("UISndPieMenuSliceHighlight1"); + break; + case 2: + make_ui_sound("UISndPieMenuSliceHighlight2"); + break; + case 3: + make_ui_sound("UISndPieMenuSliceHighlight3"); + break; + case 4: + make_ui_sound("UISndPieMenuSliceHighlight4"); + break; + case 5: + make_ui_sound("UISndPieMenuSliceHighlight5"); + break; + case 6: + make_ui_sound("UISndPieMenuSliceHighlight6"); + break; + case 7: + make_ui_sound("UISndPieMenuSliceHighlight7"); + break; + default: + make_ui_sound("UISndPieMenuSliceHighlight0"); + break; + } + } + } + else + { + // clear out our selection + if (mHoverItem) + { + mHoverItem->setHighlight(FALSE); + mHoverItem = NULL; + } + } + + if( !handled && pointInView( x, y ) ) + { + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; + handled = TRUE; + } + + mHoverThisFrame = TRUE; + + return handled; +} + +BOOL LLPieMenu::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + BOOL handled = FALSE; + // The click was somewhere within our rectangle + LLMenuItemGL *item = pieItemFromXY( x, y ); + + if (item) + { + // lie to the item about where the click happened + // to make sure it's within the item's rectangle + handled = item->handleMouseDown( 0, 0, mask ); + } + + // always handle mouse down as mouse up will close open menus + return handled; +} + +BOOL LLPieMenu::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + mRightMouseDown = TRUE; + + // The click was somewhere within our rectangle + LLMenuItemGL *item = pieItemFromXY( x, y ); + S32 delta_x = x /*+ mShiftHoriz*/ - getLocalRect().getCenterX(); + S32 delta_y = y /*+ mShiftVert*/ - getLocalRect().getCenterY(); + BOOL clicked_in_pie = ((delta_x * delta_x) + (delta_y * delta_y) < mCurRadius*mCurRadius) || mUseInfiniteRadius; + + // grab mouse if right clicking anywhere within pie (even deadzone in middle), to detect drag outside of pie + if (clicked_in_pie) + { + // capture mouse cursor as if on initial menu show + gFocusMgr.setMouseCapture(this, NULL); + mShrinkBorderTimer.stop(); + mUseInfiniteRadius = TRUE; + handled = TRUE; + } + + if (item) + { + // lie to the item about where the click happened + // to make sure it's within the item's rectangle + if (item->handleMouseDown( 0, 0, mask )) + { + handled = TRUE; + } + } + + return handled; +} + +BOOL LLPieMenu::handleRightMouseUp( S32 x, S32 y, MASK mask ) +{ + // release mouse capture when right mouse button released, and we're past the shrink time + if (mShrinkBorderTimer.getStarted() && + mShrinkBorderTimer.getElapsedTimeF32() > PIE_SHRINK_TIME) + { + mUseInfiniteRadius = FALSE; + gFocusMgr.setMouseCapture(NULL, NULL); + } + + BOOL result = handleMouseUp( x, y, mask ); + mRightMouseDown = FALSE; + + return result; +} + +BOOL LLPieMenu::handleMouseUp( S32 x, S32 y, MASK mask ) +{ + BOOL handled = FALSE; + + // The click was somewhere within our rectangle + LLMenuItemGL *item = pieItemFromXY( x, y ); + + if (item) + { + // lie to the item about where the click happened + // to make sure it's within the item's rectangle + if (item->getEnabled()) + { + handled = item->handleMouseUp( 0, 0, mask ); + hide(TRUE); + } + } + + if (handled) + { + make_ui_sound("UISndClickRelease"); + } + + if (!handled && !mUseInfiniteRadius) + { + // call hidemenus to make sure transient selections get cleared + ((LLMenuHolderGL*)getParent())->hideMenus(); + } + + if (mFirstMouseDown) + { + make_ui_sound("UISndPieMenuAppear"); + mFirstMouseDown = FALSE; + } + + //FIXME: is this necessary? + if (!mShrinkBorderTimer.getStarted()) + { + mShrinkBorderTimer.start(); + } + + return handled; +} + + +// virtual +void LLPieMenu::draw() +{ + // clear hover if mouse moved away + if (!mHoverThisFrame && mHoverItem) + { + mHoverItem->setHighlight(FALSE); + mHoverItem = NULL; + } + + F32 width = (F32) mRect.getWidth(); + F32 height = (F32) mRect.getHeight(); + mCurRadius = PIE_SCALE_FACTOR * llmax( width/2, height/2 ); + + mOuterRingAlpha = mUseInfiniteRadius ? 0.f : 1.f; + if (mShrinkBorderTimer.getStarted()) + { + mOuterRingAlpha = clamp_rescale(mShrinkBorderTimer.getElapsedTimeF32(), 0.f, PIE_SHRINK_TIME, 0.f, 1.f); + mCurRadius *= clamp_rescale(mShrinkBorderTimer.getElapsedTimeF32(), 0.f, PIE_SHRINK_TIME, 1.f, 1.f / PIE_SCALE_FACTOR); + } + + // correct for non-square pixels + F32 center_x = width/2; + F32 center_y = height/2; + S32 steps = 100; + + glPushMatrix(); + { + glTranslatef(center_x, center_y, 0.f); + + F32 line_width = LLUI::sConfigGroup->getF32("PieMenuLineWidth"); + LLColor4 line_color = LLUI::sColorsGroup->getColor("PieMenuLineColor"); + LLColor4 bg_color = LLUI::sColorsGroup->getColor("PieMenuBgColor"); + LLColor4 selected_color = LLUI::sColorsGroup->getColor("PieMenuSelectedColor"); + + // main body + LLColor4 outer_color = bg_color; + outer_color.mV[VALPHA] *= mOuterRingAlpha; + gl_washer_2d( mCurRadius, (F32) PIE_CENTER_SIZE, steps, bg_color, outer_color ); + + // selected wedge + item_list_t::iterator item_iter; + S32 i = 0; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if ((*item_iter)->getHighlight()) + { + F32 arc_size = F_PI * 0.25f; + + F32 start_radians = (i * arc_size) - (arc_size * 0.5f); + F32 end_radians = start_radians + arc_size; + + LLColor4 outer_color = selected_color; + outer_color.mV[VALPHA] *= mOuterRingAlpha; + gl_washer_segment_2d( mCurRadius, (F32)PIE_CENTER_SIZE, start_radians, end_radians, steps / 8, selected_color, outer_color ); + } + i++; + } + + LLUI::setLineWidth( line_width ); + + // inner lines + outer_color = line_color; + outer_color.mV[VALPHA] *= mOuterRingAlpha; + gl_washer_spokes_2d( mCurRadius, (F32)PIE_CENTER_SIZE, 8, line_color, outer_color ); + + // inner circle + glColor4fv( line_color.mV ); + gl_circle_2d( 0, 0, (F32)PIE_CENTER_SIZE, steps, FALSE ); + + // outer circle + glColor4fv( outer_color.mV ); + gl_circle_2d( 0, 0, mCurRadius, steps, FALSE ); + + LLUI::setLineWidth(1.0f); + } + glPopMatrix(); + + mHoverThisFrame = FALSE; + + LLView::draw(); +} + +void LLPieMenu::drawBackground(LLMenuItemGL* itemp, LLColor4& color) +{ + F32 width = (F32) mRect.getWidth(); + F32 height = (F32) mRect.getHeight(); + F32 center_x = width/2; + F32 center_y = height/2; + S32 steps = 100; + + glColor4fv( color.mV ); + glPushMatrix(); + { + glTranslatef(center_x - itemp->getRect().mLeft, center_y - itemp->getRect().mBottom, 0.f); + + item_list_t::iterator item_iter; + S32 i = 0; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if ((*item_iter) == itemp) + { + F32 arc_size = F_PI * 0.25f; + + F32 start_radians = (i * arc_size) - (arc_size * 0.5f); + F32 end_radians = start_radians + arc_size; + + LLColor4 outer_color = color; + outer_color.mV[VALPHA] *= mOuterRingAlpha; + gl_washer_segment_2d( mCurRadius, (F32)PIE_CENTER_SIZE, start_radians, end_radians, steps / 8, color, outer_color ); + } + i++; + } + } + glPopMatrix(); +} + +// virtual +BOOL LLPieMenu::append(LLMenuItemGL *item) +{ + item->setBriefItem(TRUE); + item->setFont( LLFontGL::sSansSerifSmall ); + return LLMenuGL::append(item); +} + +// virtual +BOOL LLPieMenu::appendSeparator(const LLString &separator_name) +{ + LLMenuItemGL* separator = new LLMenuItemBlankGL(); + separator->setFont( LLFontGL::sSansSerifSmall ); + return append( separator ); +} + + +// virtual +BOOL LLPieMenu::appendMenu(LLPieMenu *menu, + enabled_callback enabled_cb, + void* user_data) +{ + if (menu == this) + { + llerrs << "Can't attach a pie menu to itself" << llendl; + } + LLPieMenuBranch *item; + item = new LLPieMenuBranch(menu->getName(), menu->getLabel(), menu, enabled_cb, user_data); + getParent()->addChild(item->getBranch()); + item->setFont( LLFontGL::sSansSerifSmall ); + return append( item ); +} + +// virtual +void LLPieMenu::arrange() +{ + const S32 rect_height = 180; + const S32 rect_width = 180; + + // all divide by 6 + const S32 CARD_X = 60; + const S32 DIAG_X = 48; + const S32 CARD_Y = 76; + const S32 DIAG_Y = 42; + + const S32 ITEM_CENTER_X[] = { CARD_X, DIAG_X, 0, -DIAG_X, -CARD_X, -DIAG_X, 0, DIAG_X }; + const S32 ITEM_CENTER_Y[] = { 0, DIAG_Y, CARD_Y, DIAG_Y, 0, -DIAG_Y, -CARD_Y, -DIAG_Y }; + + LLRect rect; + + S32 font_height = 0; + if( mItems.size() ) + { + font_height = (*mItems.begin())->getNominalHeight(); + } + S32 item_width = 0; + +// F32 sin_delta = OO_SQRT2; // sin(45 deg) +// F32 cos_delta = OO_SQRT2; // cos(45 deg) + + // TODO: Compute actual bounding rect for menu + + mRect.setOriginAndSize(mRect.mLeft, mRect.mBottom, rect_width, rect_height ); + + // place items around a circle, with item 0 at positive X, + // rotating counter-clockwise + item_list_t::iterator item_iter; + S32 i = 0; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + LLMenuItemGL *item = *item_iter; + + item_width = item->getNominalWidth(); + + // Put in the right place around a circle centered at 0,0 + rect.setCenterAndSize(ITEM_CENTER_X[i], + ITEM_CENTER_Y[i], + item_width, font_height ); + + // Correct for the actual rectangle size + rect.translate( rect_width/2, rect_height/2 ); + + item->setRect( rect ); + + // Make sure enablement is correct + item->buildDrawLabel(); + i++; + } +} + +LLMenuItemGL *LLPieMenu::pieItemFromXY(S32 x, S32 y) +{ + // We might have shifted this menu on draw. If so, we need + // to shift over mouseup events until we get a hover event. + //x += mShiftHoriz; + //y += mShiftVert; + + // An arc of the pie menu is 45 degrees + const F32 ARC_DEG = 45.f; + S32 delta_x = x - mRect.getWidth() / 2; + S32 delta_y = y - mRect.getHeight() / 2; + + // circle safe zone in the center + S32 dist_squared = delta_x*delta_x + delta_y*delta_y; + if (dist_squared < PIE_CENTER_SIZE*PIE_CENTER_SIZE) + { + return NULL; + } + + // infinite radius is only used with right clicks + S32 radius = llmax( mRect.getWidth()/2, mRect.getHeight()/2 ); + if (!(mUseInfiniteRadius && mRightMouseDown) && dist_squared > radius * radius) + { + return NULL; + } + + F32 angle = RAD_TO_DEG * (F32) atan2((F32)delta_y, (F32)delta_x); + + // rotate marks CCW so that east = [0, ARC_DEG) instead of + // [-ARC_DEG/2, ARC_DEG/2) + angle += ARC_DEG / 2.f; + + // make sure we're only using positive angles + if (angle < 0.f) angle += 360.f; + + S32 which = S32( angle / ARC_DEG ); + + if (0 <= which && which < (S32)mItems.size() ) + { + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if (which == 0) + { + return (*item_iter); + } + which--; + } + } + + return NULL; +} + +S32 LLPieMenu::pieItemIndexFromXY(S32 x, S32 y) +{ + // An arc of the pie menu is 45 degrees + const F32 ARC_DEG = 45.f; + // correct for non-square pixels + S32 delta_x = x - mRect.getWidth() / 2; + S32 delta_y = y - mRect.getHeight() / 2; + + // circle safe zone in the center + if (delta_x*delta_x + delta_y*delta_y < PIE_CENTER_SIZE*PIE_CENTER_SIZE) + { + return -1; + } + + F32 angle = RAD_TO_DEG * (F32) atan2((F32)delta_y, (F32)delta_x); + + // rotate marks CCW so that east = [0, ARC_DEG) instead of + // [-ARC_DEG/2, ARC_DEG/2) + angle += ARC_DEG / 2.f; + + // make sure we're only using positive angles + if (angle < 0.f) angle += 360.f; + + S32 which = S32( angle / ARC_DEG ); + return which; +} + +void LLPieMenu::show(S32 x, S32 y, BOOL mouse_down) +{ + S32 width = mRect.getWidth(); + S32 height = mRect.getHeight(); + + LLView* parent_view = getParent(); + S32 menu_region_width = parent_view->getRect().getWidth(); + S32 menu_region_height = parent_view->getRect().getHeight(); + + BOOL moved = FALSE; + + S32 local_x, local_y; + parent_view->screenPointToLocal(x, y, &local_x, &local_y); + + mRect.setCenterAndSize(local_x, local_y, width, height); + arrange(); + + // Adjust the pie rectangle to keep it on screen + if (mRect.mLeft < 0) + { + //mShiftHoriz = 0 - mRect.mLeft; + //mRect.translate( mShiftHoriz, 0 ); + mRect.translate( 0 - mRect.mLeft, 0 ); + moved = TRUE; + } + + if (mRect.mRight > menu_region_width) + { + //mShiftHoriz = menu_region_width - mRect.mRight; + //mRect.translate( mShiftHoriz, 0); + mRect.translate( menu_region_width - mRect.mRight, 0 ); + moved = TRUE; + } + + if (mRect.mBottom < 0) + { + //mShiftVert = -mRect.mBottom; + //mRect.translate( 0, mShiftVert ); + mRect.translate( 0, 0 - mRect.mBottom ); + moved = TRUE; + } + + + if (mRect.mTop > menu_region_height) + { + //mShiftVert = menu_region_height - mRect.mTop; + //mRect.translate( 0, mShiftVert ); + mRect.translate( 0, menu_region_height - mRect.mTop ); + moved = TRUE; + } + + // If we had to relocate the pie menu, put the cursor in the + // center of its rectangle + if (moved) + { + LLCoordGL center; + center.mX = (mRect.mLeft + mRect.mRight) / 2; + center.mY = (mRect.mTop + mRect.mBottom) / 2; + + LLUI::setCursorPositionLocal(getParent(), center.mX, center.mY); + } + + // FIXME: what happens when mouse buttons reversed? + mRightMouseDown = mouse_down; + mFirstMouseDown = mouse_down; + mUseInfiniteRadius = TRUE; + if (!mFirstMouseDown) + { + make_ui_sound("UISndPieMenuAppear"); + } + + LLView::setVisible(TRUE); + + // we want all mouse events in case user does quick right click again off of pie menu + // rectangle, to support gestural menu traversal + gFocusMgr.setMouseCapture(this, NULL); + + if (mouse_down) + { + mShrinkBorderTimer.stop(); + } + else + { + mShrinkBorderTimer.start(); + } +} + +void LLPieMenu::hide(BOOL item_selected) +{ + if (!getVisible()) return; + + if (mHoverItem) + { + mHoverItem->setHighlight( FALSE ); + mHoverItem = NULL; + } + + make_ui_sound("UISndPieMenuHide"); + + mFirstMouseDown = FALSE; + mRightMouseDown = FALSE; + mUseInfiniteRadius = FALSE; + + LLView::setVisible(FALSE); + + gFocusMgr.setMouseCapture(NULL, NULL); +} + +///============================================================================ +/// Class LLMenuBarGL +///============================================================================ + +// Default constructor +LLMenuBarGL::LLMenuBarGL( const LLString& name ) : LLMenuGL ( name, name ) +{ + mHorizontalLayout = TRUE; + setCanTearOff(FALSE); + mKeepFixedSize = TRUE; +} + +// Default destructor +LLMenuBarGL::~LLMenuBarGL() +{ + std::for_each(mAccelerators.begin(), mAccelerators.end(), DeletePointer()); + mAccelerators.clear(); +} + +// virtual +LLXMLNodePtr LLMenuBarGL::getXML(bool save_children) const +{ + // Sorty of hacky: reparent items to this and then back at the end of the export + LLView *orig_parent = NULL; + item_list_t::const_iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + LLMenuItemGL* child = *item_iter; + LLMenuItemBranchGL* branch = (LLMenuItemBranchGL*)child; + LLMenuGL *menu = branch->getBranch(); + orig_parent = menu->getParent(); + menu->updateParent((LLView *)this); + } + + LLXMLNodePtr node = LLMenuGL::getXML(); + + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + LLMenuItemGL* child = *item_iter; + LLMenuItemBranchGL* branch = (LLMenuItemBranchGL*)child; + LLMenuGL *menu = branch->getBranch(); + menu->updateParent(orig_parent); + } + + return node; +} + +LLView* LLMenuBarGL::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("menu"); + node->getAttributeString("name", name); + + BOOL opaque = FALSE; + node->getAttributeBOOL("opaque", opaque); + + LLMenuBarGL *menubar = new LLMenuBarGL(name); + + LLViewHandle parent_handle = LLViewHandle::sDeadHandle; + if (parent->getWidgetType() == WIDGET_TYPE_FLOATER) + { + parent_handle = ((LLFloater*)parent)->getHandle(); + } + + // We need to have the rect early so that it's around when building + // the menu items + LLRect view_rect; + createRect(node, view_rect, parent, menubar->getRequiredRect()); + menubar->setRect(view_rect); + + if (node->hasAttribute("drop_shadow")) + { + BOOL drop_shadow = FALSE; + node->getAttributeBOOL("drop_shadow", drop_shadow); + menubar->setDropShadowed(drop_shadow); + } + + menubar->setBackgroundVisible(opaque); + LLColor4 color(0,0,0,0); + if (opaque && LLUICtrlFactory::getAttributeColor(node,"color", color)) + { + menubar->setBackgroundColor(color); + } + + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + if (child->hasName("menu")) + { + LLMenuGL *menu = (LLMenuGL*)LLMenuGL::fromXML(child, parent, factory); + // because of lazy initialization, have to disable tear off functionality + // and then re-enable with proper parent handle + if (menu->getCanTearOff()) + { + menu->setCanTearOff(FALSE); + menu->setCanTearOff(TRUE, parent_handle); + } + menubar->appendMenu(menu); + if (LLMenuGL::sDefaultMenuContainer != NULL) + { + menu->updateParent(LLMenuGL::sDefaultMenuContainer); + } + else + { + menu->updateParent(parent); + } + } + } + + menubar->initFromXML(node, parent); + + BOOL create_jump_keys = FALSE; + node->getAttributeBOOL("create_jump_keys", create_jump_keys); + if (create_jump_keys) + { + menubar->createJumpKeys(); + } + + return menubar; +} + +void LLMenuBarGL::handleJumpKey(KEY key) +{ + navigation_key_map_t::iterator found_it = mJumpKeys.find(key); + if(found_it != mJumpKeys.end() && found_it->second->getEnabled()) + { + clearHoverItem(); + found_it->second->setHighlight(TRUE); + found_it->second->doIt(); + } +} + +// rearrange the child rects so they fit the shape of the menu bar. +void LLMenuBarGL::arrange( void ) +{ + U32 pos = 0; + LLRect rect( 0, mRect.getHeight(), 0, 0 ); + item_list_t::const_iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + LLMenuItemGL* item = *item_iter; + rect.mLeft = pos; + pos += item->getNominalWidth(); + rect.mRight = pos; + item->setRect( rect ); + item->buildDrawLabel(); + } +} + + +S32 LLMenuBarGL::getRightmostMenuEdge() +{ + // Find the last visible menu + item_list_t::reverse_iterator item_iter; + for (item_iter = mItems.rbegin(); item_iter != mItems.rend(); ++item_iter) + { + if ((*item_iter)->getVisible()) + { + break; + } + } + + if (item_iter == mItems.rend()) + { + return 0; + } + return (*item_iter)->getRect().mRight; +} + +// add a vertical separator to this menu +BOOL LLMenuBarGL::appendSeparator( const LLString &separator_name ) +{ + LLMenuItemGL* separator = new LLMenuItemVerticalSeparatorGL(); + return append( separator ); +} + +// add a menu - this will create a drop down menu. +BOOL LLMenuBarGL::appendMenu( LLMenuGL* menu ) +{ + if( menu == this ) + { + llerrs << "** Attempt to attach menu to itself. This is certainly " + << "a logic error." << llendl; + } + + BOOL success = TRUE; + + LLMenuItemBranchGL* branch = NULL; + branch = new LLMenuItemBranchDownGL( menu->getName(), menu->getLabel(), menu ); + success &= branch->addToAcceleratorList(&mAccelerators); + success &= append( branch ); + branch->setJumpKey(branch->getJumpKey()); + return success; +} + +BOOL LLMenuBarGL::handleHover( S32 x, S32 y, MASK mask ) +{ + BOOL handled = FALSE; + LLView* active_menu = NULL; + + S32 mouse_delta_x = x - mLastMouseX; + S32 mouse_delta_y = y - mLastMouseY; + mMouseVelX = (mMouseVelX / 2) + (mouse_delta_x / 2); + mMouseVelY = (mMouseVelY / 2) + (mouse_delta_y / 2); + mLastMouseX = x; + mLastMouseY = y; + + // if nothing currently selected or mouse has moved since last call, pick menu item via mouse + // otherwise let keyboard control it + if (!getHighlightedItem() || llabs(mMouseVelX) > 0 || llabs(mMouseVelY) > 0) + { + // find current active menu + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if (((LLMenuItemGL*)viewp)->isActive()) + { + active_menu = viewp; + } + } + + // check for new active menu + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + if( viewp->getVisible() && + viewp->getEnabled() && + viewp->pointInView(local_x, local_y) && + viewp->handleHover(local_x, local_y, mask)) + { + ((LLMenuItemGL*)viewp)->setHighlight(TRUE); + handled = TRUE; + if (active_menu && active_menu != viewp) + { + ((LLMenuItemGL*)viewp)->doIt(); + } + } + } + + if (handled) + { + // set hover false on inactive menus + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + if (!viewp->pointInView(local_x, local_y) && ((LLMenuItemGL*)viewp)->getHighlight()) + { + ((LLMenuItemGL*)viewp)->setHighlight(FALSE); + } + } + } + } + + getWindow()->setCursor(UI_CURSOR_ARROW); + + return TRUE; +} + +///============================================================================ +/// Class LLMenuHolderGL +///============================================================================ +LLMenuHolderGL::LLMenuHolderGL() +: LLPanel("Menu Holder") +{ + setMouseOpaque(FALSE); + sItemActivationTimer.stop(); + mCanHide = TRUE; +} + +LLMenuHolderGL::LLMenuHolderGL(const LLString& name, const LLRect& rect, BOOL mouse_opaque, U32 follows) +: LLPanel(name, rect, FALSE) +{ + setMouseOpaque(mouse_opaque); + sItemActivationTimer.stop(); + mCanHide = TRUE; +} + +LLMenuHolderGL::~LLMenuHolderGL() +{ +} + +EWidgetType LLMenuHolderGL::getWidgetType() const +{ + return WIDGET_TYPE_MENU_HOLDER; +} + +LLString LLMenuHolderGL::getWidgetTag() const +{ + return LL_MENU_HOLDER_GL_TAG; +} + +void LLMenuHolderGL::draw() +{ + LLView::draw(); + // now draw last selected item as overlay + LLMenuItemGL* selecteditem = (LLMenuItemGL*)LLView::getViewByHandle(sItemLastSelectedHandle); + if (selecteditem && sItemActivationTimer.getStarted() && sItemActivationTimer.getElapsedTimeF32() < ACTIVATE_HIGHLIGHT_TIME) + { + // make sure toggle items, for example, show the proper state when fading out + selecteditem->buildDrawLabel(); + + LLRect item_rect; + selecteditem->localRectToOtherView(selecteditem->getLocalRect(), &item_rect, this); + + F32 interpolant = sItemActivationTimer.getElapsedTimeF32() / ACTIVATE_HIGHLIGHT_TIME; + F32 alpha = lerp(LLMenuItemGL::sHighlightBackground.mV[VALPHA], 0.f, interpolant); + LLColor4 bg_color(LLMenuItemGL::sHighlightBackground.mV[VRED], + LLMenuItemGL::sHighlightBackground.mV[VGREEN], + LLMenuItemGL::sHighlightBackground.mV[VBLUE], + alpha); + + LLUI::pushMatrix(); + { + LLUI::translate((F32)item_rect.mLeft, (F32)item_rect.mBottom, 0.f); + selecteditem->getMenu()->drawBackground(selecteditem, bg_color); + selecteditem->draw(); + } + LLUI::popMatrix(); + } +} + +BOOL LLMenuHolderGL::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + BOOL handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; + if (!handled) + { + // clicked off of menu, hide them all + hideMenus(); + } + return handled; +} + +BOOL LLMenuHolderGL::handleRightMouseDown( S32 x, S32 y, MASK mask ) +{ + BOOL handled = LLView::childrenHandleRightMouseDown(x, y, mask) != NULL; + if (!handled) + { + // clicked off of menu, hide them all + hideMenus(); + } + return handled; +} + +void LLMenuHolderGL::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + if (width != mRect.getWidth() || height != mRect.getHeight()) + { + hideMenus(); + } + LLView::reshape(width, height, called_from_parent); +} + +BOOL LLMenuHolderGL::hasVisibleMenu() +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if (viewp->getVisible()) + { + return TRUE; + } + } + return FALSE; +} + +BOOL LLMenuHolderGL::hideMenus() +{ + if (!mCanHide) + { + return FALSE; + } + BOOL menu_visible = hasVisibleMenu(); + if (menu_visible) + { + // clicked off of menu, hide them all + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if (viewp->getVisible()) + { + viewp->setVisible(FALSE); + } + } + } + //if (gFocusMgr.childHasKeyboardFocus(this)) + //{ + // gFocusMgr.setKeyboardFocus(NULL, NULL); + //} + + return menu_visible; +} + +void LLMenuHolderGL::setActivatedItem(LLMenuItemGL* item) +{ + sItemLastSelectedHandle = item->mViewHandle; + sItemActivationTimer.start(); +} + +///============================================================================ +/// Class LLTearOffMenu +///============================================================================ +LLTearOffMenu::LLTearOffMenu(LLMenuGL* menup) : + LLFloater(menup->getName(), LLRect(0, 100, 100, 0), menup->getLabel(), FALSE, DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT, FALSE, FALSE) +{ + LLRect rect; + menup->localRectToOtherView(LLRect(-1, menup->getRect().getHeight(), menup->getRect().getWidth() + 3, 0), &rect, gFloaterView); + mTargetHeight = (F32)(rect.getHeight() + LLFLOATER_HEADER_SIZE + 5); + reshape(rect.getWidth(), rect.getHeight()); + setRect(rect); + mOldParent = menup->getParent(); + mOldParent->removeChild(menup); + + menup->setFollowsAll(); + addChild(menup); + menup->setVisible(TRUE); + menup->translate(-menup->getRect().mLeft + 1, -menup->getRect().mBottom + 1); + + menup->setTornOff(TRUE); + menup->setDropShadowed(FALSE); + + mMenu = menup; + + // highlight first item (tear off item will be disabled) + mMenu->highlightNextItem(NULL); +} + +LLTearOffMenu::~LLTearOffMenu() +{ +} + +void LLTearOffMenu::draw() +{ + if (hasFocus()) + { + LLMenuItemGL* parent_menu_item = mMenu->getParentMenuItem(); + while(parent_menu_item) + { + if (parent_menu_item->getMenu()->getVisible()) + { + parent_menu_item->setHighlight(TRUE); + parent_menu_item = parent_menu_item->getMenu()->getParentMenuItem(); + } + else + { + break; + } + } + } + + mMenu->setBackgroundVisible(mBgOpaque); + mMenu->arrange(); + + if (mRect.getHeight() != mTargetHeight) + { + // animate towards target height + reshape(mRect.getWidth(), llceil(lerp((F32)mRect.getHeight(), mTargetHeight, LLCriticalDamp::getInterpolant(0.05f)))); + } + else + { + // when in stasis, remain big enough to hold menu contents + mTargetHeight = (F32)(mMenu->getRect().getHeight() + LLFLOATER_HEADER_SIZE + 4); + reshape(mMenu->getRect().getWidth() + 3, mMenu->getRect().getHeight() + LLFLOATER_HEADER_SIZE + 5); + } + LLFloater::draw(); +} + +void LLTearOffMenu::onFocusReceived() +{ + // if nothing is highlighted, just highlight first item + if (!mMenu->getHighlightedItem()) + { + mMenu->highlightNextItem(NULL); + } +} + +void LLTearOffMenu::onFocusLost() +{ + // remove highlight from parent item and our own menu + mMenu->clearHoverItem(); +} + +//static +LLTearOffMenu* LLTearOffMenu::create(LLMenuGL* menup) +{ + LLTearOffMenu* tearoffp = new LLTearOffMenu(menup); + // keep onscreen + gFloaterView->adjustToFitScreen(tearoffp, FALSE); + tearoffp->open(); + return tearoffp; +} + +void LLTearOffMenu::onClose(bool app_quitting) +{ + removeChild(mMenu); + mOldParent->addChild(mMenu); + mMenu->clearHoverItem(); + mMenu->setFollowsNone(); + mMenu->setBackgroundVisible(TRUE); + mMenu->setVisible(FALSE); + mMenu->setTornOff(FALSE); + mMenu->setDropShadowed(TRUE); + destroy(); +} + +///============================================================================ +/// Class LLEditMenuHandlerMgr +///============================================================================ +LLEditMenuHandlerMgr& LLEditMenuHandlerMgr::getInstance() +{ + static LLEditMenuHandlerMgr instance; + return instance; +} + +LLEditMenuHandlerMgr::LLEditMenuHandlerMgr() +{ +} + +LLEditMenuHandlerMgr::~LLEditMenuHandlerMgr() +{ +} + +///============================================================================ +/// Local function definitions +///============================================================================ diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h new file mode 100644 index 0000000000..84cbf13b69 --- /dev/null +++ b/indra/llui/llmenugl.h @@ -0,0 +1,728 @@ +/** + * @file llmenugl.h + * @brief Declaration of the opengl based menu system. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMENUGL_H +#define LL_LLMENUGL_H + +#include + +#include "llstring.h" +#include "v4color.h" +#include "llframetimer.h" +#include "llevent.h" + +#include "llkeyboard.h" +#include "llfloater.h" +#include "lluistring.h" +#include "llview.h" + +class LLMenuItemGL; + +extern S32 MENU_BAR_HEIGHT; +extern S32 MENU_BAR_WIDTH; + +// These callbacks are used by the LLMenuItemCallGL and LLMenuItemCheckGL +// classes during their work. +typedef void (*menu_callback)(void*); + +// These callbacks are used by the LLMenuItemCallGL +// classes during their work. +typedef void (*on_disabled_callback)(void*); + +// This callback is used by the LLMenuItemCallGL and LLMenuItemCheckGL +// to determine if the current menu is enabled. +typedef BOOL (*enabled_callback)(void*); + +// This callback is used by LLMenuItemCheckGL to determine it's +// 'checked' state. +typedef BOOL (*check_callback)(void*); + +// This callback is potentially used by LLMenuItemCallGL. If provided, +// this function is called whenever it's time to determine the label's +// contents. Put the contents of the label in the provided parameter. +typedef void (*label_callback)(LLString&,void*); + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemGL +// +// The LLMenuItemGL represents a single menu item in a menu. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLFontGL; +class LLMenuGL; + + +class LLMenuItemGL : public LLView +{ +public: + LLMenuItemGL( const LLString& name, const LLString& label, KEY key = KEY_NONE, MASK = MASK_NONE ); + + virtual void setValue(const LLSD& value) { setLabel(value.asString()); } + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_MENU_ITEM; } + virtual LLString getWidgetTag() const { return LL_MENU_ITEM_TAG; } + + virtual LLXMLNodePtr getXML(bool save_children = true) const; + + virtual LLString getType() const { return "item"; } + + virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + + virtual BOOL handleAcceleratorKey(KEY key, MASK mask); + + BOOL getHighlight() const { return mHighlight; } + + void setJumpKey(KEY key); + KEY getJumpKey(); + + // set the font used by this item. + void setFont(LLFontGL* font); + void setFontStyle(U8 style) { mStyle = style; } + + // returns the height in pixels for the current font. + virtual U32 getNominalHeight( void ); + + // functions to control the color scheme + static void setEnabledColor( const LLColor4& color ); + static void setDisabledColor( const LLColor4& color ); + static void setHighlightBGColor( const LLColor4& color ); + static void setHighlightFGColor( const LLColor4& color ); + + // Marks item as not needing space for check marks or accelerator keys + virtual void setBriefItem(BOOL brief); + + virtual BOOL addToAcceleratorList(std::list *listp); + void setAllowKeyRepeat(BOOL allow) { mAllowKeyRepeat = allow; } + + // return the name label + LLString getLabel( void ) const { return mLabel.getString(); } + + // change the label + void setLabel( const LLString& label ); + virtual BOOL setLabelArg( const LLString& key, const LLString& text ); + + // Get the parent menu for this item + virtual LLMenuGL* getMenu(); + + // returns the normal width of this control in pixels - this is + // used for calculating the widest item, as well as for horizontal + // arrangement. + virtual U32 getNominalWidth( void ); + + // buildDrawLabel() - constructs the string used during the draw() + // function. This reduces the overall string manipulation, but can + // lead to visual errors if the state of the object changes + // without the knowledge of the menu item. For example, if a + // boolean being watched is changed outside of the menu item's + // doIt() function, the draw buffer will not be updated and will + // reflect the wrong value. If this ever becomes an issue, there + // are ways to fix this. + // Returns the enabled state of the item. + virtual void buildDrawLabel( void ); + + // for branching menu items, bring sub menus up to root level of menu hierarchy + virtual void updateBranchParent( LLView* parentp ){}; + + // doIt() - do the primary funcationality of the menu item. + virtual void doIt( void ) = 0; + + // set the hover status (called by it's menu) + virtual void setHighlight( BOOL highlight ); + + // determine if this object is active + virtual BOOL isActive( void ) const; + + virtual void setEnabledSubMenus(BOOL enable){}; + + // LLView Functionality + virtual BOOL handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ); + virtual BOOL handleMouseUp( S32 x, S32 y, MASK mask ); + virtual void draw( void ); + + BOOL getDrawTextDisabled() const { return mDrawTextDisabled; } + +protected: + // This function appends the character string representation of + // the current accelerator key and mask to the provided string. + void appendAcceleratorString( LLString& st ); + +public: + static LLColor4 sEnabledColor; + static LLColor4 sDisabledColor; + static LLColor4 sHighlightBackground; + static LLColor4 sHighlightForeground; + +protected: + static BOOL sDropShadowText; + + // mLabel contains the actual label specified by the user. + LLUIString mLabel; + + // The draw labels contain some of the labels that we draw during + // the draw() routine. This optimizes away some of the string + // manipulation. + LLUIString mDrawBoolLabel; + LLUIString mDrawAccelLabel; + LLUIString mDrawBranchLabel; + + // Keyboard and mouse variables + KEY mJumpKey; + KEY mAcceleratorKey; + MASK mAcceleratorMask; + BOOL mAllowKeyRepeat; + BOOL mHighlight; + BOOL mGotHover; + + // If true, suppress normal space for check marks on the left and accelerator + // keys on the right. + BOOL mBriefItem; + + // Font for this item + LLFontGL* mFont; + + U8 mStyle; + BOOL mDrawTextDisabled; +}; + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemCallGL +// +// The LLMenuItemCallerGL represents a single menu item in a menu that +// calls a user defined callback. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemCallGL : public LLMenuItemGL +{ +protected: + menu_callback mCallback; + // mEnabledCallback should return TRUE if the item should be enabled + enabled_callback mEnabledCallback; + label_callback mLabelCallback; + void* mUserData; + on_disabled_callback mOnDisabledCallback; + +public: + + + void setMenuCallback(menu_callback callback, void* data) { mCallback = callback; mUserData = data; }; + void setEnabledCallback(enabled_callback callback) { mEnabledCallback = callback; }; + + // normal constructor + LLMenuItemCallGL( const LLString& name, + menu_callback clicked_cb, + enabled_callback enabled_cb = NULL, + void* user_data = NULL, + KEY key = KEY_NONE, MASK mask = MASK_NONE, + BOOL enabled = TRUE, + on_disabled_callback on_disabled_cb = NULL); + LLMenuItemCallGL( const LLString& name, + const LLString& label, + menu_callback clicked_cb, + enabled_callback enabled_cb = NULL, + void* user_data = NULL, + KEY key = KEY_NONE, MASK mask = MASK_NONE, + BOOL enabled = TRUE, + on_disabled_callback on_disabled_cb = NULL); + + // constructor for when you want to trap the arrange method. + LLMenuItemCallGL( const LLString& name, + const LLString& label, + menu_callback clicked_cb, + enabled_callback enabled_cb, + label_callback label_cb, + void* user_data, + KEY key = KEY_NONE, MASK mask = MASK_NONE, + BOOL enabled = TRUE, + on_disabled_callback on_disabled_c = NULL); + LLMenuItemCallGL( const LLString& name, + menu_callback clicked_cb, + enabled_callback enabled_cb, + label_callback label_cb, + void* user_data, + KEY key = KEY_NONE, MASK mask = MASK_NONE, + BOOL enabled = TRUE, + on_disabled_callback on_disabled_c = NULL); + virtual LLXMLNodePtr getXML(bool save_children = true) const; + + virtual LLString getType() const { return "call"; } + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + void setEnabledControl(LLString enabled_control, LLView *context); + void setVisibleControl(LLString enabled_control, LLView *context); + + virtual void setUserData(void *userdata) { mUserData = userdata; } + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + // doIt() - do the primary funcationality of the menu item. + virtual void doIt( void ); + + virtual BOOL handleAcceleratorKey(KEY key, MASK mask); + + //virtual void draw(); + + virtual bool handleEvent(LLPointer event, const LLSD& userdata); +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemCheckGL +// +// The LLMenuItemCheckGL is an extension of the LLMenuItemCallGL +// class, by allowing another method to be specified which determines +// if the menu item should consider itself checked as true or not. Be +// careful that the check callback provided - it needs to be VERY +// FUCKING EFFICIENT, because it may need to be checked a lot. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemCheckGL +: public LLMenuItemCallGL +{ +protected: + check_callback mCheckCallback; + BOOL mChecked; + +public: + LLMenuItemCheckGL( const LLString& name, + const LLString& label, + menu_callback callback, + enabled_callback enabled_cb, + check_callback check, + void* user_data, + KEY key = KEY_NONE, MASK mask = MASK_NONE ); + LLMenuItemCheckGL( const LLString& name, + menu_callback callback, + enabled_callback enabled_cb, + check_callback check, + void* user_data, + KEY key = KEY_NONE, MASK mask = MASK_NONE ); + LLMenuItemCheckGL( const LLString& name, + const LLString& label, + menu_callback callback, + enabled_callback enabled_cb, + LLString control_name, + LLView *context, + void* user_data, + KEY key = KEY_NONE, MASK mask = MASK_NONE ); + virtual LLXMLNodePtr getXML(bool save_children = true) const; + + void setCheckedControl(LLString checked_control, LLView *context); + + virtual void setValue(const LLSD& value) { mChecked = value.asBoolean(); } + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual LLString getType() const { return "check"; } + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + virtual bool handleEvent(LLPointer event, const LLSD& userdata); + + // LLView Functionality + //virtual void draw( void ); +}; + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemToggleGL +// +// The LLMenuItemToggleGL is a menu item that wraps around a user +// specified and controlled boolean. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemToggleGL : public LLMenuItemGL +{ +protected: + BOOL* mToggle; + +public: + LLMenuItemToggleGL( const LLString& name, const LLString& label, + BOOL* toggle, + KEY key = KEY_NONE, MASK mask = MASK_NONE ); + + LLMenuItemToggleGL( const LLString& name, + BOOL* toggle, + KEY key = KEY_NONE, MASK mask = MASK_NONE ); + + virtual LLString getType() const { return "toggle"; } + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + // doIt() - do the primary funcationality of the menu item. + virtual void doIt( void ); + + // LLView Functionality + //virtual void draw( void ); +}; + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuGL +// +// The Menu class represents a normal rectangular menu somewhere on +// screen. A Menu can have menu items (described above) or sub-menus +// attached to it. Sub-menus are implemented via a specialized +// menu-item type known as a branch. Since it's easy to do wrong, I've +// taken the branch functionality out of public view, and encapsulate +// it in the appendMenu() method. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuArrowGL; +class LLMenuItemBranchGL; +class LLMenuItemTearOffGL; + +class LLMenuGL +: public LLUICtrl +{ +public: + LLMenuGL( const LLString& name, const LLString& label, LLViewHandle parent_floater = LLViewHandle::sDeadHandle ); + LLMenuGL( const LLString& label, LLViewHandle parent_floater = LLViewHandle::sDeadHandle ); + virtual ~LLMenuGL( void ); + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + void parseChildXML(LLXMLNodePtr child, LLView *parent, LLUICtrlFactory *factory); + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_MENU; } + virtual LLString getWidgetTag() const { return LL_MENU_GL_TAG; } + + // LLView Functionality + virtual BOOL handleKey( KEY key, MASK mask, BOOL called_from_parent ); + virtual BOOL handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ); + virtual BOOL handleHover( S32 x, S32 y, MASK mask ); + virtual BOOL handleMouseUp( S32 x, S32 y, MASK mask ); + virtual void draw( void ); + virtual void drawBackground(LLMenuItemGL* itemp, LLColor4& color); + virtual void setVisible(BOOL visible); + + virtual BOOL LLMenuGL::handleAcceleratorKey(KEY key, MASK mask); + + LLMenuGL* getChildMenuByName(const LLString& name, BOOL recurse) const; + + BOOL clearHoverItem(BOOL include_active = TRUE); + + // return the name label + const LLString& getLabel( void ) const { return mLabel.getString(); } + void setLabel(const LLString& label) { mLabel = label; } + + static void setDefaultBackgroundColor( const LLColor4& color ); + void setBackgroundColor( const LLColor4& color ); + void setBackgroundVisible( BOOL b ) { mBgVisible = b; } + void setCanTearOff(BOOL tear_off, LLViewHandle parent_floater_handle = LLViewHandle::sDeadHandle); + + // Add the menu item to this menu. + virtual BOOL append( LLMenuItemGL* item ); + + // add a separator to this menu + virtual BOOL appendSeparator( const LLString &separator_name = "separator" ); + + // add a menu - this will create a cascading menu + virtual BOOL appendMenu( LLMenuGL* menu ); + + // for branching menu items, bring sub menus up to root level of menu hierarchy + virtual void updateParent( LLView* parentp ); + + // setItemEnabled() - pass the name and the enable flag for a + // menu item. TRUE will make sure it's enabled, FALSE will disable + // it. + void setItemEnabled( const LLString& name, BOOL enable ); + + // propagate message to submenus + void setEnabledSubMenus(BOOL enable); + + void setItemVisible( const LLString& name, BOOL visible); + + // sets the left,bottom corner of menu, useful for popups + void setLeftAndBottom(S32 left, S32 bottom); + + virtual void handleJumpKey(KEY key); + + // Shape this menu to fit the current state of the children, and + // adjust the child rects to fit. This is called automatically + // when you add items. *FIX: We may need to deal with visibility + // arrangement. + virtual void arrange( void ); + + // remove all items on the menu + void empty( void ); + + // Rearrange the components, and do the right thing if the menu doesn't + // fit in the bounds. + // virtual void arrangeWithBounds(LLRect bounds); + + void setItemLastSelected(LLMenuItemGL* item); // must be in menu + U32 getItemCount(); // number of menu items + LLMenuItemGL* getItem(S32 number); // 0 = first item + LLMenuItemGL* getHighlightedItem(); + + LLMenuItemGL* highlightNextItem(LLMenuItemGL* cur_item, BOOL skip_disabled = TRUE); + LLMenuItemGL* highlightPrevItem(LLMenuItemGL* cur_item, BOOL skip_disabled = TRUE); + + void buildDrawLabels(); + void createJumpKeys(); + + // Show popup in global screen space based on last mouse location. + static void showPopup(LLMenuGL* menu); + + // Show popup at a specific location. + static void showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y); + + // Whether to drop shadow menu bar + void setDropShadowed( const BOOL shadowed ); + + void setParentMenuItem( LLMenuItemGL* parent_menu_item ) { mParentMenuItem = parent_menu_item; } + LLMenuItemGL* getParentMenuItem() { return mParentMenuItem; } + + void setTornOff(BOOL torn_off); + BOOL getTornOff() { return mTornOff; } + + BOOL getCanTearOff() { return mTearOffItem != NULL; } + + KEY getJumpKey() { return mJumpKey; } + void setJumpKey(KEY key) { mJumpKey = key; } + + static void onFocusLost(LLView* old_focus); + + static LLView *sDefaultMenuContainer; + +protected: + void createSpilloverBranch(); + void cleanupSpilloverBranch(); + +protected: + static LLColor4 sDefaultBackgroundColor; + + LLColor4 mBackgroundColor; + BOOL mBgVisible; + typedef std::list< LLMenuItemGL* > item_list_t; + item_list_t mItems; + typedef std::map navigation_key_map_t; + navigation_key_map_t mJumpKeys; + LLMenuItemGL* mParentMenuItem; + LLUIString mLabel; + BOOL mDropShadowed; // Whether to drop shadow + BOOL mHorizontalLayout; + BOOL mKeepFixedSize; + BOOL mHasSelection; + LLFrameTimer mFadeTimer; + S32 mLastMouseX; + S32 mLastMouseY; + S32 mMouseVelX; + S32 mMouseVelY; + BOOL mTornOff; + LLMenuItemTearOffGL* mTearOffItem; + LLMenuItemBranchGL* mSpilloverBranch; + LLMenuGL* mSpilloverMenu; + LLViewHandle mParentFloaterHandle; + KEY mJumpKey; +}; + +//----------------------------------------------------------------------------- +// class LLPieMenu +// A circular menu of items, icons, etc. +//----------------------------------------------------------------------------- + +class LLPieMenu +: public LLMenuGL +{ +public: + LLPieMenu(const LLString& name, const LLString& label); + LLPieMenu(const LLString& name); + virtual ~LLPieMenu(); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + void initXML(LLXMLNodePtr node, LLView *context, LLUICtrlFactory *factory); + + // LLView Functionality + // can't set visibility directly, must call show or hide + virtual void setVisible(BOOL visible); + + //virtual BOOL handleKey( KEY key, MASK mask, BOOL called_from_parent ); + virtual BOOL handleHover( S32 x, S32 y, MASK mask ); + virtual BOOL handleMouseDown( S32 x, S32 y, MASK mask ); + virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleRightMouseUp( S32 x, S32 y, MASK mask ); + virtual BOOL handleMouseUp( S32 x, S32 y, MASK mask ); + virtual void draw(); + virtual void drawBackground(LLMenuItemGL* itemp, LLColor4& color); + + virtual BOOL append(LLMenuItemGL* item); + virtual BOOL appendSeparator( const LLString &separator_name = "separator" ); + + // the enabled callback is meant for the submenu. The api works + // this way because the menu branch item responsible for the pie + // submenu is constructed here. + virtual BOOL appendMenu(LLPieMenu *menu, + enabled_callback enabled_cb = NULL, + void* user_data = NULL ); + virtual void arrange( void ); + + // Display the menu centered on this point on the screen. + void show(S32 x, S32 y, BOOL mouse_down); + void hide(BOOL item_selected); + +protected: + LLMenuItemGL *pieItemFromXY(S32 x, S32 y); + S32 pieItemIndexFromXY(S32 x, S32 y); + +private: + // These cause menu items to be spuriously selected by right-clicks + // near the window edge at low frame rates. I don't think they are + // needed unless you shift the menu position in the draw() function. JC + //S32 mShiftHoriz; // non-zero if menu had to shift this frame + //S32 mShiftVert; // non-zero if menu had to shift this frame + BOOL mFirstMouseDown; // true from show until mouse up + BOOL mUseInfiniteRadius; // allow picking pie menu items anywhere outside of center circle + LLMenuItemGL* mHoverItem; + BOOL mHoverThisFrame; + LLFrameTimer mShrinkBorderTimer; + F32 mOuterRingAlpha; // for rendering pie menus as both bounded and unbounded + F32 mCurRadius; + BOOL mRightMouseDown; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuBarGL +// +// A menu bar displays menus horizontally. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuBarGL : public LLMenuGL +{ +protected: + std::list mAccelerators; + +public: + LLMenuBarGL( const LLString& name ); + virtual ~LLMenuBarGL(); + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_MENU_BAR; } + virtual LLString getWidgetTag() const { return LL_MENU_BAR_GL_TAG; } + + // rearrange the child rects so they fit the shape of the menu + // bar. + virtual void handleJumpKey(KEY key); + virtual void arrange( void ); + + // add a vertical separator to this menu + virtual BOOL appendSeparator( const LLString &separator_name = "separator" ); + + // add a menu - this will create a drop down menu. + virtual BOOL appendMenu( LLMenuGL* menu ); + + // LLView Functionality + virtual BOOL handleHover( S32 x, S32 y, MASK mask ); + + // Returns x position of rightmost child, usually Help menu + S32 getRightmostMenuEdge(); +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuHolderGL +// +// High level view that serves as parent for all menus +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLMenuHolderGL : public LLPanel +{ +public: + LLMenuHolderGL(); + LLMenuHolderGL(const LLString& name, const LLRect& rect, BOOL mouse_opaque, U32 follows = FOLLOWS_NONE); + virtual ~LLMenuHolderGL(); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual BOOL hideMenus(); + void reshape(S32 width, S32 height, BOOL called_from_parent); + void setCanHide(BOOL can_hide) { mCanHide = can_hide; } + + // LLView functionality + virtual void draw(); + virtual BOOL handleMouseDown( S32 x, S32 y, MASK mask ); + virtual BOOL handleRightMouseDown( S32 x, S32 y, MASK mask ); + + static void setActivatedItem(LLMenuItemGL* item); +protected: + BOOL hasVisibleMenu(); + + static LLViewHandle sItemLastSelectedHandle; + static LLFrameTimer sItemActivationTimer; + + BOOL mCanHide; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLTearOffMenu +// +// Floater that hosts a menu +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLTearOffMenu : public LLFloater +{ +public: + static LLTearOffMenu* create(LLMenuGL* menup); + virtual ~LLTearOffMenu(); + virtual void onClose(bool app_quitting); + virtual void draw(void); + virtual void onFocusReceived(); + virtual void onFocusLost(); + +protected: + LLTearOffMenu(LLMenuGL* menup); + +protected: + LLView* mOldParent; + LLMenuGL* mMenu; + F32 mTargetHeight; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemTearOffGL +// +// This class represents a separator. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemTearOffGL : public LLMenuItemGL +{ +public: + LLMenuItemTearOffGL( LLViewHandle parent_floater_handle = (LLViewHandle)LLViewHandle::sDeadHandle ); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual LLString getType() const { return "tearoff_menu"; } + + virtual void doIt(void); + virtual void draw(void); + virtual U32 getNominalHeight(); + +protected: + LLViewHandle mParentHandle; +}; + + +//FIXME: this is currently working, so finish implementation +class LLEditMenuHandlerMgr +{ +public: + LLEditMenuHandlerMgr& getInstance(); + virtual ~LLEditMenuHandlerMgr(); +protected: + LLEditMenuHandlerMgr(); + +}; + +#endif // LL_LLMENUGL_H diff --git a/indra/llui/llmodaldialog.cpp b/indra/llui/llmodaldialog.cpp new file mode 100644 index 0000000000..4eaf6b7559 --- /dev/null +++ b/indra/llui/llmodaldialog.cpp @@ -0,0 +1,288 @@ +/** + * @file llmodaldialog.cpp + * @brief LLModalDialog base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llmodaldialog.h" + +#include "llfocusmgr.h" +#include "v4color.h" +#include "v2math.h" +#include "llui.h" +#include "llwindow.h" +#include "llkeyboard.h" + +// static +std::list LLModalDialog::sModalStack; + +LLModalDialog::LLModalDialog( const LLString& title, S32 width, S32 height, BOOL modal ) + : LLFloater( "modal container", + LLRect( 0, height, width, 0 ), + title, + FALSE, // resizable + DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT, + FALSE, // drag_on_left + modal ? FALSE : TRUE, // minimizable + modal ? FALSE : TRUE, // close button + TRUE), // bordered + mModal( modal ) +{ + setVisible( FALSE ); + setBackgroundVisible(TRUE); + setBackgroundOpaque(TRUE); + centerOnScreen(); // default position +} + +LLModalDialog::~LLModalDialog() +{ + // don't unlock focus unless we have it + if (gFocusMgr.childHasKeyboardFocus(this)) + { + gFocusMgr.unlockFocus(); + } +} + +void LLModalDialog::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLFloater::reshape(width, height, called_from_parent); + centerOnScreen(); +} + +void LLModalDialog::startModal() +{ + if (mModal) + { + // If Modal, Hide the active modal dialog + if (!sModalStack.empty()) + { + LLModalDialog* front = sModalStack.front(); + front->setVisible(FALSE); + } + + // This is a modal dialog. It sucks up all mouse and keyboard operations. + gFocusMgr.setMouseCapture( this, NULL ); + gFocusMgr.setTopView( this, NULL ); + setFocus(TRUE); + + sModalStack.push_front( this ); + } + + setVisible( TRUE ); +} + +void LLModalDialog::stopModal() +{ + gFocusMgr.unlockFocus(); + gFocusMgr.releaseFocusIfNeeded( this ); + + if (mModal) + { + std::list::iterator iter = std::find(sModalStack.begin(), sModalStack.end(), this); + if (iter != sModalStack.end()) + { + sModalStack.erase(iter); + } + else + { + llwarns << "LLModalDialog::stopModal not in list!" << llendl; + } + } + if (!sModalStack.empty()) + { + LLModalDialog* front = sModalStack.front(); + front->setVisible(TRUE); + } +} + + +void LLModalDialog::setVisible( BOOL visible ) +{ + if (mModal) + { + if( visible ) + { + // This is a modal dialog. It sucks up all mouse and keyboard operations. + gFocusMgr.setMouseCapture( this, NULL ); + + // The dialog view is a root view + gFocusMgr.setTopView( this, NULL ); + setFocus( TRUE ); + } + else + { + gFocusMgr.releaseFocusIfNeeded( this ); + } + } + + LLFloater::setVisible( visible ); +} + +BOOL LLModalDialog::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (!LLFloater::handleMouseDown(x, y, mask)) + { + if (mModal) + { + // Click was outside the panel + make_ui_sound("UISndInvalidOp"); + } + } + return TRUE; +} + +BOOL LLModalDialog::handleHover(S32 x, S32 y, MASK mask) +{ + if( childrenHandleHover(x, y, mask) == NULL ) + { + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; + } + return TRUE; +} + +BOOL LLModalDialog::handleMouseUp(S32 x, S32 y, MASK mask) +{ + childrenHandleMouseUp(x, y, mask); + return TRUE; +} + +BOOL LLModalDialog::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + childrenHandleScrollWheel(x, y, clicks); + return TRUE; +} + +BOOL LLModalDialog::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if (!LLFloater::handleDoubleClick(x, y, mask)) + { + // Click outside the panel + make_ui_sound("UISndInvalidOp"); + } + return TRUE; +} + +BOOL LLModalDialog::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + childrenHandleRightMouseDown(x, y, mask); + return TRUE; +} + + +BOOL LLModalDialog::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent ) +{ + childrenHandleKey(key, mask); + + LLFloater::handleKeyHere(key, mask, called_from_parent ); + + if (mModal) + { + // Suck up all keystokes except CTRL-Q. + BOOL is_quit = ('Q' == key) && (MASK_CONTROL == mask); + return !is_quit; + } + else + { + // don't process escape key until message box has been on screen a minimal amount of time + // to avoid accidentally destroying the message box when user is hitting escape at the time it appears + BOOL enough_time_elapsed = mVisibleTime.getElapsedTimeF32() > 1.0f; + if (enough_time_elapsed && key == KEY_ESCAPE) + { + close(); + return TRUE; + } + return FALSE; + } +} + +void LLModalDialog::onClose(bool app_quitting) +{ + stopModal(); + LLFloater::onClose(app_quitting); +} + +// virtual +void LLModalDialog::draw() +{ + if (getVisible()) + { + LLColor4 shadow_color = LLUI::sColorsGroup->getColor("ColorDropShadow"); + S32 shadow_lines = LLUI::sConfigGroup->getS32("DropShadowFloater"); + + gl_drop_shadow( 0, mRect.getHeight(), mRect.getWidth(), 0, + shadow_color, shadow_lines); + + LLFloater::draw(); + + if (mModal) + { + // If we've lost focus to a non-child, get it back ASAP. + if( gFocusMgr.getTopView() != this ) + { + gFocusMgr.setTopView( this, NULL); + } + + if( !gFocusMgr.childHasKeyboardFocus( this ) ) + { + setFocus(TRUE); + } + + if( !gFocusMgr.childHasMouseCapture( this ) ) + { + gFocusMgr.setMouseCapture( this, NULL ); + } + } + } +} + +void LLModalDialog::centerOnScreen() +{ + LLVector2 window_size = LLUI::getWindowSize(); + + S32 dialog_left = (llround(window_size.mV[VX]) - mRect.getWidth()) / 2; + S32 dialog_bottom = (llround(window_size.mV[VY]) - mRect.getHeight()) / 2; + + translate( dialog_left - mRect.mLeft, dialog_bottom - mRect.mBottom ); +} + + +// static +void LLModalDialog::onAppFocusLost() +{ + if( !sModalStack.empty() ) + { + LLModalDialog* instance = LLModalDialog::sModalStack.front(); + if( gFocusMgr.childHasMouseCapture( instance ) ) + { + gFocusMgr.setMouseCapture( NULL, NULL ); + } + + if( gFocusMgr.childHasKeyboardFocus( instance ) ) + { + gFocusMgr.setKeyboardFocus( NULL, NULL ); + } + } +} + +// static +void LLModalDialog::onAppFocusGained() +{ + if( !sModalStack.empty() ) + { + LLModalDialog* instance = LLModalDialog::sModalStack.front(); + + // This is a modal dialog. It sucks up all mouse and keyboard operations. + gFocusMgr.setMouseCapture( instance, NULL ); + instance->setFocus(TRUE); + gFocusMgr.setTopView( instance, NULL ); + + instance->centerOnScreen(); + } +} + + diff --git a/indra/llui/llmodaldialog.h b/indra/llui/llmodaldialog.h new file mode 100644 index 0000000000..b97e95d12c --- /dev/null +++ b/indra/llui/llmodaldialog.h @@ -0,0 +1,60 @@ +/** + * @file llmodaldialog.h + * @brief LLModalDialog base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMODALDIALOG_H +#define LL_LLMODALDIALOG_H + +#include "llfloater.h" +#include "llframetimer.h" + +class LLModalDialog; + +// By default, a ModalDialog is modal, i.e. no other window can have focus +// However, for the sake of code reuse and simplicity, if mModal == false, +// the dialog behaves like a normal floater + +class LLModalDialog : public LLFloater +{ +public: + LLModalDialog( const LLString& title, S32 width, S32 height, BOOL modal = true ); + /*virtual*/ ~LLModalDialog(); + + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = 1); + + /*virtual*/ void startModal(); + /*virtual*/ void stopModal(); + + /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); + /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent ); + + /*virtual*/ void onClose(bool app_quitting); + + /*virtual*/ void setVisible(BOOL visible); + /*virtual*/ void draw(); + + static void onAppFocusLost(); + static void onAppFocusGained(); + + static S32 activeCount() { return sModalStack.size(); } + +protected: + void centerOnScreen(); + +protected: + LLFrameTimer mVisibleTime; + BOOL mModal; // do not change this after creation! + + static std::list sModalStack; // Top of stack is currently being displayed +}; + +#endif // LL_LLMODALDIALOG_H diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp new file mode 100644 index 0000000000..91bf6befe7 --- /dev/null +++ b/indra/llui/llpanel.cpp @@ -0,0 +1,1030 @@ +/** + * @file llpanel.cpp + * @brief LLPanel base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Opaque view with a background and a border. Can contain LLUICtrls. + +#include "linden_common.h" + +#include "llpanel.h" + +#include "llalertdialog.h" +#include "llfocusmgr.h" +#include "llfontgl.h" +#include "llrect.h" +#include "llerror.h" +#include "lltimer.h" + +#include "llmenugl.h" +//#include "llstatusbar.h" +#include "llui.h" +#include "llkeyboard.h" +#include "lllineeditor.h" +#include "llcontrol.h" +#include "lltextbox.h" +#include "lluictrl.h" +#include "lluictrlfactory.h" +#include "llviewborder.h" +#include "llbutton.h" + +LLPanel::panel_map_t LLPanel::sPanelMap; +LLPanel::alert_queue_t LLPanel::sAlertQueue; + +void LLPanel::init() +{ + // mRectControl + mBgColorAlpha = LLUI::sColorsGroup->getColor( "DefaultBackgroundColor" ); + mBgColorOpaque = LLUI::sColorsGroup->getColor( "FocusBackgroundColor" ); + mDefaultBtnHighlight = LLUI::sColorsGroup->getColor( "DefaultHighlightLight" ); + mBgVisible = FALSE; + mBgOpaque = FALSE; + mBorder = NULL; + mDefaultBtn = NULL; + setIsChrome(FALSE); //is this a decorator to a live window or a form? + mLastTabGroup = 0; + + // add self to handle->panel map + sPanelMap[mViewHandle] = this; + setTabStop(FALSE); +} + +LLPanel::LLPanel() +: mRectControl() +{ + init(); +} + +LLPanel::LLPanel(const LLString& name) +: LLUICtrl(name, LLRect(0, 0, 0, 0), TRUE, NULL, NULL), + mRectControl() +{ + init(); +} + + +LLPanel::LLPanel(const LLString& name, const LLRect& rect, BOOL bordered) +: LLUICtrl(name, rect, TRUE, NULL, NULL), + mRectControl() +{ + init(); + if (bordered) + { + addBorder(); + } +} + + +LLPanel::LLPanel(const LLString& name, const LLString& rect_control, BOOL bordered) +: LLUICtrl(name, LLUI::sConfigGroup->getRect(rect_control), TRUE, NULL, NULL), + mRectControl( rect_control ) +{ + init(); + if (bordered) + { + addBorder(); + } +} + +void LLPanel::addBorder(LLViewBorder::EBevel border_bevel, + LLViewBorder::EStyle border_style, S32 border_thickness) +{ + mBorder = new LLViewBorder( "panel border", + LLRect(0, mRect.getHeight(), mRect.getWidth(), 0), + border_bevel, border_style, border_thickness ); + mBorder->setSaveToXML(false); + addChild( mBorder ); +} + + +LLPanel::~LLPanel() +{ + if( !mRectControl.empty() ) + { + LLUI::sConfigGroup->setRect( mRectControl, mRect ); + } + sPanelMap.erase(mViewHandle); +} + + +// virtual +EWidgetType LLPanel::getWidgetType() const +{ + return WIDGET_TYPE_PANEL; +} + +// virtual +LLString LLPanel::getWidgetTag() const +{ + return LL_PANEL_TAG; +} + +// virtual +BOOL LLPanel::isPanel() +{ + return TRUE; +} + +// virtual +BOOL LLPanel::postBuild() +{ + return TRUE; +} + +// virtual +void LLPanel::clearCtrls() +{ + LLView::ctrl_list_t ctrls = getCtrlList(); + for (LLView::ctrl_list_t::iterator ctrl_it = ctrls.begin(); ctrl_it != ctrls.end(); ++ctrl_it) + { + LLUICtrl* ctrl = *ctrl_it; + ctrl->setFocus( FALSE ); + ctrl->setEnabled( FALSE ); + ctrl->clear(); + } +} + +void LLPanel::setCtrlsEnabled( BOOL b ) +{ + LLView::ctrl_list_t ctrls = getCtrlList(); + for (LLView::ctrl_list_t::iterator ctrl_it = ctrls.begin(); ctrl_it != ctrls.end(); ++ctrl_it) + { + LLUICtrl* ctrl = *ctrl_it; + ctrl->setEnabled( b ); + } +} + +void LLPanel::draw() +{ + if( getVisible() ) + { + // draw background + if( mBgVisible ) + { + S32 left = LLPANEL_BORDER_WIDTH; + S32 top = mRect.getHeight() - LLPANEL_BORDER_WIDTH; + S32 right = mRect.getWidth() - LLPANEL_BORDER_WIDTH; + S32 bottom = LLPANEL_BORDER_WIDTH; + + if (mBgOpaque ) + { + gl_rect_2d( left, top, right, bottom, mBgColorOpaque ); + } + else + { + gl_rect_2d( left, top, right, bottom, mBgColorAlpha ); + } + } + + if( mDefaultBtn) + { + if (gFocusMgr.childHasKeyboardFocus( this ) && mDefaultBtn->getEnabled()) + { + LLUICtrl* focus_ctrl = gFocusMgr.getKeyboardFocus(); + BOOL focus_is_child_button = focus_ctrl->getWidgetType() == WIDGET_TYPE_BUTTON && static_cast(focus_ctrl)->getCommitOnReturn(); + // only enable default button when current focus is not a return-capturing button + mDefaultBtn->setBorderEnabled(!focus_is_child_button); + } + else + { + mDefaultBtn->setBorderEnabled(FALSE); + } + } + + LLView::draw(); + } +} + +void LLPanel::refresh() +{ + // do nothing by default + // but is automatically called in setFocus(TRUE) +} + +void LLPanel::setDefaultBtn(LLButton* btn) +{ + if (mDefaultBtn && mDefaultBtn->getEnabled()) + { + mDefaultBtn->setBorderEnabled(FALSE); + } + mDefaultBtn = btn; + if (mDefaultBtn) + { + mDefaultBtn->setBorderEnabled(TRUE); + } +} + +void LLPanel::setDefaultBtn(const LLString& id) +{ + LLButton *button = LLUICtrlFactory::getButtonByName(this, id); + if (button) + { + setDefaultBtn(button); + } + else + { + setDefaultBtn(NULL); + } +} + +BOOL LLPanel::handleKey(KEY key, MASK mask, BOOL called_from_parent) +{ + BOOL handled = FALSE; + if (getVisible() && getEnabled()) + { + if( (mask == MASK_SHIFT) && (KEY_TAB == key)) + { + //SHIFT-TAB + LLView* cur_focus = gFocusMgr.getKeyboardFocus(); + if (cur_focus && gFocusMgr.childHasKeyboardFocus(this)) + { + LLView* focus_root = cur_focus; + while(cur_focus->getParent()) + { + cur_focus = cur_focus->getParent(); + if (cur_focus->isFocusRoot()) + { + // this is the root-most focus root found so far + focus_root = cur_focus; + } + } + handled = focus_root->focusPrevItem(FALSE); + } + else if (!cur_focus && mIsFocusRoot) + { + handled = focusLastItem(); + if (!handled) + { + setFocus(TRUE); + handled = TRUE; + } + } + } + else + if( (mask == MASK_NONE ) && (KEY_TAB == key)) + { + //TAB + LLView* cur_focus = gFocusMgr.getKeyboardFocus(); + if (cur_focus && gFocusMgr.childHasKeyboardFocus(this)) + { + LLView* focus_root = cur_focus; + while(cur_focus->getParent()) + { + cur_focus = cur_focus->getParent(); + if (cur_focus->isFocusRoot()) + { + focus_root = cur_focus; + } + } + handled = focus_root->focusNextItem(FALSE); + } + else if (!cur_focus && mIsFocusRoot) + { + handled = focusFirstItem(); + if (!handled) + { + setFocus(TRUE); + handled = TRUE; + } + } + } + } + + if (!handled) + { + handled = LLView::handleKey(key, mask, called_from_parent); + } + + return handled; +} + +void LLPanel::addCtrl( LLUICtrl* ctrl, S32 tab_group) +{ + mLastTabGroup = tab_group; + + LLView::addCtrl(ctrl, tab_group); + // propagate chrome to children only if they have not been flagged as chrome + if (!ctrl->getIsChrome()) + { + ctrl->setIsChrome(getIsChrome()); + } +} + +void LLPanel::addCtrlAtEnd( LLUICtrl* ctrl, S32 tab_group) +{ + mLastTabGroup = tab_group; + + LLView::addCtrlAtEnd(ctrl, tab_group); + if (!ctrl->getIsChrome()) + { + ctrl->setIsChrome(getIsChrome()); + } +} + +BOOL LLPanel::handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ) +{ + BOOL handled = FALSE; + + if( getVisible() && getEnabled() && gFocusMgr.childHasKeyboardFocus(this) && KEY_ESCAPE == key ) + { + gFocusMgr.setKeyboardFocus(NULL, NULL); + return TRUE; + } + + if( getVisible() && getEnabled() && + gFocusMgr.childHasKeyboardFocus(this) && !called_from_parent ) + { + LLUICtrl* cur_focus = gFocusMgr.getKeyboardFocus(); + if (key == KEY_RETURN && mask == MASK_NONE) + { + // set keyboard focus to self to trigger commitOnFocusLost behavior on current ctrl + if (cur_focus && cur_focus->acceptsTextInput()) + { + cur_focus->onCommit(); + handled = TRUE; + } + } + + // If we have a default button, click it when + // return is pressed, unless current focus is a return-capturing button + // in which case *that* button will handle the return key + if (!(cur_focus->getWidgetType() == WIDGET_TYPE_BUTTON && static_cast(cur_focus)->getCommitOnReturn())) + { + // RETURN key means hit default button in this case + if (key == KEY_RETURN && mask == MASK_NONE + && mDefaultBtn != NULL + && mDefaultBtn->getVisible() + && mDefaultBtn->getEnabled()) + { + mDefaultBtn->onCommit(); + handled = TRUE; + } + } + } + + return handled; +} + +void LLPanel::requires(LLString name, EWidgetType type) +{ + mRequirements[name] = type; +} + +BOOL LLPanel::checkRequirements() +{ + BOOL retval = TRUE; + LLString message; + + for (requirements_map_t::iterator i = mRequirements.begin(); i != mRequirements.end(); ++i) + { + if (!this->getCtrlByNameAndType(i->first, i->second)) + { + retval = FALSE; + message += i->first + " " + LLUICtrlFactory::getWidgetType(i->second) + "\n"; + } + } + + if (!retval) + { + LLString::format_map_t args; + args["[COMPONENTS]"] = message; + args["[FLOATER]"] = getName(); + + llwarns << getName() << " failed requirements check on: \n" + << message << llendl; + + alertXml("FailedRequirementsCheck", args); + } + + return retval; +} + +//static +void LLPanel::alertXml(LLString label, LLString::format_map_t args) +{ + sAlertQueue.push(LLAlertInfo(label,args)); +} + +//static +BOOL LLPanel::nextAlert(LLAlertInfo &alert) +{ + if (!sAlertQueue.empty()) + { + alert = sAlertQueue.front(); + sAlertQueue.pop(); + return TRUE; + } + + return FALSE; +} + +void LLPanel::setFocus(BOOL b) +{ + if( b ) + { + if (!gFocusMgr.childHasKeyboardFocus(this)) + { + //refresh(); + if (!focusFirstItem()) + { + LLUICtrl::setFocus(TRUE); + } + onFocusReceived(); + } + } + else + { + if( this == gFocusMgr.getKeyboardFocus() ) + { + gFocusMgr.setKeyboardFocus( NULL, NULL ); + } + else + { + //RN: why is this here? + LLView::ctrl_list_t ctrls = getCtrlList(); + for (LLView::ctrl_list_t::iterator ctrl_it = ctrls.begin(); ctrl_it != ctrls.end(); ++ctrl_it) + { + LLUICtrl* ctrl = *ctrl_it; + ctrl->setFocus( FALSE ); + } + } + } +} + +void LLPanel::setBackgroundColor(const LLColor4& color) +{ + mBgColorOpaque = color; +} + +void LLPanel::setTransparentColor(const LLColor4& color) +{ + mBgColorAlpha = color; +} + +void LLPanel::setBorderVisible(BOOL b) +{ + if (mBorder) + { + mBorder->setVisible( b ); + } +} + +LLView* LLPanel::getCtrlByNameAndType(const LLString& name, EWidgetType type) +{ + LLView* view = getChildByName(name, TRUE); + if (view) + { + if (type == WIDGET_TYPE_DONTCARE || view->getWidgetType() == type) + { + return view; + } + else + { + llwarns << "Widget " << name << " has improper type in panel " << mName << "\n" + << "Is: \t\t" << view->getWidgetType() << "\n" + << "Should be: \t" << type + << llendl; + } + } + else + { + childNotFound(name); + } + return NULL; +} + +// static +LLPanel* LLPanel::getPanelByHandle(LLViewHandle handle) +{ + if (!sPanelMap.count(handle)) + { + return NULL; + } + + return sPanelMap[handle]; +} + +// virtual +LLXMLNodePtr LLPanel::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLView::getXML(); + + if (mBorder && mBorder->getVisible()) + { + node->createChild("border", TRUE)->setBoolValue(TRUE); + } + + if (!mRectControl.empty()) + { + node->createChild("rect_control", TRUE)->setStringValue(mRectControl); + } + + if (!mLabel.empty()) + { + node->createChild("label", TRUE)->setStringValue(mLabel); + } + + if (save_children) + { + LLView::child_list_const_reverse_iter_t rit; + for (rit = getChildList()->rbegin(); rit != getChildList()->rend(); ++rit) + { + LLView* childp = *rit; + + if (childp->getSaveToXML()) + { + LLXMLNodePtr xml_node = childp->getXML(); + + node->addChild(xml_node); + } + } + } + + return node; +} + +LLView* LLPanel::fromXML(LLXMLNodePtr node, LLView* parentp, LLUICtrlFactory *factory) +{ + LLString name("panel"); + node->getAttributeString("name", name); + + LLPanel* panelp = factory->createFactoryPanel(name); + // Fall back on a default panel, if there was no special factory. + if (!panelp) + { + panelp = new LLPanel("tab panel"); + } + + panelp->initPanelXML(node, parentp, factory); + + return panelp; +} + +void LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("panel"); + node->getAttributeString("name", name); + setName(name); + + setPanelParameters(node, parent); + + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + factory->createWidget(this, child); + } + + LLString xml_filename; + node->getAttributeString("filename", xml_filename); + if (!xml_filename.empty()) + { + factory->buildPanel(this, xml_filename, NULL); + } + + postBuild(); +} + +void LLPanel::setPanelParameters(LLXMLNodePtr node, LLView* parentp) +{ + /////// Rect, follows, tool_tip, enabled, visible attributes /////// + initFromXML(node, parentp); + + /////// Border attributes /////// + BOOL border = FALSE; + node->getAttributeBOOL("border", border); + if (border) + { + LLViewBorder::EBevel bevel_style = LLViewBorder::BEVEL_OUT; + LLViewBorder::getBevelFromAttribute(node, bevel_style); + + LLViewBorder::EStyle border_style = LLViewBorder::STYLE_LINE; + LLString border_string; + node->getAttributeString("border_style", border_string); + LLString::toLower(border_string); + + if (border_string == "texture") + { + border_style = LLViewBorder::STYLE_TEXTURE; + } + + S32 border_thickness = LLPANEL_BORDER_WIDTH; + node->getAttributeS32("border_thickness", border_thickness); + + addBorder(bevel_style, border_style, border_thickness); + } + + /////// Background attributes /////// + BOOL background_visible = FALSE; + node->getAttributeBOOL("background_visible", background_visible); + setBackgroundVisible(background_visible); + + BOOL background_opaque = FALSE; + node->getAttributeBOOL("background_opaque", background_opaque); + setBackgroundOpaque(background_opaque); + + LLColor4 color; + color = LLUI::sColorsGroup->getColor( "FocusBackgroundColor" ); + LLUICtrlFactory::getAttributeColor(node,"bg_opaque_color", color); + setBackgroundColor(color); + + color = LLUI::sColorsGroup->getColor( "DefaultBackgroundColor" ); + LLUICtrlFactory::getAttributeColor(node,"bg_alpha_color", color); + setTransparentColor(color); + + LLString label; + node->getAttributeString("label", label); + setLabel(label); +} + +void LLPanel::childSetVisible(const LLString& id, bool visible) +{ + LLView* child = getChildByName(id, true); + if (child) + { + child->setVisible(visible); + } +} + +bool LLPanel::childIsVisible(const LLString& id) const +{ + LLView* child = getChildByName(id, true); + if (child) + { + return (bool)child->getVisible(); + } + return false; +} + +void LLPanel::childSetEnabled(const LLString& id, bool enabled) +{ + LLView* child = getChildByName(id, true); + if (child) + { + child->setEnabled(enabled); + } +} + +void LLPanel::childSetTentative(const LLString& id, bool tentative) +{ + LLView* child = getChildByName(id, true); + if (child) + { + child->setTentative(tentative); + } +} + +bool LLPanel::childIsEnabled(const LLString& id) const +{ + LLView* child = getChildByName(id, true); + if (child) + { + return (bool)child->getEnabled(); + } + return false; +} + + +void LLPanel::childSetToolTip(const LLString& id, const LLString& msg) +{ + LLView* child = getChildByName(id, true); + if (child) + { + child->setToolTip(msg); + } +} + +void LLPanel::childSetRect(const LLString& id, const LLRect& rect) +{ + LLView* child = getChildByName(id, true); + if (child) + { + child->setRect(rect); + } +} + +bool LLPanel::childGetRect(const LLString& id, LLRect& rect) const +{ + LLView* child = getChildByName(id, true); + if (child) + { + rect = child->getRect(); + return true; + } + return false; +} + +void LLPanel::childSetFocus(const LLString& id, BOOL focus) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setFocus(focus); + } +} + +BOOL LLPanel::childHasFocus(const LLString& id) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + return child->hasFocus(); + } + else + { + childNotFound(id); + return FALSE; + } +} + + +void LLPanel::childSetFocusChangedCallback(const LLString& id, void (*cb)(LLUICtrl*, void*)) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setFocusChangedCallback(cb); + } +} + +void LLPanel::childSetCommitCallback(const LLString& id, void (*cb)(LLUICtrl*, void*), void *userdata ) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setCommitCallback(cb); + child->setCallbackUserData(userdata); + } +} + +void LLPanel::childSetDoubleClickCallback(const LLString& id, void (*cb)(void*), void *userdata ) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setDoubleClickCallback(cb); + if (userdata) + { + child->setCallbackUserData(userdata); + } + } +} + +void LLPanel::childSetValidate(const LLString& id, BOOL (*cb)(LLUICtrl*, void*)) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setValidateBeforeCommit(cb); + } +} + +void LLPanel::childSetUserData(const LLString& id, void* userdata) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setCallbackUserData(userdata); + } +} + +void LLPanel::childSetColor(const LLString& id, const LLColor4& color) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setColor(color); + } +} + +LLCtrlSelectionInterface* LLPanel::childGetSelectionInterface(const LLString& id) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + return child->getSelectionInterface(); + } + return NULL; +} + +LLCtrlListInterface* LLPanel::childGetListInterface(const LLString& id) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + return child->getListInterface(); + } + return NULL; +} + +LLCtrlScrollInterface* LLPanel::childGetScrollInterface(const LLString& id) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + return child->getScrollInterface(); + } + return NULL; +} + +void LLPanel::childSetValue(const LLString& id, LLSD value) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setValue(value); + } +} + +LLSD LLPanel::childGetValue(const LLString& id) const +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + return child->getValue(); + } + // Not found => return undefined + return LLSD(); +} + +BOOL LLPanel::childSetTextArg(const LLString& id, const LLString& key, const LLString& text) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + return child->setTextArg(key, text); + } + return FALSE; +} + +BOOL LLPanel::childSetLabelArg(const LLString& id, const LLString& key, const LLString& text) +{ + LLView* child = getChildByName(id, true); + if (child) + { + return child->setLabelArg(key, text); + } + return FALSE; +} + +void LLPanel::childSetMinValue(const LLString& id, LLSD min_value) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setMinValue(min_value); + } +} + +void LLPanel::childSetMaxValue(const LLString& id, LLSD max_value) +{ + LLUICtrl* child = (LLUICtrl*)getChildByName(id, true); + if (child) + { + child->setMaxValue(max_value); + } +} + +void LLPanel::childShowTab(const LLString& id, const LLString& tabname, bool visible) +{ + LLTabContainerCommon* child = LLUICtrlFactory::getTabContainerByName(this, id); + if (child) + { + child->selectTabByName(tabname); + } +} + +LLPanel *LLPanel::childGetVisibleTab(const LLString& id) +{ + LLTabContainerCommon* child = LLUICtrlFactory::getTabContainerByName(this, id); + if (child) + { + return child->getCurrentPanel(); + } + return NULL; +} + +void LLPanel::childSetTabChangeCallback(const LLString& id, const LLString& tabname, void (*on_tab_clicked)(void*, bool), void *userdata) +{ + LLTabContainerCommon* child = LLUICtrlFactory::getTabContainerByName(this, id); + if (child) + { + LLPanel *panel = child->getPanelByName(tabname); + if (panel) + { + child->setTabChangeCallback(panel, on_tab_clicked); + child->setTabUserData(panel, userdata); + } + } +} + +void LLPanel::childSetText(const LLString& id, const LLString& text) +{ + childSetValue(id, LLSD(text)); +} + +void LLPanel::childSetKeystrokeCallback(const LLString& id, void (*keystroke_callback)(LLLineEditor* caller, void* user_data), void *user_data) +{ + LLLineEditor* child = LLUICtrlFactory::getLineEditorByName(this, id); + if (child) + { + child->setKeystrokeCallback(keystroke_callback); + if (user_data) + { + child->setCallbackUserData(user_data); + } + } +} + +void LLPanel::childSetPrevalidate(const LLString& id, BOOL (*func)(const LLWString &) ) +{ + LLLineEditor* child = LLUICtrlFactory::getLineEditorByName(this, id); + if (child) + { + child->setPrevalidate(func); + } +} + +LLString LLPanel::childGetText(const LLString& id) +{ + return childGetValue(id).asString(); +} + +void LLPanel::childSetWrappedText(const LLString& id, const LLString& text, bool visible) +{ + LLTextBox* child = (LLTextBox*)getCtrlByNameAndType(id, WIDGET_TYPE_TEXT_BOX); + if (child) + { + child->setVisible(visible); + child->setWrappedText(text); + } +} + +void LLPanel::childSetAction(const LLString& id, void(*function)(void*), void* value) +{ + LLButton* button = (LLButton*)getCtrlByNameAndType(id, WIDGET_TYPE_BUTTON); + if (button) + { + button->setClickedCallback(function, value); + } +} + +void LLPanel::childSetActionTextbox(const LLString& id, void(*function)(void*)) +{ + LLTextBox* textbox = (LLTextBox*)getCtrlByNameAndType(id, WIDGET_TYPE_TEXT_BOX); + if (textbox) + { + textbox->setClickedCallback(function); + } +} + +void LLPanel::childSetControlName(const LLString& id, const LLString& control_name) +{ + LLView* view = getChildByName(id, TRUE); + if (view) + { + view->setControlName(control_name, NULL); + } +} + +//virtual +LLView* LLPanel::getChildByName(const LLString& name, BOOL recurse) const +{ + LLView* view = LLUICtrl::getChildByName(name, recurse); + if (!view) + { + childNotFound(name); + } + return view; +} + +void LLPanel::childNotFound(const LLString& id) const +{ + if (mExpectedMembers.find(id) == mExpectedMembers.end()) + { + mNewExpectedMembers.insert(id); + } +} + +void LLPanel::childDisplayNotFound() +{ + if (mNewExpectedMembers.empty()) + { + return; + } + LLString msg; + expected_members_list_t::iterator itor; + for (itor=mNewExpectedMembers.begin(); itor!=mNewExpectedMembers.end(); ++itor) + { + msg.append(*itor); + msg.append("\n"); + mExpectedMembers.insert(*itor); + } + mNewExpectedMembers.clear(); + LLString::format_map_t args; + args["[CONTROLS]"] = msg; + LLAlertDialog::showXml("FloaterNotFound", args); +} + diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h new file mode 100644 index 0000000000..9da27b6f38 --- /dev/null +++ b/indra/llui/llpanel.h @@ -0,0 +1,229 @@ +/** + * @file llpanel.h + * @author James Cook, Tom Yedwab + * @brief LLPanel base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLPANEL_H +#define LL_LLPANEL_H + +// Opaque view with a background and a border. Can contain LLUICtrls. + +#include "llcallbackmap.h" +#include "lluictrl.h" +#include "llviewborder.h" +#include "v4color.h" +#include + +const S32 LLPANEL_BORDER_WIDTH = 1; +const BOOL BORDER_YES = TRUE; +const BOOL BORDER_NO = FALSE; + +class LLViewerImage; +class LLUUID; +class LLCheckBoxCtrl; +class LLComboBox; +class LLIconCtrl; +class LLLineEditor; +class LLRadioGroup; +class LLScrollListCtrl; +class LLSliderCtrl; +class LLSpinCtrl; +class LLTextBox; +class LLTextEditor; + +class LLAlertInfo +{ +public: + LLString mLabel; + LLString::format_map_t mArgs; + + LLAlertInfo(LLString label, LLString::format_map_t args) + : mLabel(label), mArgs(args) { } + + LLAlertInfo() { } +}; + +class LLPanel : public LLUICtrl +{ +public: + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + // defaults to TRUE + virtual BOOL isPanel(); + + // minimal constructor for data-driven initialization + LLPanel(); + LLPanel(const LLString& name); + + // Position and size not saved + LLPanel(const LLString& name, const LLRect& rect, BOOL bordered = TRUE); + + // Position and size are saved to rect_control + LLPanel(const LLString& name, const LLString& rect_control, BOOL bordered = TRUE); + + void addBorder( LLViewBorder::EBevel border_bevel = LLViewBorder::BEVEL_OUT, + LLViewBorder::EStyle border_style = LLViewBorder::STYLE_LINE, + S32 border_thickness = LLPANEL_BORDER_WIDTH ); + + virtual ~LLPanel(); + virtual void draw(); + virtual void refresh(); // called in setFocus() + virtual void setFocus( BOOL b ); + void setFocusRoot(BOOL b) { mIsFocusRoot = b; } + virtual BOOL handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ); + virtual BOOL handleKey( KEY key, MASK mask, BOOL called_from_parent ); + virtual BOOL postBuild(); + + void requires(LLString name, EWidgetType type = WIDGET_TYPE_DONTCARE); + BOOL checkRequirements(); + + static void alertXml(LLString label, LLString::format_map_t args = LLString::format_map_t()); + static BOOL nextAlert(LLAlertInfo &alert); + + void setBackgroundColor( const LLColor4& color ); + void setTransparentColor(const LLColor4& color); + void setBackgroundVisible( BOOL b ) { mBgVisible = b; } + void setBackgroundOpaque(BOOL b) { mBgOpaque = b; } + void setDefaultBtn(LLButton* btn = NULL); + void setDefaultBtn(const LLString& id); + void setLabel(LLString label) { mLabel = label; } + LLString getLabel() const { return mLabel; } + + void setRectControl(const LLString& rect_control) { mRectControl.assign(rect_control); } + + void setBorderVisible( BOOL b ); + + void setCtrlsEnabled(BOOL b); + virtual void clearCtrls(); + + LLViewHandle getHandle() { return mViewHandle; } + + S32 getLastTabGroup() { return mLastTabGroup; } + + LLView* getCtrlByNameAndType(const LLString& name, EWidgetType type); + + static LLPanel* getPanelByHandle(LLViewHandle handle); + + virtual const LLCallbackMap::map_t& getFactoryMap() const { return mFactoryMap; } + + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + void initPanelXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + void setPanelParameters(LLXMLNodePtr node, LLView *parentp); + + // ** Wrappers for setting child properties by name ** -TomY + + // Override to set not found list + virtual LLView* getChildByName(const LLString& name, BOOL recurse = FALSE) const; + + // LLView + void childSetVisible(const LLString& name, bool visible); + void childShow(const LLString& name) { childSetVisible(name, true); } + void childHide(const LLString& name) { childSetVisible(name, false); } + bool childIsVisible(const LLString& id) const; + void childSetTentative(const LLString& name, bool tentative); + + void childSetEnabled(const LLString& name, bool enabled); + void childEnable(const LLString& name) { childSetEnabled(name, true); } + void childDisable(const LLString& name) { childSetEnabled(name, false); }; + bool childIsEnabled(const LLString& id) const; + + void childSetToolTip(const LLString& id, const LLString& msg); + void childSetRect(const LLString& id, const LLRect &rect); + bool childGetRect(const LLString& id, LLRect& rect) const; + + // LLUICtrl + void childSetFocus(const LLString& id, BOOL focus = TRUE); + BOOL childHasFocus(const LLString& id); + void childSetFocusChangedCallback(const LLString& id, void (*cb)(LLUICtrl*, void*)); + + void childSetCommitCallback(const LLString& id, void (*cb)(LLUICtrl*, void*), void* userdata = NULL ); + void childSetDoubleClickCallback(const LLString& id, void (*cb)(void*), void* userdata = NULL ); + void childSetValidate(const LLString& id, BOOL (*cb)(LLUICtrl*, void*) ); + void childSetUserData(const LLString& id, void* userdata); + + void childSetColor(const LLString& id, const LLColor4& color); + + LLCtrlSelectionInterface* childGetSelectionInterface(const LLString& id); + LLCtrlListInterface* childGetListInterface(const LLString& id); + LLCtrlScrollInterface* childGetScrollInterface(const LLString& id); + + // This is the magic bullet for data-driven UI + void childSetValue(const LLString& id, LLSD value); + LLSD childGetValue(const LLString& id) const; + + // For setting text / label replacement params, e.g. "Hello [NAME]" + // Not implemented for all types, defaults to noop, returns FALSE if not applicaple + BOOL childSetTextArg(const LLString& id, const LLString& key, const LLString& text); + BOOL childSetLabelArg(const LLString& id, const LLString& key, const LLString& text); + + // LLSlider / LLSpinCtrl + void childSetMinValue(const LLString& id, LLSD min_value); + void childSetMaxValue(const LLString& id, LLSD max_value); + + // LLTabContainer + void childShowTab(const LLString& id, const LLString& tabname, bool visible = true); + LLPanel *childGetVisibleTab(const LLString& id); + void childSetTabChangeCallback(const LLString& id, const LLString& tabname, void (*on_tab_clicked)(void*, bool), void *userdata); + + // LLTextBox + void childSetWrappedText(const LLString& id, const LLString& text, bool visible = true); + + // LLTextBox/LLTextEditor/LLLineEditor + void childSetText(const LLString& id, const LLString& text); + LLString childGetText(const LLString& id); + + // LLLineEditor + void childSetKeystrokeCallback(const LLString& id, void (*keystroke_callback)(LLLineEditor* caller, void* user_data), void *user_data); + void childSetPrevalidate(const LLString& id, BOOL (*func)(const LLWString &) ); + + // LLButton + void childSetAction(const LLString& id, void(*function)(void*), void* value); + void childSetActionTextbox(const LLString& id, void(*function)(void*)); + void childSetControlName(const LLString& id, const LLString& control_name); + + // Error reporting + void childNotFound(const LLString& id) const; + void childDisplayNotFound(); + + typedef std::queue alert_queue_t; + static alert_queue_t sAlertQueue; + +private: + // common constructor + void init(); + +protected: + virtual void addCtrl( LLUICtrl* ctrl, S32 tab_group ); + virtual void addCtrlAtEnd( LLUICtrl* ctrl, S32 tab_group); + + // Unified error reporting for the child* functions + typedef std::set expected_members_list_t; + mutable expected_members_list_t mExpectedMembers; + mutable expected_members_list_t mNewExpectedMembers; + + LLString mRectControl; + LLColor4 mBgColorAlpha; + LLColor4 mBgColorOpaque; + LLColor4 mDefaultBtnHighlight; + BOOL mBgVisible; + BOOL mBgOpaque; + LLViewBorder* mBorder; + LLButton* mDefaultBtn; + LLCallbackMap::map_t mFactoryMap; + LLString mLabel; + S32 mLastTabGroup; + + typedef std::map requirements_map_t; + requirements_map_t mRequirements; + + typedef std::map panel_map_t; + static panel_map_t sPanelMap; +}; + +#endif diff --git a/indra/llui/llradiogroup.cpp b/indra/llui/llradiogroup.cpp new file mode 100644 index 0000000000..69c0da6933 --- /dev/null +++ b/indra/llui/llradiogroup.cpp @@ -0,0 +1,440 @@ +/** + * @file llradiogroup.cpp + * @brief LLRadioGroup base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// An invisible view containing multiple mutually exclusive toggling +// buttons (usually radio buttons). Automatically handles the mutex +// condition by highlighting only one button at a time. + +#include "linden_common.h" + +#include "llboost.h" + +#include "llradiogroup.h" +#include "indra_constants.h" + +#include "llviewborder.h" +#include "llcontrol.h" +#include "llui.h" +#include "llfocusmgr.h" + +LLRadioGroup::LLRadioGroup(const LLString& name, const LLRect& rect, + const LLString& control_name, + LLUICtrlCallback callback, + void* userdata, + BOOL border) +: LLUICtrl(name, rect, TRUE, callback, userdata, FOLLOWS_LEFT | FOLLOWS_TOP), + mSelectedIndex(0) +{ + setControlName(control_name, NULL); + init(border); +} + +LLRadioGroup::LLRadioGroup(const LLString& name, const LLRect& rect, + S32 initial_index, + LLUICtrlCallback callback, + void* userdata, + BOOL border) : + LLUICtrl(name, rect, TRUE, callback, userdata, FOLLOWS_LEFT | FOLLOWS_TOP), + mSelectedIndex(initial_index) +{ + init(border); +} + +void LLRadioGroup::init(BOOL border) +{ + if (border) + { + addChild( new LLViewBorder( "radio group border", + LLRect(0, mRect.getHeight(), mRect.getWidth(), 0), + LLViewBorder::BEVEL_NONE, + LLViewBorder::STYLE_LINE, + 1 ) ); + } + mHasBorder = border; +} + + + + +LLRadioGroup::~LLRadioGroup() +{ +} + + +// virtual +void LLRadioGroup::setEnabled(BOOL enabled) +{ + for (child_list_const_iter_t child_iter = getChildList()->begin(); + child_iter != getChildList()->end(); ++child_iter) + { + LLView *child = *child_iter; + child->setEnabled(enabled); + } + LLView::setEnabled(enabled); +} + +void LLRadioGroup::setIndexEnabled(S32 index, BOOL enabled) +{ + S32 count = 0; + for (button_list_t::iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + LLRadioCtrl* child = *iter; + if (count == index) + { + child->setEnabled(enabled); + if (index == mSelectedIndex && enabled == FALSE) + { + setSelectedIndex(-1); + } + break; + } + count++; + } + count = 0; + if (mSelectedIndex < 0) + { + // Set to highest enabled value < index, + // or lowest value above index if none lower are enabled + // or 0 if none are enabled + for (button_list_t::iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + LLRadioCtrl* child = *iter; + if (count >= index && mSelectedIndex >= 0) + { + break; + } + if (child->getEnabled()) + { + setSelectedIndex(count); + } + count++; + } + if (mSelectedIndex < 0) + { + setSelectedIndex(0); + } + } +} + +S32 LLRadioGroup::getSelectedIndex() const +{ + return mSelectedIndex; +} + +BOOL LLRadioGroup::setSelectedIndex(S32 index, BOOL from_event) +{ + if (index < 0 || index >= (S32)mRadioButtons.size()) + { + return FALSE; + } + + mSelectedIndex = index; + + if (!from_event) + { + setControlValue(getSelectedIndex()); + } + + return TRUE; +} + +BOOL LLRadioGroup::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + BOOL handled = FALSE; + // do any of the tab buttons have keyboard focus? + if (getEnabled() && !called_from_parent) + { + switch(key) + { + case KEY_DOWN: + if (!setSelectedIndex((getSelectedIndex() + 1))) + { + make_ui_sound("UISndInvalidOp"); + } + else + { + onCommit(); + } + handled = TRUE; + break; + case KEY_UP: + if (!setSelectedIndex((getSelectedIndex() - 1))) + { + make_ui_sound("UISndInvalidOp"); + } + else + { + onCommit(); + } + handled = TRUE; + break; + case KEY_LEFT: + if (!setSelectedIndex((getSelectedIndex() - 1))) + { + make_ui_sound("UISndInvalidOp"); + } + else + { + onCommit(); + } + handled = TRUE; + break; + case KEY_RIGHT: + if (!setSelectedIndex((getSelectedIndex() + 1))) + { + make_ui_sound("UISndInvalidOp"); + } + else + { + onCommit(); + } + handled = TRUE; + break; + default: + break; + } + } + return handled; +} + +void LLRadioGroup::draw() +{ + S32 current_button = 0; + + BOOL take_focus = FALSE; + if (gFocusMgr.childHasKeyboardFocus(this)) + { + take_focus = TRUE; + } + + for (button_list_t::iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + LLRadioCtrl* radio = *iter; + BOOL selected = (current_button == mSelectedIndex); + radio->setValue( selected ); + if (take_focus && selected && !gFocusMgr.childHasKeyboardFocus(radio)) + { + radio->focusFirstItem(); + } + current_button++; + } + + LLView::draw(); +} + + +// When adding a button, we need to ensure that the radio +// group gets a message when the button is clicked. +LLRadioCtrl* LLRadioGroup::addRadioButton(const LLString& name, const LLString& label, const LLRect& rect, const LLFontGL* font ) +{ + // Highlight will get fixed in draw method above + LLRadioCtrl* radio = new LLRadioCtrl(name, rect, label, font, + onClickButton, this); + addChild(radio); + mRadioButtons.push_back(radio); + return radio; +} + +// Handle one button being clicked. All child buttons must have this +// function as their callback function. + +// static +void LLRadioGroup::onClickButton(LLUICtrl* ui_ctrl, void* userdata) +{ + // llinfos << "LLRadioGroup::onClickButton" << llendl; + + LLRadioCtrl* clickedRadio = (LLRadioCtrl*) ui_ctrl; + LLRadioGroup* self = (LLRadioGroup*) userdata; + + S32 counter = 0; + for (button_list_t::iterator iter = self->mRadioButtons.begin(); + iter != self->mRadioButtons.end(); ++iter) + { + LLRadioCtrl* radio = *iter; + if (radio == clickedRadio) + { + // llinfos << "clicked button " << counter << llendl; + self->setSelectedIndex(counter); + self->setControlValue(counter); + + // BUG: Calls click callback even if button didn't actually change + self->onCommit(); + + return; + } + + counter++; + } + + llwarns << "LLRadioGroup::onClickButton - clicked button that isn't a child" << llendl; +} + +void LLRadioGroup::setValue( const LLSD& value ) +{ + LLString value_name = value.asString(); + int idx = 0; + for (button_list_t::const_iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + LLRadioCtrl* radio = *iter; + if (radio->getName() == value_name) + { + setSelectedIndex(idx); + idx = -1; + break; + } + ++idx; + } + if (idx != -1) + { + // string not found, try integer + if (value.isInteger()) + { + setSelectedIndex((S32) value.asInteger(), TRUE); + } + else + { + llwarns << "LLRadioGroup::setValue: value not found: " << value_name << llendl; + } + } +} + +LLSD LLRadioGroup::getValue() const +{ + int index = getSelectedIndex(); + int idx = 0; + for (button_list_t::const_iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + if (idx == index) return LLSD((*iter)->getName()); + ++idx; + } + return LLSD(); +} + +// virtual +LLXMLNodePtr LLRadioGroup::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + // Attributes + + node->createChild("draw_border", TRUE)->setBoolValue(mHasBorder); + + // Contents + + for (button_list_t::const_iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + LLRadioCtrl* radio = *iter; + + LLXMLNodePtr child_node = radio->LLView::getXML(); + child_node->setStringValue(radio->getLabel()); + child_node->setName("radio_item"); + + node->addChild(child_node); + } + + return node; +} + +// static +LLView* LLRadioGroup::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("radio_group"); + node->getAttributeString("name", name); + + U32 initial_value = 0; + node->getAttributeU32("initial_value", initial_value); + + BOOL draw_border = TRUE; + node->getAttributeBOOL("draw_border", draw_border); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + LLRadioGroup* radio_group = new LLRadioGroup(name, + rect, + initial_value, + NULL, + NULL, + draw_border); + + const LLString& contents = node->getValue(); + + LLRect group_rect = radio_group->getRect(); + + LLFontGL *font = LLView::selectFont(node); + + if (contents.find_first_not_of(" \n\t") != contents.npos) + { + // ...old school default vertical layout + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("\t\n"); + tokenizer tokens(contents, sep); + tokenizer::iterator token_iter = tokens.begin(); + + const S32 HPAD = 4, VPAD = 4; + S32 cur_y = group_rect.getHeight() - VPAD; + + while(token_iter != tokens.end()) + { + const char* line = token_iter->c_str(); + LLRect rect(HPAD, cur_y, group_rect.getWidth() - (2 * HPAD), cur_y - 15); + cur_y -= VPAD + 15; + radio_group->addRadioButton("radio", line, rect, font); + ++token_iter; + } + llwarns << "Legacy radio group format used! Please convert to use tags!" << llendl; + } + else + { + // ...per pixel layout + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + if (child->hasName("radio_item")) + { + LLRect item_rect; + createRect(child, item_rect, radio_group, rect); + + LLString radioname("radio"); + child->getAttributeString("name", radioname); + LLString item_label = child->getTextContents(); + LLRadioCtrl* radio = radio_group->addRadioButton(radioname, item_label.c_str(), item_rect, font); + + radio->initFromXML(child, radio_group); + } + } + } + + radio_group->initFromXML(node, parent); + + return radio_group; +} + + +LLRadioCtrl::LLRadioCtrl(const LLString& name, const LLRect& rect, const LLString& label, + const LLFontGL* font, void (*commit_callback)(LLUICtrl*, void*), void* callback_userdata) : + LLCheckBoxCtrl(name, rect, label, font, commit_callback, callback_userdata, FALSE, RADIO_STYLE) +{ + setTabStop(FALSE); +} + +LLRadioCtrl::~LLRadioCtrl() +{ +} + +void LLRadioCtrl::setValue(const LLSD& value) +{ + LLCheckBoxCtrl::setValue(value); + mButton->setTabStop(value.asBoolean()); +} diff --git a/indra/llui/llradiogroup.h b/indra/llui/llradiogroup.h new file mode 100644 index 0000000000..01b4a61b82 --- /dev/null +++ b/indra/llui/llradiogroup.h @@ -0,0 +1,102 @@ +/** + * @file llradiogroup.h + * @brief LLRadioGroup base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// An invisible view containing multiple mutually exclusive toggling +// buttons (usually radio buttons). Automatically handles the mutex +// condition by highlighting only one button at a time. + +#ifndef LL_LLRADIOGROUP_H +#define LL_LLRADIOGROUP_H + +#include "lluictrl.h" +#include "llcheckboxctrl.h" + +class LLFontGL; + +// Radio controls are checkbox controls with use_radio_style true +class LLRadioCtrl : public LLCheckBoxCtrl +{ +public: + LLRadioCtrl(const LLString& name, const LLRect& rect, const LLString& label, + const LLFontGL* font = NULL, + void (*commit_callback)(LLUICtrl*, void*) = NULL, + void* callback_userdata = NULL); + /*virtual*/ ~LLRadioCtrl(); + + /*virtual*/ void setValue(const LLSD& value); +}; + +class LLRadioGroup +: public LLUICtrl +{ +public: + // Build a radio group. The number (0...n-1) of the currently selected + // element will be stored in the named control. After the control is + // changed the callback will be called. + LLRadioGroup(const LLString& name, const LLRect& rect, + const LLString& control_name, + LLUICtrlCallback callback = NULL, + void* userdata = NULL, + BOOL border = TRUE); + + // Another radio group constructor, but this one doesn't rely on + // needing a control + LLRadioGroup(const LLString& name, const LLRect& rect, + S32 initial_index, + LLUICtrlCallback callback = NULL, + void* userdata = NULL, + BOOL border = TRUE); + + virtual ~LLRadioGroup(); + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_RADIO_GROUP; } + virtual LLString getWidgetTag() const { return LL_RADIO_GROUP_TAG; } + + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + + virtual void setEnabled(BOOL enabled); + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + void setIndexEnabled(S32 index, BOOL enabled); + + S32 getItemCount() { return mRadioButtons.size(); } + // return the index value of the selected item + S32 getSelectedIndex() const; + + // set the index value programatically + BOOL setSelectedIndex(S32 index, BOOL from_event = FALSE); + + // Accept and retrieve strings of the radio group control names + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + + // Draw the group, but also fix the highlighting based on the + // control. + void draw(); + + // You must use this method to add buttons to a radio group. + // Don't use addChild -- it won't set the callback function + // correctly. + LLRadioCtrl* addRadioButton(const LLString& name, const LLString& label, const LLRect& rect, const LLFontGL* font); + LLRadioCtrl* getRadioButton(const S32& index) { return mRadioButtons[index]; } + // Update the control as needed. Userdata must be a pointer to the + // button. + static void onClickButton(LLUICtrl* radio, void* userdata); + +protected: + // protected function shared by the two constructors. + void init(BOOL border); + + S32 mSelectedIndex; + typedef std::vector button_list_t; + button_list_t mRadioButtons; + + BOOL mHasBorder; +}; + + +#endif diff --git a/indra/llui/llresizebar.cpp b/indra/llui/llresizebar.cpp new file mode 100644 index 0000000000..0183c58c93 --- /dev/null +++ b/indra/llui/llresizebar.cpp @@ -0,0 +1,257 @@ +/** + * @file llresizebar.cpp + * @brief LLResizeBar base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llresizebar.h" + +//#include "llviewermenu.h" +//#include "llviewerimagelist.h" +#include "llmath.h" +#include "llui.h" +#include "llmenugl.h" +#include "llfocusmgr.h" +#include "llwindow.h" + +LLResizeBar::LLResizeBar( const LLString& name, const LLRect& rect, S32 min_width, S32 min_height, Side side ) + : + LLView( name, rect, TRUE ), + mDragStartScreenX( 0 ), + mDragStartScreenY( 0 ), + mLastMouseScreenX( 0 ), + mLastMouseScreenY( 0 ), + mMinWidth( min_width ), + mMinHeight( min_height ), + mSide( side ) +{ + // set up some generically good follow code. + switch( side ) + { + case LEFT: + setFollowsLeft(); + setFollowsTop(); + setFollowsBottom(); + break; + case TOP: + setFollowsTop(); + setFollowsLeft(); + setFollowsRight(); + break; + case RIGHT: + setFollowsRight(); + setFollowsTop(); + setFollowsBottom(); + break; + case BOTTOM: + setFollowsBottom(); + setFollowsLeft(); + setFollowsRight(); + break; + default: + break; + } +} + + +BOOL LLResizeBar::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if( mEnabled ) + { + // Route future Mouse messages here preemptively. (Release on mouse up.) + // No handler needed for focus lost since this clas has no state that depends on it. + gFocusMgr.setMouseCapture( this, NULL ); + + //localPointToScreen(x, y, &mDragStartScreenX, &mDragStartScreenX); + localPointToOtherView(x, y, &mDragStartScreenX, &mDragStartScreenY, getParent()->getParent()); + mLastMouseScreenX = mDragStartScreenX; + mLastMouseScreenY = mDragStartScreenY; + } + + return TRUE; +} + + +BOOL LLResizeBar::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + if( gFocusMgr.getMouseCapture() == this ) + { + // Release the mouse + gFocusMgr.setMouseCapture( NULL, NULL ); + handled = TRUE; + } + else + { + handled = TRUE; + } + return handled; +} + +EWidgetType LLResizeBar::getWidgetType() const +{ + return WIDGET_TYPE_RESIZE_BAR; +} + +LLString LLResizeBar::getWidgetTag() const +{ + return LL_RESIZE_BAR_TAG; +} + +BOOL LLResizeBar::handleHover(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + // We only handle the click if the click both started and ended within us + if( gFocusMgr.getMouseCapture() == this ) + { + //FIXME: this, of course, is fragile + LLView* floater_view = getParent()->getParent(); + S32 floater_view_x; + S32 floater_view_y; + localPointToOtherView(x, y, &floater_view_x, &floater_view_y, floater_view); + + S32 delta_x = floater_view_x - mDragStartScreenX; + S32 delta_y = floater_view_y - mDragStartScreenY; + + LLCoordGL mouse_dir; + // use hysteresis on mouse motion to preserve user intent when mouse stops moving + mouse_dir.mX = (floater_view_x == mLastMouseScreenX) ? mLastMouseDir.mX : floater_view_x - mLastMouseScreenX; + mouse_dir.mY = (floater_view_y == mLastMouseScreenY) ? mLastMouseDir.mY : floater_view_y - mLastMouseScreenY; + mLastMouseDir = mouse_dir; + mLastMouseScreenX = floater_view_x; + mLastMouseScreenY = floater_view_y; + + // Make sure the mouse in still over the application. We don't want to make the parent + // so big that we can't see the resize handle any more. + LLRect valid_rect = floater_view->getRect(); + LLView* parentView = getParent(); + if( valid_rect.localPointInRect( floater_view_x, floater_view_y ) && parentView ) + { + // Resize the parent + LLRect parent_rect = parentView->getRect(); + LLRect scaled_rect = parent_rect; + + S32 new_width = parent_rect.getWidth(); + S32 new_height = parent_rect.getHeight(); + + switch( mSide ) + { + case LEFT: + new_width = parent_rect.getWidth() - delta_x; + if( new_width < mMinWidth ) + { + new_width = mMinWidth; + delta_x = parent_rect.getWidth() - mMinWidth; + } + scaled_rect.translate(delta_x, 0); + break; + + case TOP: + new_height = parent_rect.getHeight() + delta_y; + if( new_height < mMinHeight ) + { + new_height = mMinHeight; + delta_y = mMinHeight - parent_rect.getHeight(); + } + break; + + case RIGHT: + new_width = parent_rect.getWidth() + delta_x; + if( new_width < mMinWidth ) + { + new_width = mMinWidth; + delta_x = mMinWidth - parent_rect.getWidth(); + } + break; + + case BOTTOM: + new_height = parent_rect.getHeight() - delta_y; + if( new_height < mMinHeight ) + { + new_height = mMinHeight; + delta_y = parent_rect.getHeight() - mMinHeight; + } + scaled_rect.translate(0, delta_y); + break; + } + + scaled_rect.mTop = scaled_rect.mBottom + new_height; + scaled_rect.mRight = scaled_rect.mLeft + new_width; + parentView->setRect(scaled_rect); + + S32 snap_delta_x = 0; + S32 snap_delta_y = 0; + + LLView* snap_view = NULL; + + switch( mSide ) + { + case LEFT: + snap_view = parentView->findSnapEdge(snap_delta_x, mouse_dir, SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_x -= scaled_rect.mLeft; + scaled_rect.mLeft += snap_delta_x; + break; + case TOP: + snap_view = parentView->findSnapEdge(snap_delta_y, mouse_dir, SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_y -= scaled_rect.mTop; + scaled_rect.mTop += snap_delta_y; + break; + case RIGHT: + snap_view = parentView->findSnapEdge(snap_delta_x, mouse_dir, SNAP_RIGHT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_x -= scaled_rect.mRight; + scaled_rect.mRight += snap_delta_x; + break; + case BOTTOM: + snap_view = parentView->findSnapEdge(snap_delta_y, mouse_dir, SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_y -= scaled_rect.mBottom; + scaled_rect.mBottom += snap_delta_y; + break; + } + + parentView->snappedTo(snap_view); + + parentView->setRect(parent_rect); + + parentView->reshape(scaled_rect.getWidth(), scaled_rect.getHeight(), FALSE); + parentView->translate(scaled_rect.mLeft - parentView->getRect().mLeft, scaled_rect.mBottom - parentView->getRect().mBottom); + + floater_view_x = mDragStartScreenX + delta_x; + floater_view_y = mDragStartScreenY + delta_y; + mDragStartScreenX = floater_view_x + snap_delta_x; + mDragStartScreenY = floater_view_y + snap_delta_y; + } + + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; + handled = TRUE; + } + else + { + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl; + handled = TRUE; + } + + if( handled ) + { + switch( mSide ) + { + case LEFT: + case RIGHT: + getWindow()->setCursor(UI_CURSOR_SIZEWE); + break; + + case TOP: + case BOTTOM: + getWindow()->setCursor(UI_CURSOR_SIZENS); + break; + } + } + + return handled; +} + diff --git a/indra/llui/llresizebar.h b/indra/llui/llresizebar.h new file mode 100644 index 0000000000..c2a07fd3e3 --- /dev/null +++ b/indra/llui/llresizebar.h @@ -0,0 +1,47 @@ +/** + * @file llresizebar.h + * @brief LLResizeBar base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_RESIZEBAR_H +#define LL_RESIZEBAR_H + +#include "llview.h" +#include "llcoord.h" + +class LLResizeBar : public LLView +{ +public: + enum Side { LEFT, TOP, RIGHT, BOTTOM }; + + LLResizeBar(const LLString& name, const LLRect& rect, S32 min_width, S32 min_height, Side side ); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + +// virtual void draw(); No appearance + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + + void setResizeLimits( S32 min_width, S32 min_height ) { mMinWidth = min_width; mMinHeight = min_height; } + +protected: + S32 mDragStartScreenX; + S32 mDragStartScreenY; + S32 mLastMouseScreenX; + S32 mLastMouseScreenY; + LLCoordGL mLastMouseDir; + S32 mMinWidth; + S32 mMinHeight; + Side mSide; +}; + +const S32 RESIZE_BAR_HEIGHT = 3; + +#endif // LL_RESIZEBAR_H + + diff --git a/indra/llui/llresizehandle.cpp b/indra/llui/llresizehandle.cpp new file mode 100644 index 0000000000..77101fa296 --- /dev/null +++ b/indra/llui/llresizehandle.cpp @@ -0,0 +1,321 @@ +/** + * @file llresizehandle.cpp + * @brief LLResizeHandle base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llresizehandle.h" + +#include "llfocusmgr.h" +#include "llmath.h" +#include "llui.h" +#include "llmenugl.h" +#include "llcontrol.h" +#include "llfloater.h" +#include "llwindow.h" + +const S32 RESIZE_BORDER_WIDTH = 3; + +LLResizeHandle::LLResizeHandle( const LLString& name, const LLRect& rect, S32 min_width, S32 min_height, ECorner corner ) + : + LLView( name, rect, TRUE ), + mDragStartScreenX( 0 ), + mDragStartScreenY( 0 ), + mLastMouseScreenX( 0 ), + mLastMouseScreenY( 0 ), + mImage( NULL ), + mMinWidth( min_width ), + mMinHeight( min_height ), + mCorner( corner ) +{ + setSaveToXML(false); + + if( RIGHT_BOTTOM == mCorner) + { + LLUUID image_id(LLUI::sConfigGroup->getString("UIImgResizeBottomRightUUID")); + mImage = LLUI::sImageProvider->getUIImageByID(image_id); + } + + switch( mCorner ) + { + case LEFT_TOP: setFollows( FOLLOWS_LEFT | FOLLOWS_TOP ); break; + case LEFT_BOTTOM: setFollows( FOLLOWS_LEFT | FOLLOWS_BOTTOM ); break; + case RIGHT_TOP: setFollows( FOLLOWS_RIGHT | FOLLOWS_TOP ); break; + case RIGHT_BOTTOM: setFollows( FOLLOWS_RIGHT | FOLLOWS_BOTTOM ); break; + } +} + +EWidgetType LLResizeHandle::getWidgetType() const +{ + return WIDGET_TYPE_RESIZE_HANDLE; +} + +LLString LLResizeHandle::getWidgetTag() const +{ + return LL_RESIZE_HANDLE_TAG; +} + +BOOL LLResizeHandle::handleMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + if( getVisible() && pointInHandle(x, y) ) + { + handled = TRUE; + if( mEnabled ) + { + // Route future Mouse messages here preemptively. (Release on mouse up.) + // No handler needed for focus lost since this clas has no state that depends on it. + gFocusMgr.setMouseCapture( this, NULL ); + + localPointToScreen(x, y, &mDragStartScreenX, &mDragStartScreenY); + mLastMouseScreenX = mDragStartScreenX; + mLastMouseScreenY = mDragStartScreenY; + } + } + + return handled; +} + + +BOOL LLResizeHandle::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + if( gFocusMgr.getMouseCapture() == this ) + { + // Release the mouse + gFocusMgr.setMouseCapture( NULL, NULL ); + handled = TRUE; + } + else + if( getVisible() && pointInHandle(x, y) ) + { + handled = TRUE; + } + + return handled; +} + + +BOOL LLResizeHandle::handleHover(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + // We only handle the click if the click both started and ended within us + if( gFocusMgr.getMouseCapture() == this ) + { + // Make sure the mouse in still over the application. We don't want to make the parent + // so big that we can't see the resize handle any more. + + S32 screen_x; + S32 screen_y; + localPointToScreen(x, y, &screen_x, &screen_y); + const LLRect& valid_rect = gFloaterView->getRect(); // Assumes that the parent is a floater. + screen_x = llclamp( screen_x, valid_rect.mLeft, valid_rect.mRight ); + screen_y = llclamp( screen_y, valid_rect.mBottom, valid_rect.mTop ); + + LLView* parentView = getParent(); + if( parentView ) + { + // Resize the parent + LLRect parent_rect = parentView->getRect(); + LLRect scaled_rect = parent_rect; + S32 delta_x = screen_x - mDragStartScreenX; + S32 delta_y = screen_y - mDragStartScreenY; + LLCoordGL mouse_dir; + // use hysteresis on mouse motion to preserve user intent when mouse stops moving + mouse_dir.mX = (screen_x == mLastMouseScreenX) ? mLastMouseDir.mX : screen_x - mLastMouseScreenX; + mouse_dir.mY = (screen_y == mLastMouseScreenY) ? mLastMouseDir.mY : screen_y - mLastMouseScreenY; + mLastMouseScreenX = screen_x; + mLastMouseScreenY = screen_y; + mLastMouseDir = mouse_dir; + + S32 x_multiple = 1; + S32 y_multiple = 1; + switch( mCorner ) + { + case LEFT_TOP: + x_multiple = -1; + y_multiple = 1; + break; + case LEFT_BOTTOM: + x_multiple = -1; + y_multiple = -1; + break; + case RIGHT_TOP: + x_multiple = 1; + y_multiple = 1; + break; + case RIGHT_BOTTOM: + x_multiple = 1; + y_multiple = -1; + break; + } + + S32 new_width = parent_rect.getWidth() + x_multiple * delta_x; + if( new_width < mMinWidth ) + { + new_width = mMinWidth; + delta_x = x_multiple * (mMinWidth - parent_rect.getWidth()); + } + + S32 new_height = parent_rect.getHeight() + y_multiple * delta_y; + if( new_height < mMinHeight ) + { + new_height = mMinHeight; + delta_y = y_multiple * (mMinHeight - parent_rect.getHeight()); + } + + switch( mCorner ) + { + case LEFT_TOP: + scaled_rect.translate(delta_x, 0); + break; + case LEFT_BOTTOM: + scaled_rect.translate(delta_x, delta_y); + break; + case RIGHT_TOP: + break; + case RIGHT_BOTTOM: + scaled_rect.translate(0, delta_y); + break; + } + + // temporarily set new parent rect + scaled_rect.mRight = scaled_rect.mLeft + new_width; + scaled_rect.mTop = scaled_rect.mBottom + new_height; + parentView->setRect(scaled_rect); + + S32 snap_delta_x = 0; + S32 snap_delta_y = 0; + + LLView* snap_view = NULL; + LLView* test_view = NULL; + + // now do snapping + switch(mCorner) + { + case LEFT_TOP: + snap_view = parentView->findSnapEdge(snap_delta_x, mouse_dir, SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_x -= scaled_rect.mLeft; + test_view = parentView->findSnapEdge(snap_delta_y, mouse_dir, SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_y -= scaled_rect.mTop; + if (!snap_view) + { + snap_view = test_view; + } + scaled_rect.mLeft += snap_delta_x; + scaled_rect.mTop += snap_delta_y; + break; + case LEFT_BOTTOM: + snap_view = parentView->findSnapEdge(snap_delta_x, mouse_dir, SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_x -= scaled_rect.mLeft; + test_view = parentView->findSnapEdge(snap_delta_y, mouse_dir, SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_y -= scaled_rect.mBottom; + if (!snap_view) + { + snap_view = test_view; + } + scaled_rect.mLeft += snap_delta_x; + scaled_rect.mBottom += snap_delta_y; + break; + case RIGHT_TOP: + snap_view = parentView->findSnapEdge(snap_delta_x, mouse_dir, SNAP_RIGHT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_x -= scaled_rect.mRight; + test_view = parentView->findSnapEdge(snap_delta_y, mouse_dir, SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_y -= scaled_rect.mTop; + if (!snap_view) + { + snap_view = test_view; + } + scaled_rect.mRight += snap_delta_x; + scaled_rect.mTop += snap_delta_y; + break; + case RIGHT_BOTTOM: + snap_view = parentView->findSnapEdge(snap_delta_x, mouse_dir, SNAP_RIGHT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_x -= scaled_rect.mRight; + test_view = parentView->findSnapEdge(snap_delta_y, mouse_dir, SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + snap_delta_y -= scaled_rect.mBottom; + if (!snap_view) + { + snap_view = test_view; + } + scaled_rect.mRight += snap_delta_x; + scaled_rect.mBottom += snap_delta_y; + break; + } + + parentView->snappedTo(snap_view); + + // reset parent rect + parentView->setRect(parent_rect); + + // translate and scale to new shape + parentView->reshape(scaled_rect.getWidth(), scaled_rect.getHeight(), FALSE); + parentView->translate(scaled_rect.mLeft - parentView->getRect().mLeft, scaled_rect.mBottom - parentView->getRect().mBottom); + + screen_x = mDragStartScreenX + delta_x + snap_delta_x; + screen_y = mDragStartScreenY + delta_y + snap_delta_y; + mDragStartScreenX = screen_x; + mDragStartScreenY = screen_y; + } + + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active) " << llendl; + handled = TRUE; + } + else + if( getVisible() && pointInHandle( x, y ) ) + { + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive) " << llendl; + handled = TRUE; + } + + if( handled ) + { + switch( mCorner ) + { + case RIGHT_BOTTOM: + case LEFT_TOP: + getWindow()->setCursor(UI_CURSOR_SIZENWSE); + break; + case LEFT_BOTTOM: + case RIGHT_TOP: + getWindow()->setCursor(UI_CURSOR_SIZENESW); + break; + } + } + + return handled; +} + +// assumes GL state is set for 2D +void LLResizeHandle::draw() +{ + if( mImage.notNull() && getVisible() && (RIGHT_BOTTOM == mCorner) ) + { + gl_draw_image( 0, 0, mImage ); + } +} + + +BOOL LLResizeHandle::pointInHandle( S32 x, S32 y ) +{ + if( pointInView(x, y) ) + { + const S32 TOP_BORDER = (mRect.getHeight() - RESIZE_BORDER_WIDTH); + const S32 RIGHT_BORDER = (mRect.getWidth() - RESIZE_BORDER_WIDTH); + + switch( mCorner ) + { + case LEFT_TOP: return (x <= RESIZE_BORDER_WIDTH) || (y >= TOP_BORDER); + case LEFT_BOTTOM: return (x <= RESIZE_BORDER_WIDTH) || (y <= RESIZE_BORDER_WIDTH); + case RIGHT_TOP: return (x >= RIGHT_BORDER) || (y >= TOP_BORDER); + case RIGHT_BOTTOM: return TRUE; + } + } + return FALSE; +} diff --git a/indra/llui/llresizehandle.h b/indra/llui/llresizehandle.h new file mode 100644 index 0000000000..1350d1af20 --- /dev/null +++ b/indra/llui/llresizehandle.h @@ -0,0 +1,56 @@ +/** + * @file llresizehandle.h + * @brief LLResizeHandle base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_RESIZEHANDLE_H +#define LL_RESIZEHANDLE_H + +#include "stdtypes.h" +#include "llview.h" +#include "llimagegl.h" +#include "llcoord.h" + + +class LLResizeHandle : public LLView +{ +public: + enum ECorner { LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM }; + + + LLResizeHandle(const LLString& name, const LLRect& rect, S32 min_width, S32 min_height, ECorner corner = RIGHT_BOTTOM ); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual void draw(); + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + + void setResizeLimits( S32 min_width, S32 min_height ) { mMinWidth = min_width; mMinHeight = min_height; } + +protected: + BOOL pointInHandle( S32 x, S32 y ); + +protected: + S32 mDragStartScreenX; + S32 mDragStartScreenY; + S32 mLastMouseScreenX; + S32 mLastMouseScreenY; + LLCoordGL mLastMouseDir; + LLPointer mImage; + S32 mMinWidth; + S32 mMinHeight; + ECorner mCorner; +}; + +const S32 RESIZE_HANDLE_HEIGHT = 16; +const S32 RESIZE_HANDLE_WIDTH = 16; + +#endif // LL_RESIZEHANDLE_H + + diff --git a/indra/llui/llresmgr.cpp b/indra/llui/llresmgr.cpp new file mode 100644 index 0000000000..67137d8bbb --- /dev/null +++ b/indra/llui/llresmgr.cpp @@ -0,0 +1,447 @@ +/** + * @file llresmgr.cpp + * @brief Localized resource manager + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// NOTE: this is a MINIMAL implementation. The interface will remain, but the implementation will +// (when the time is right) become dynamic and probably use external files. + +#include "linden_common.h" + +#include "llresmgr.h" +#include "llfontgl.h" +#include "llerror.h" +#include "llstring.h" + +// Global singleton +LLResMgr* gResMgr = NULL; + +LLResMgr::LLResMgr() +{ + U32 i; + + // Init values for each locale. + // Note: This is only the most bare-bones version. In the future, load these dynamically, on demand. + + ////////////////////////////////////////////////////////////////////////////// + // USA + // USA Fonts + for( i=0; idecimal_point[0]; + +#if LL_DARWIN + // On the Mac, locale support is broken before 10.4, which causes things to go all pear-shaped. + if(decimal == 0) + { + decimal = '.'; + } +#endif + + return decimal; +} + +char LLResMgr::getThousandsSeparator() const +{ + char separator = localeconv()->thousands_sep[0]; + +#if LL_DARWIN + // On the Mac, locale support is broken before 10.4, which causes things to go all pear-shaped. + if(separator == 0) + { + separator = ','; + } +#endif + + return separator; +} + +char LLResMgr::getMonetaryDecimalPoint() const +{ + char decimal = localeconv()->mon_decimal_point[0]; + +#if LL_DARWIN + // On the Mac, locale support is broken before 10.4, which causes things to go all pear-shaped. + if(decimal == 0) + { + decimal = '.'; + } +#endif + + return decimal; +} + +char LLResMgr::getMonetaryThousandsSeparator() const +{ + char separator = localeconv()->mon_thousands_sep[0]; + +#if LL_DARWIN + // On the Mac, locale support is broken before 10.4, which causes things to go all pear-shaped. + if(separator == 0) + { + separator = ','; + } +#endif + + return separator; +} + + +// Sets output to a string of integers with monetary separators inserted according to the locale. +void LLResMgr::getMonetaryString( LLString& output, S32 input ) const +{ + LLLocale locale(LLLocale::USER_LOCALE); + struct lconv *conv = localeconv(); + +#if LL_DARWIN + // On the Mac, locale support is broken before 10.4, which causes things to go all pear-shaped. + // Fake up a conv structure with some reasonable values for the fields this function uses. + struct lconv fakeconv; + if(conv->negative_sign[0] == 0) // Real locales all seem to have something here... + { + fakeconv = *conv; // start with what's there. + switch(mLocale) + { + default: // Unknown -- use the US defaults. + case LLLOCALE_USA: + case LLLOCALE_UK: // UK ends up being the same as US for the items used here. + fakeconv.negative_sign = "-"; + fakeconv.mon_grouping = "\x03\x03\x00"; // commas every 3 digits + fakeconv.n_sign_posn = 1; // negative sign before the string + break; + } + conv = &fakeconv; + } +#endif + + char* negative_sign = conv->negative_sign; + char separator = getMonetaryThousandsSeparator(); + char* grouping = conv->mon_grouping; + + // Note on mon_grouping: + // Specifies a string that defines the size of each group of digits in formatted monetary quantities. + // The operand for the mon_grouping keyword consists of a sequence of semicolon-separated integers. + // Each integer specifies the number of digits in a group. The initial integer defines the size of + // the group immediately to the left of the decimal delimiter. The following integers define succeeding + // groups to the left of the previous group. If the last integer is not -1, the size of the previous + // group (if any) is repeatedly used for the remainder of the digits. If the last integer is -1, no + // further grouping is performed. + + + // Note: we assume here that the currency symbol goes on the left. (Hey, it's Lindens! We can just decide.) + BOOL negative = (input < 0 ); + BOOL negative_before = negative && (conv->n_sign_posn != 2); + BOOL negative_after = negative && (conv->n_sign_posn == 2); + + LLString digits = llformat("%u", abs(input)); + if( !grouping || !grouping[0] ) + { + output.assign("L$"); + if( negative_before ) + { + output.append( negative_sign ); + } + output.append( digits ); + if( negative_after ) + { + output.append( negative_sign ); + } + return; + } + + S32 groupings[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + S32 cur_group; + for( cur_group = 0; grouping[ cur_group ]; cur_group++ ) + { + if( grouping[ cur_group ] != ';' ) + { + groupings[cur_group] = grouping[ cur_group ]; + } + cur_group++; + + if( groupings[cur_group] < 0 ) + { + break; + } + } + S32 group_count = cur_group; + + char reversed_output[20] = ""; + char forward_output[20] = ""; + S32 output_pos = 0; + + cur_group = 0; + S32 pos = digits.size()-1; + S32 count_within_group = 0; + while( (pos >= 0) && (groupings[cur_group] >= 0) ) + { + count_within_group++; + if( count_within_group > groupings[cur_group] ) + { + count_within_group = 1; + reversed_output[ output_pos++ ] = separator; + + if( (cur_group + 1) >= group_count ) + { + break; + } + else + if( groupings[cur_group + 1] > 0 ) + { + cur_group++; + } + } + reversed_output[ output_pos++ ] = digits[pos--]; + } + + while( pos >= 0 ) + { + reversed_output[ output_pos++ ] = digits[pos--]; + } + + + reversed_output[ output_pos ] = '\0'; + forward_output[ output_pos ] = '\0'; + + for( S32 i = 0; i < output_pos; i++ ) + { + forward_output[ output_pos - 1 - i ] = reversed_output[ i ]; + } + + output.assign("L$"); + if( negative_before ) + { + output.append( negative_sign ); + } + output.append( forward_output ); + if( negative_after ) + { + output.append( negative_sign ); + } +} + +void LLResMgr::getIntegerString( LLString& output, S32 input ) const +{ + S32 fraction = 0; + LLString fraction_string; + S32 remaining_count = input; + while(remaining_count > 0) + { + fraction = (remaining_count) % 1000; + + if (!output.empty()) + { + if (fraction == remaining_count) + { + fraction_string = llformat("%d%c", fraction, getThousandsSeparator()); + } + else + { + fraction_string = llformat("%3.3d%c", fraction, getThousandsSeparator()); + } + output = fraction_string + output; + } + else + { + if (fraction == remaining_count) + { + fraction_string = llformat("%d", fraction); + } + else + { + fraction_string = llformat("%3.3d", fraction); + } + output = fraction_string; + } + remaining_count /= 1000; + } +} + +const LLString LLFONT_ID_NAMES[] = +{ + LLString("OCRA"), + LLString("SANSSERIF"), + LLString("SANSSERIF_SMALL"), + LLString("SANSSERIF_BIG"), + LLString("SMALL"), +}; + +const LLFontGL* LLResMgr::getRes( LLString font_id ) const +{ + for (S32 i=0; igetColor("ScrollbarTrackColor") ), + mThumbColor ( LLUI::sColorsGroup->getColor("ScrollbarThumbColor") ), + mHighlightColor ( LLUI::sColorsGroup->getColor("DefaultHighlightLight") ), + mShadowColor ( LLUI::sColorsGroup->getColor("DefaultShadowLight") ), + mOnScrollEndCallback( NULL ), + mOnScrollEndData( NULL ) +{ + //llassert( 0 <= mDocSize ); + //llassert( 0 <= mDocPos && mDocPos <= mDocSize ); + + setTabStop(FALSE); + updateThumbRect(); + + // Page up and page down buttons + LLRect line_up_rect; + LLString line_up_img; + LLString line_up_selected_img; + LLString line_down_img; + LLString line_down_selected_img; + + LLRect line_down_rect; + + if( LLScrollbar::VERTICAL == mOrientation ) + { + line_up_rect.setLeftTopAndSize( 0, mRect.getHeight(), SCROLLBAR_SIZE, SCROLLBAR_SIZE ); + line_up_img="UIImgBtnScrollUpOutUUID"; + line_up_selected_img="UIImgBtnScrollUpInUUID"; + + line_down_rect.setOriginAndSize( 0, 0, SCROLLBAR_SIZE, SCROLLBAR_SIZE ); + line_down_img="UIImgBtnScrollDownOutUUID"; + line_down_selected_img="UIImgBtnScrollDownInUUID"; + } + else + { + // Horizontal + line_up_rect.setOriginAndSize( 0, 0, SCROLLBAR_SIZE, SCROLLBAR_SIZE ); + line_up_img="UIImgBtnScrollLeftOutUUID"; + line_up_selected_img="UIImgBtnScrollLeftInUUID"; + + line_down_rect.setOriginAndSize( mRect.getWidth() - SCROLLBAR_SIZE, 0, SCROLLBAR_SIZE, SCROLLBAR_SIZE ); + line_down_img="UIImgBtnScrollRightOutUUID"; + line_down_selected_img="UIImgBtnScrollRightInUUID"; + } + + LLButton* line_up_btn = new LLButton( + "Line Up", line_up_rect, + line_up_img, line_up_selected_img, "", + &LLScrollbar::onLineUpBtnPressed, this, LLFontGL::sSansSerif ); + if( LLScrollbar::VERTICAL == mOrientation ) + { + line_up_btn->setFollowsRight(); + line_up_btn->setFollowsTop(); + } + else + { + // horizontal + line_up_btn->setFollowsLeft(); + line_up_btn->setFollowsBottom(); + } + line_up_btn->setHeldDownCallback( &LLScrollbar::onLineUpBtnPressed ); + line_up_btn->setTabStop(FALSE); + addChild(line_up_btn); + + LLButton* line_down_btn = new LLButton( + "Line Down", line_down_rect, + line_down_img, line_down_selected_img, "", + &LLScrollbar::onLineDownBtnPressed, this, LLFontGL::sSansSerif ); + line_down_btn->setFollowsRight(); + line_down_btn->setFollowsBottom(); + line_down_btn->setHeldDownCallback( &LLScrollbar::onLineDownBtnPressed ); + line_down_btn->setTabStop(FALSE); + addChild(line_down_btn); +} + + +LLScrollbar::~LLScrollbar() +{ + // Children buttons killed by parent class +} + +void LLScrollbar::setDocParams( S32 size, S32 pos ) +{ + mDocSize = size; + mDocPos = llclamp( pos, 0, getDocPosMax() ); + mDocChanged = TRUE; + + updateThumbRect(); +} + +void LLScrollbar::setDocPos(S32 pos) +{ + mDocPos = llclamp( pos, 0, getDocPosMax() ); + mDocChanged = TRUE; + + updateThumbRect(); +} + +void LLScrollbar::setDocSize(S32 size) +{ + mDocSize = size; + mDocPos = llclamp( mDocPos, 0, getDocPosMax() ); + mDocChanged = TRUE; + + updateThumbRect(); +} + +void LLScrollbar::setPageSize( S32 page_size ) +{ + mPageSize = page_size; + mDocPos = llclamp( mDocPos, 0, getDocPosMax() ); + mDocChanged = TRUE; + + updateThumbRect(); +} + +void LLScrollbar::updateThumbRect() +{ +// llassert( 0 <= mDocSize ); +// llassert( 0 <= mDocPos && mDocPos <= getDocPosMax() ); + + const S32 THUMB_MIN_LENGTH = 16; + + S32 window_length = (mOrientation == LLScrollbar::HORIZONTAL) ? mRect.getWidth() : mRect.getHeight(); + S32 thumb_bg_length = window_length - 2 * SCROLLBAR_SIZE; + S32 visible_lines = llmin( mDocSize, mPageSize ); + S32 thumb_length = mDocSize ? llmax( visible_lines * thumb_bg_length / mDocSize, THUMB_MIN_LENGTH ) : thumb_bg_length; + + S32 variable_lines = mDocSize - visible_lines; + + if( mOrientation == LLScrollbar::VERTICAL ) + { + S32 thumb_start_max = thumb_bg_length + SCROLLBAR_SIZE; + S32 thumb_start_min = SCROLLBAR_SIZE + THUMB_MIN_LENGTH; + S32 thumb_start = variable_lines ? llclamp( thumb_start_max - (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min, thumb_start_max ) : thumb_start_max; + + mThumbRect.mLeft = 0; + mThumbRect.mTop = thumb_start; + mThumbRect.mRight = SCROLLBAR_SIZE; + mThumbRect.mBottom = thumb_start - thumb_length; + } + else + { + // Horizontal + S32 thumb_start_max = thumb_bg_length + SCROLLBAR_SIZE - thumb_length; + S32 thumb_start_min = SCROLLBAR_SIZE; + S32 thumb_start = variable_lines ? llclamp( thumb_start_min + (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min, thumb_start_max ) : thumb_start_min; + + mThumbRect.mLeft = thumb_start; + mThumbRect.mTop = SCROLLBAR_SIZE; + mThumbRect.mRight = thumb_start + thumb_length; + mThumbRect.mBottom = 0; + } + + if (mOnScrollEndCallback && mOnScrollEndData && (mDocPos == getDocPosMax())) + { + mOnScrollEndCallback(mOnScrollEndData); + } +} + +BOOL LLScrollbar::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // Check children first + BOOL handled_by_child = LLView::childrenHandleMouseDown(x, y, mask) != NULL; + if( !handled_by_child ) + { + if( mThumbRect.pointInRect(x,y) ) + { + // Start dragging the thumb + // No handler needed for focus lost since this clas has no state that depends on it. + gFocusMgr.setMouseCapture( this, NULL ); + mDragStartX = x; + mDragStartY = y; + mOrigRect.mTop = mThumbRect.mTop; + mOrigRect.mBottom = mThumbRect.mBottom; + mOrigRect.mLeft = mThumbRect.mLeft; + mOrigRect.mRight = mThumbRect.mRight; + mLastDelta = 0; + } + else + { + if( + ( (LLScrollbar::VERTICAL == mOrientation) && (mThumbRect.mTop < y) ) || + ( (LLScrollbar::HORIZONTAL == mOrientation) && (x < mThumbRect.mLeft) ) + ) + { + // Page up + pageUp(0); + } + else + if( + ( (LLScrollbar::VERTICAL == mOrientation) && (y < mThumbRect.mBottom) ) || + ( (LLScrollbar::HORIZONTAL == mOrientation) && (mThumbRect.mRight < x) ) + ) + { + // Page down + pageDown(0); + } + } + } + + return TRUE; +} + + +BOOL LLScrollbar::handleHover(S32 x, S32 y, MASK mask) +{ + // Note: we don't bother sending the event to the children (the arrow buttons) + // because they'll capture the mouse whenever they need hover events. + + BOOL handled = FALSE; + if( gFocusMgr.getMouseCapture() == this ) + { + S32 height = mRect.getHeight(); + S32 width = mRect.getWidth(); + + if( VERTICAL == mOrientation ) + { +// S32 old_pos = mThumbRect.mTop; + + S32 delta_pixels = y - mDragStartY; + if( mOrigRect.mBottom + delta_pixels < SCROLLBAR_SIZE ) + { + delta_pixels = SCROLLBAR_SIZE - mOrigRect.mBottom - 1; + } + else + if( mOrigRect.mTop + delta_pixels > height - SCROLLBAR_SIZE ) + { + delta_pixels = height - SCROLLBAR_SIZE - mOrigRect.mTop + 1; + } + + mThumbRect.mTop = mOrigRect.mTop + delta_pixels; + mThumbRect.mBottom = mOrigRect.mBottom + delta_pixels; + + S32 thumb_length = mThumbRect.getHeight(); + S32 thumb_track_length = height - 2 * SCROLLBAR_SIZE; + + + if( delta_pixels != mLastDelta || mDocChanged) + { + // Note: delta_pixels increases as you go up. mDocPos increases down (line 0 is at the top of the page). + S32 usable_track_length = thumb_track_length - thumb_length; + if( 0 < usable_track_length ) + { + S32 variable_lines = getDocPosMax(); + S32 pos = mThumbRect.mTop; + F32 ratio = F32(pos - SCROLLBAR_SIZE - thumb_length) / usable_track_length; + + S32 new_pos = llclamp( S32(variable_lines - ratio * variable_lines + 0.5f), 0, variable_lines ); + // Note: we do not call updateThumbRect() here. Instead we let the thumb and the document go slightly + // out of sync (less than a line's worth) to make the thumb feel responsive. + changeLine( new_pos - mDocPos, FALSE ); + } + } + + mLastDelta = delta_pixels; + + } + else + { + // Horizontal +// S32 old_pos = mThumbRect.mLeft; + + S32 delta_pixels = x - mDragStartX; + + if( mOrigRect.mLeft + delta_pixels < SCROLLBAR_SIZE ) + { + delta_pixels = SCROLLBAR_SIZE - mOrigRect.mLeft - 1; + } + else + if( mOrigRect.mRight + delta_pixels > width - SCROLLBAR_SIZE ) + { + delta_pixels = width - SCROLLBAR_SIZE - mOrigRect.mRight + 1; + } + + mThumbRect.mLeft = mOrigRect.mLeft + delta_pixels; + mThumbRect.mRight = mOrigRect.mRight + delta_pixels; + + S32 thumb_length = mThumbRect.getWidth(); + S32 thumb_track_length = width - 2 * SCROLLBAR_SIZE; + + if( delta_pixels != mLastDelta || mDocChanged) + { + // Note: delta_pixels increases as you go up. mDocPos increases down (line 0 is at the top of the page). + S32 usable_track_length = thumb_track_length - thumb_length; + if( 0 < usable_track_length ) + { + S32 variable_lines = getDocPosMax(); + S32 pos = mThumbRect.mLeft; + F32 ratio = F32(pos - SCROLLBAR_SIZE) / usable_track_length; + + S32 new_pos = llclamp( S32(ratio * variable_lines + 0.5f), 0, variable_lines); + + // Note: we do not call updateThumbRect() here. Instead we let the thumb and the document go slightly + // out of sync (less than a line's worth) to make the thumb feel responsive. + changeLine( new_pos - mDocPos, FALSE ); + } + } + + mLastDelta = delta_pixels; + } + + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; + handled = TRUE; + } + else + { + handled = childrenHandleMouseUp( x, y, mask ) != NULL; + } + + // Opaque + if( !handled ) + { + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl; + handled = TRUE; + } + + mDocChanged = FALSE; + return handled; +} + + +BOOL LLScrollbar::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + BOOL handled = FALSE; + if( getVisible() && mRect.localPointInRect( x, y ) ) + { + if( getEnabled() ) + { + changeLine( clicks * mStepSize, TRUE ); + } + handled = TRUE; + } + + return handled; +} + +BOOL LLScrollbar::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, void *carge_data, EAcceptance *accept, LLString &tooltip_msg) +{ + if (!drop) + { + //TODO: refactor this + S32 variable_lines = getDocPosMax(); + S32 pos = (VERTICAL == mOrientation) ? y : x; + S32 thumb_length = (VERTICAL == mOrientation) ? mThumbRect.getHeight() : mThumbRect.getWidth(); + S32 thumb_track_length = (VERTICAL == mOrientation) ? (mRect.getHeight() - 2 * SCROLLBAR_SIZE) : (mRect.getWidth() - 2 * SCROLLBAR_SIZE); + S32 usable_track_length = thumb_track_length - thumb_length; + F32 ratio = (VERTICAL == mOrientation) ? F32(pos - SCROLLBAR_SIZE - thumb_length) / usable_track_length + : F32(pos - SCROLLBAR_SIZE) / usable_track_length; + S32 new_pos = (VERTICAL == mOrientation) ? llclamp( S32(variable_lines - ratio * variable_lines + 0.5f), 0, variable_lines ) + : llclamp( S32(ratio * variable_lines + 0.5f), 0, variable_lines ); + changeLine( new_pos - mDocPos, TRUE ); + } + return TRUE; +} + +BOOL LLScrollbar::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + if( gFocusMgr.getMouseCapture() == this ) + { + gFocusMgr.setMouseCapture( NULL, NULL ); + handled = TRUE; + } + else + { + // Opaque, so don't just check children + handled = LLView::handleMouseUp( x, y, mask ); + } + + return handled; +} + +void LLScrollbar::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLView::reshape( width, height, called_from_parent ); + updateThumbRect(); +} + + +void LLScrollbar::draw() +{ + if( getVisible() ) + { + S32 local_mouse_x; + S32 local_mouse_y; + LLCoordWindow cursor_pos_window; + getWindow()->getCursorPosition(&cursor_pos_window); + LLCoordGL cursor_pos_gl; + getWindow()->convertCoords(cursor_pos_window, &cursor_pos_gl); + + screenPointToLocal(cursor_pos_gl.mX, cursor_pos_gl.mY, &local_mouse_x, &local_mouse_y); + BOOL other_captor = gFocusMgr.getMouseCapture() && gFocusMgr.getMouseCapture() != this; + BOOL hovered = mEnabled && !other_captor && (gFocusMgr.getMouseCapture() == this || mThumbRect.pointInRect(local_mouse_x, local_mouse_y)); + if (hovered) + { + mCurGlowStrength = lerp(mCurGlowStrength, mHoverGlowStrength, LLCriticalDamp::getInterpolant(0.05f)); + } + else + { + mCurGlowStrength = lerp(mCurGlowStrength, 0.f, LLCriticalDamp::getInterpolant(0.05f)); + } + + + // Draw background and thumb. + LLUUID rounded_rect_image_id; + rounded_rect_image_id.set(LLUI::sAssetsGroup->getString("rounded_square.tga")); + LLImageGL* rounded_rect_imagep = LLUI::sImageProvider->getUIImageByID(rounded_rect_image_id); + + if (!rounded_rect_imagep) + { + gl_rect_2d(mOrientation == HORIZONTAL ? SCROLLBAR_SIZE : 0, + mOrientation == VERTICAL ? mRect.getHeight() - 2 * SCROLLBAR_SIZE : mRect.getHeight(), + mOrientation == HORIZONTAL ? mRect.getWidth() - 2 * SCROLLBAR_SIZE : mRect.getWidth(), + mOrientation == VERTICAL ? SCROLLBAR_SIZE : 0, mTrackColor, TRUE); + + gl_rect_2d(mThumbRect, mThumbColor, TRUE); + + } + else + { + // Background + gl_draw_scaled_image_with_border(mOrientation == HORIZONTAL ? SCROLLBAR_SIZE : 0, + mOrientation == VERTICAL ? SCROLLBAR_SIZE : 0, + 16, + 16, + mOrientation == HORIZONTAL ? mRect.getWidth() - 2 * SCROLLBAR_SIZE : mRect.getWidth(), + mOrientation == VERTICAL ? mRect.getHeight() - 2 * SCROLLBAR_SIZE : mRect.getHeight(), + rounded_rect_imagep, + mTrackColor, + TRUE); + + // Thumb + LLRect outline_rect = mThumbRect; + outline_rect.stretch(2); + + if (gFocusMgr.getKeyboardFocus() == this) + { + gl_draw_scaled_image_with_border(outline_rect.mLeft, outline_rect.mBottom, 16, 16, outline_rect.getWidth(), outline_rect.getHeight(), + rounded_rect_imagep, gFocusMgr.getFocusColor() ); + } + + gl_draw_scaled_image_with_border(mThumbRect.mLeft, mThumbRect.mBottom, 16, 16, mThumbRect.getWidth(), mThumbRect.getHeight(), + rounded_rect_imagep, mThumbColor ); + if (mCurGlowStrength > 0.01f) + { + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + gl_draw_scaled_image_with_border(mThumbRect.mLeft, mThumbRect.mBottom, 16, 16, mThumbRect.getWidth(), mThumbRect.getHeight(), + rounded_rect_imagep, LLColor4(1.f, 1.f, 1.f, mCurGlowStrength), TRUE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + } + + BOOL was_scrolled_to_bottom = (getDocPos() == getDocPosMax()); + if (mOnScrollEndCallback && was_scrolled_to_bottom) + { + mOnScrollEndCallback(mOnScrollEndData); + } + // Draw children + LLView::draw(); + } +} + +void LLScrollbar::changeLine( S32 delta, BOOL update_thumb ) +{ + S32 new_pos = llclamp( mDocPos + delta, 0, getDocPosMax() ); + if( new_pos != mDocPos ) + { + mDocPos = new_pos; + } + + if( mChangeCallback ) + { + mChangeCallback( mDocPos, this, mCallbackUserData ); + } + + if( update_thumb ) + { + updateThumbRect(); + } +} + +void LLScrollbar::setValue(const LLSD& value) +{ + setDocPos((S32) value.asInteger()); +} + +EWidgetType LLScrollbar::getWidgetType() const +{ + return WIDGET_TYPE_SCROLLBAR; +} + +LLString LLScrollbar::getWidgetTag() const +{ + return LL_SCROLLBAR_TAG; +} + +BOOL LLScrollbar::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + BOOL handled = FALSE; + + if( getVisible() && mEnabled && !called_from_parent ) + { + switch( key ) + { + case KEY_HOME: + changeLine( -mDocPos, TRUE ); + handled = TRUE; + break; + + case KEY_END: + changeLine( getDocPosMax() - mDocPos, TRUE ); + handled = TRUE; + break; + + case KEY_DOWN: + changeLine( mStepSize, TRUE ); + handled = TRUE; + break; + + case KEY_UP: + changeLine( - mStepSize, TRUE ); + handled = TRUE; + break; + + case KEY_PAGE_DOWN: + pageDown(1); + break; + + case KEY_PAGE_UP: + pageUp(1); + break; + } + } + + return handled; +} + +void LLScrollbar::pageUp(S32 overlap) +{ + if (mDocSize > mPageSize) + { + changeLine( -(mPageSize - overlap), TRUE ); + } +} + +void LLScrollbar::pageDown(S32 overlap) +{ + if (mDocSize > mPageSize) + { + changeLine( mPageSize - overlap, TRUE ); + } +} + +// static +void LLScrollbar::onLineUpBtnPressed( void* userdata ) +{ + LLScrollbar* self = (LLScrollbar*) userdata; + + self->changeLine( - self->mStepSize, TRUE ); +} + +// static +void LLScrollbar::onLineDownBtnPressed( void* userdata ) +{ + LLScrollbar* self = (LLScrollbar*) userdata; + self->changeLine( self->mStepSize, TRUE ); +} + diff --git a/indra/llui/llscrollbar.h b/indra/llui/llscrollbar.h new file mode 100644 index 0000000000..f479707499 --- /dev/null +++ b/indra/llui/llscrollbar.h @@ -0,0 +1,123 @@ +/** + * @file llscrollbar.h + * @brief Scrollbar UI widget + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_SCROLLBAR_H +#define LL_SCROLLBAR_H + +#include "stdtypes.h" +#include "lluictrl.h" +#include "v4color.h" + +// +// Constants +// +const S32 SCROLLBAR_SIZE = 16; + + +// +// Classes +// +class LLScrollbar +: public LLUICtrl +{ +public: + enum ORIENTATION { HORIZONTAL, VERTICAL }; + + LLScrollbar(const LLString& name, LLRect rect, + ORIENTATION orientation, + S32 doc_size, S32 doc_pos, S32 page_size, + void(*change_callback)( S32 new_pos, LLScrollbar* self, void* userdata ), + void* callback_user_data = NULL, + S32 step_size = 1); + + virtual ~LLScrollbar(); + + virtual void setValue(const LLSD& value); + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + // Overrides from LLView + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); + virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, void *carge_data, EAcceptance *accept, LLString &tooltip_msg); + + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + + virtual void draw(); + + void setDocParams( S32 size, S32 pos ); + + // How long the "document" is. + void setDocSize( S32 size ); + S32 getDocSize() { return mDocSize; } + + // How many "lines" the "document" has scrolled. + // 0 <= DocPos <= DocSize - DocVisibile + void setDocPos( S32 pos ); + S32 getDocPos() { return mDocPos; } + + // How many "lines" of the "document" is can appear on a page. + void setPageSize( S32 page_size ); + S32 getPageSize() { return mPageSize; } + + // The farthest the document can be scrolled (top of the last page). + S32 getDocPosMax() { return llmax( 0, mDocSize - mPageSize); } + + void pageUp(S32 overlap); + void pageDown(S32 overlap); + + static void onLineUpBtnPressed(void* userdata); + static void onLineDownBtnPressed(void* userdata); + + void setTrackColor( const LLColor4& color ) { mTrackColor = color; } + void setThumbColor( const LLColor4& color ) { mThumbColor = color; } + void setHighlightColor( const LLColor4& color ) { mHighlightColor = color; } + void setShadowColor( const LLColor4& color ) { mShadowColor = color; } + + void setOnScrollEndCallback(void (*callback)(void*), void* userdata) { mOnScrollEndCallback = callback; mOnScrollEndData = userdata;} +protected: + void updateThumbRect(); + void changeLine(S32 delta, BOOL update_thumb ); + +protected: + void (*mChangeCallback)( S32 new_pos, LLScrollbar* self, void* userdata ); + void* mCallbackUserData; + + ORIENTATION mOrientation; + S32 mDocSize; // Size of the document that the scrollbar is modeling. Units depend on the user. 0 <= mDocSize. + S32 mDocPos; // Position within the doc that the scrollbar is modeling, in "lines" (user size) + S32 mPageSize; // Maximum number of lines that can be seen at one time. + S32 mStepSize; + BOOL mDocChanged; + + LLRect mThumbRect; + S32 mDragStartX; + S32 mDragStartY; + F32 mHoverGlowStrength; + F32 mCurGlowStrength; + + LLRect mOrigRect; + S32 mLastDelta; + + LLColor4 mTrackColor; + LLColor4 mThumbColor; + LLColor4 mFocusColor; + LLColor4 mHighlightColor; + LLColor4 mShadowColor; + + void (*mOnScrollEndCallback)(void*); + void *mOnScrollEndData; +}; + + + +#endif // LL_SCROLLBAR_H diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp new file mode 100644 index 0000000000..15bb8e3f24 --- /dev/null +++ b/indra/llui/llscrollcontainer.cpp @@ -0,0 +1,772 @@ +/** + * @file llscrollcontainer.cpp + * @brief LLScrollableContainerView base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//***************************************************************************** +// +// A view meant to encapsulate a clipped region which is +// scrollable. It automatically takes care of pixel perfect scrolling +// and cliipping, as well as turning the scrollbars on or off based on +// the width and height of the view you're scrolling. +// +//***************************************************************************** + +#include "linden_common.h" + +#include "llgl.h" + +#include "llscrollcontainer.h" +#include "llscrollbar.h" +#include "llui.h" +#include "llkeyboard.h" +#include "llviewborder.h" +#include "llfocusmgr.h" +#include "llframetimer.h" +#include "lluictrlfactory.h" +#include "llfontgl.h" + +#include "llglheaders.h" + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- + +static const S32 HORIZONTAL_MULTIPLE = 8; +static const S32 VERTICAL_MULTIPLE = 16; +static const F32 MIN_AUTO_SCROLL_RATE = 120.f; +static const F32 MAX_AUTO_SCROLL_RATE = 500.f; +static const F32 AUTO_SCROLL_RATE_ACCEL = 120.f; + +///---------------------------------------------------------------------------- +/// Class LLScrollableContainerView +///---------------------------------------------------------------------------- + +// Default constructor +LLScrollableContainerView::LLScrollableContainerView( const LLString& name, + const LLRect& rect, + LLView* scrolled_view, + BOOL is_opaque, + const LLColor4& bg_color ) : + LLUICtrl( name, rect, FALSE, NULL, NULL ), + mScrolledView( scrolled_view ), + mIsOpaque( is_opaque ), + mBackgroundColor( bg_color ), + mReserveScrollCorner( FALSE ), + mAutoScrolling( FALSE ), + mAutoScrollRate( 0.f ) +{ + if( mScrolledView ) + { + addChild( mScrolledView ); + } + + init(); +} + +// LLUICtrl constructor +LLScrollableContainerView::LLScrollableContainerView( const LLString& name, const LLRect& rect, + LLUICtrl* scrolled_ctrl, BOOL is_opaque, + const LLColor4& bg_color) : + LLUICtrl( name, rect, FALSE, NULL, NULL ), + mScrolledView( scrolled_ctrl ), + mIsOpaque( is_opaque ), + mBackgroundColor( bg_color ), + mReserveScrollCorner( FALSE ), + mAutoScrolling( FALSE ), + mAutoScrollRate( 0.f ) +{ + if( scrolled_ctrl ) + { + addChild( scrolled_ctrl ); + } + + init(); +} + +void LLScrollableContainerView::init() +{ + LLRect border_rect( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + mBorder = new LLViewBorder( "scroll border", border_rect, LLViewBorder::BEVEL_IN ); + addChild( mBorder ); + + mInnerRect.set( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + mInnerRect.stretch( -mBorder->getBorderWidth() ); + + LLRect vertical_scroll_rect = mInnerRect; + vertical_scroll_rect.mLeft = vertical_scroll_rect.mRight - SCROLLBAR_SIZE; + mScrollbar[VERTICAL] = new LLScrollbar( "scrollable vertical", + vertical_scroll_rect, + LLScrollbar::VERTICAL, + mInnerRect.getHeight(), + 0, + mInnerRect.getHeight(), + NULL, this, + VERTICAL_MULTIPLE); + addChild( mScrollbar[VERTICAL] ); + mScrollbar[VERTICAL]->setVisible( FALSE ); + mScrollbar[VERTICAL]->setFollowsRight(); + mScrollbar[VERTICAL]->setFollowsTop(); + mScrollbar[VERTICAL]->setFollowsBottom(); + + LLRect horizontal_scroll_rect = mInnerRect; + horizontal_scroll_rect.mTop = horizontal_scroll_rect.mBottom + SCROLLBAR_SIZE; + mScrollbar[HORIZONTAL] = new LLScrollbar( "scrollable horizontal", + horizontal_scroll_rect, + LLScrollbar::HORIZONTAL, + mInnerRect.getWidth(), + 0, + mInnerRect.getWidth(), + NULL, this, + HORIZONTAL_MULTIPLE); + addChild( mScrollbar[HORIZONTAL] ); + mScrollbar[HORIZONTAL]->setVisible( FALSE ); + mScrollbar[HORIZONTAL]->setFollowsLeft(); + mScrollbar[HORIZONTAL]->setFollowsRight(); + + setTabStop(FALSE); +} + +// Destroys the object +LLScrollableContainerView::~LLScrollableContainerView( void ) +{ + // mScrolledView and mScrollbar are child views, so the LLView + // destructor takes care of memory deallocation. + for( S32 i = 0; i < SCROLLBAR_COUNT; i++ ) + { + mScrollbar[i] = NULL; + } + mScrolledView = NULL; +} + +/* +// scrollbar handlers +void LLScrollableContainerView::horizontalChange( S32 new_pos, + LLScrollbar* sb, + void* user_data ) +{ + LLScrollableContainerView* cont = reinterpret_cast(user_data); +// cont->scrollHorizontal( new_pos ); +} + + +void LLScrollableContainerView::verticalChange( S32 new_pos, LLScrollbar* sb, + void* user_data ) +{ + LLScrollableContainerView* cont = reinterpret_cast(user_data); +// cont->scrollVertical( new_pos ); +} +*/ + +// internal scrollbar handlers +// virtual +void LLScrollableContainerView::scrollHorizontal( S32 new_pos ) +{ + //llinfos << "LLScrollableContainerView::scrollHorizontal()" << llendl; + if( mScrolledView ) + { + LLRect doc_rect = mScrolledView->getRect(); + S32 old_pos = -(doc_rect.mLeft - mInnerRect.mLeft); + mScrolledView->translate( -(new_pos - old_pos), 0 ); + } +} + +// virtual +void LLScrollableContainerView::scrollVertical( S32 new_pos ) +{ + // llinfos << "LLScrollableContainerView::scrollVertical() " << new_pos << llendl; + if( mScrolledView ) + { + LLRect doc_rect = mScrolledView->getRect(); + S32 old_pos = doc_rect.mTop - mInnerRect.mTop; + mScrolledView->translate( 0, new_pos - old_pos ); + } +} + +// LLView functionality +void LLScrollableContainerView::reshape(S32 width, S32 height, + BOOL called_from_parent) +{ + LLUICtrl::reshape( width, height, called_from_parent ); + + mInnerRect.set( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + mInnerRect.stretch( -mBorder->getBorderWidth() ); + + if (mScrolledView) + { + const LLRect& scrolled_rect = mScrolledView->getRect(); + + S32 visible_width = 0; + S32 visible_height = 0; + BOOL show_v_scrollbar = FALSE; + BOOL show_h_scrollbar = FALSE; + calcVisibleSize( scrolled_rect, &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); + + mScrollbar[VERTICAL]->setDocSize( scrolled_rect.getHeight() ); + mScrollbar[VERTICAL]->setPageSize( visible_height ); + + mScrollbar[HORIZONTAL]->setDocSize( scrolled_rect.getWidth() ); + mScrollbar[HORIZONTAL]->setPageSize( visible_width ); + } +} + +BOOL LLScrollableContainerView::handleKey( KEY key, MASK mask, BOOL called_from_parent ) +{ + if( getVisible() && mEnabled ) + { + if( called_from_parent ) + { + // Downward traversal + + // Don't pass keys to scrollbars on downward. + + // Handle 'child' view. + if( mScrolledView && mScrolledView->handleKey(key, mask, TRUE) ) + { + return TRUE; + } + } + else + { + // Upward traversal + + for( S32 i = 0; i < SCROLLBAR_COUNT; i++ ) + { + // Note: the scrollbar _is_ actually being called from it's parent. Here + // we're delgating LLScrollableContainerView's upward traversal to the scrollbars + if( mScrollbar[i]->handleKey(key, mask, TRUE) ) + { + return TRUE; + } + } + + if (getParent()) + { + return getParent()->handleKey( key, mask, FALSE ); + } + } + } + + return FALSE; +} + +BOOL LLScrollableContainerView::handleScrollWheel( S32 x, S32 y, S32 clicks ) +{ + if( mEnabled ) + { + for( S32 i = 0; i < SCROLLBAR_COUNT; i++ ) + { + // Note: tries vertical and then horizontal + + // Pretend the mouse is over the scrollbar + if( mScrollbar[i]->handleScrollWheel( 0, 0, clicks ) ) + { + return TRUE; + } + } + } + + // Opaque + return TRUE; +} + +BOOL LLScrollableContainerView::handleDragAndDrop(S32 x, S32 y, MASK mask, + BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + LLString& tooltip_msg) +{ + // Scroll folder view if needed. Never accepts a drag or drop. + *accept = ACCEPT_NO; + BOOL handled = FALSE; + if( mScrollbar[HORIZONTAL]->getVisible() || mScrollbar[VERTICAL]->getVisible() ) + { + const S32 AUTOSCROLL_SIZE = 10; + S32 auto_scroll_speed = llround(mAutoScrollRate * LLFrameTimer::getFrameDeltaTimeF32()); + + LLRect inner_rect_local( 0, mInnerRect.getHeight(), mInnerRect.getWidth(), 0 ); + if( mScrollbar[HORIZONTAL]->getVisible() ) + { + inner_rect_local.mBottom += SCROLLBAR_SIZE; + } + if( mScrollbar[VERTICAL]->getVisible() ) + { + inner_rect_local.mRight -= SCROLLBAR_SIZE; + } + + if( mScrollbar[HORIZONTAL]->getVisible() ) + { + LLRect left_scroll_rect = inner_rect_local; + left_scroll_rect.mRight = AUTOSCROLL_SIZE; + if( left_scroll_rect.pointInRect( x, y ) && (mScrollbar[HORIZONTAL]->getDocPos() > 0) ) + { + mScrollbar[HORIZONTAL]->setDocPos( mScrollbar[HORIZONTAL]->getDocPos() - auto_scroll_speed ); + mAutoScrolling = TRUE; + handled = TRUE; + } + + LLRect right_scroll_rect = inner_rect_local; + right_scroll_rect.mLeft = inner_rect_local.mRight - AUTOSCROLL_SIZE; + if( right_scroll_rect.pointInRect( x, y ) && (mScrollbar[HORIZONTAL]->getDocPos() < mScrollbar[HORIZONTAL]->getDocPosMax()) ) + { + mScrollbar[HORIZONTAL]->setDocPos( mScrollbar[HORIZONTAL]->getDocPos() + auto_scroll_speed ); + mAutoScrolling = TRUE; + handled = TRUE; + } + } + if( mScrollbar[VERTICAL]->getVisible() ) + { + LLRect bottom_scroll_rect = inner_rect_local; + bottom_scroll_rect.mTop = AUTOSCROLL_SIZE + bottom_scroll_rect.mBottom; + if( bottom_scroll_rect.pointInRect( x, y ) && (mScrollbar[VERTICAL]->getDocPos() < mScrollbar[VERTICAL]->getDocPosMax()) ) + { + mScrollbar[VERTICAL]->setDocPos( mScrollbar[VERTICAL]->getDocPos() + auto_scroll_speed ); + mAutoScrolling = TRUE; + handled = TRUE; + } + + LLRect top_scroll_rect = inner_rect_local; + top_scroll_rect.mBottom = inner_rect_local.mTop - AUTOSCROLL_SIZE; + if( top_scroll_rect.pointInRect( x, y ) && (mScrollbar[VERTICAL]->getDocPos() > 0) ) + { + mScrollbar[VERTICAL]->setDocPos( mScrollbar[VERTICAL]->getDocPos() - auto_scroll_speed ); + mAutoScrolling = TRUE; + handled = TRUE; + } + } + } + + if( !handled ) + { + handled = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, + cargo_data, accept, tooltip_msg) != NULL; + } + + return TRUE; +} + + +BOOL LLScrollableContainerView::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect) +{ + if( getVisible() && pointInView(x,y) ) + { + S32 local_x, local_y; + for( S32 i = 0; i < SCROLLBAR_COUNT; i++ ) + { + local_x = x - mScrollbar[i]->getRect().mLeft; + local_y = y - mScrollbar[i]->getRect().mBottom; + if( mScrollbar[i]->handleToolTip(local_x, local_y, msg, sticky_rect) ) + { + return TRUE; + } + } + // Handle 'child' view. + if( mScrolledView ) + { + local_x = x - mScrolledView->getRect().mLeft; + local_y = y - mScrolledView->getRect().mBottom; + if( mScrolledView->handleToolTip(local_x, local_y, msg, sticky_rect) ) + { + return TRUE; + } + } + + // Opaque + return TRUE; + } + return FALSE; +} + +void LLScrollableContainerView::calcVisibleSize( S32 *visible_width, S32 *visible_height, BOOL* show_h_scrollbar, BOOL* show_v_scrollbar ) +{ + const LLRect& rect = mScrolledView->getRect(); + calcVisibleSize(rect, visible_width, visible_height, show_h_scrollbar, show_v_scrollbar); +} + +void LLScrollableContainerView::calcVisibleSize( const LLRect& doc_rect, S32 *visible_width, S32 *visible_height, BOOL* show_h_scrollbar, BOOL* show_v_scrollbar ) +{ + S32 doc_width = doc_rect.getWidth(); + S32 doc_height = doc_rect.getHeight(); + + *visible_width = mRect.getWidth() - 2 * mBorder->getBorderWidth(); + *visible_height = mRect.getHeight() - 2 * mBorder->getBorderWidth(); + + *show_v_scrollbar = FALSE; + if( *visible_height < doc_height ) + { + *show_v_scrollbar = TRUE; + *visible_width -= SCROLLBAR_SIZE; + } + + *show_h_scrollbar = FALSE; + if( *visible_width < doc_width ) + { + *show_h_scrollbar = TRUE; + *visible_height -= SCROLLBAR_SIZE; + + // Must retest now that visible_height has changed + if( !*show_v_scrollbar && (*visible_height < doc_height) ) + { + *show_v_scrollbar = TRUE; + *visible_width -= SCROLLBAR_SIZE; + } + } +} + +void LLScrollableContainerView::draw() +{ + if (mAutoScrolling) + { + // add acceleration to autoscroll + mAutoScrollRate = llmin(mAutoScrollRate + (LLFrameTimer::getFrameDeltaTimeF32() * AUTO_SCROLL_RATE_ACCEL), MAX_AUTO_SCROLL_RATE); + } + else + { + // reset to minimum + mAutoScrollRate = MIN_AUTO_SCROLL_RATE; + } + // clear this flag to be set on next call to handleDragAndDrop + mAutoScrolling = FALSE; + + if( getVisible() ) + { + // auto-focus when scrollbar active + // this allows us to capture user intent (i.e. stop automatically scrolling the view/etc) + if (!gFocusMgr.childHasKeyboardFocus(this) && + (gFocusMgr.getMouseCapture() == mScrollbar[VERTICAL] || gFocusMgr.getMouseCapture() == mScrollbar[HORIZONTAL])) + { + focusFirstItem(); + } + + // Draw background + if( mIsOpaque ) + { + LLGLSNoTexture no_texture; + glColor4fv( mBackgroundColor.mV ); + gl_rect_2d( mInnerRect ); + } + + // Draw mScrolledViews and update scroll bars. + // get a scissor region ready, and draw the scrolling view. The + // scissor region ensures that we don't draw outside of the bounds + // of the rectangle. + if( mScrolledView ) + { + updateScroll(); + + // Draw the scrolled area. + { + S32 visible_width = 0; + S32 visible_height = 0; + BOOL show_v_scrollbar = FALSE; + BOOL show_h_scrollbar = FALSE; + calcVisibleSize( mScrolledView->getRect(), &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); + + LLGLEnable scissor_test(GL_SCISSOR_TEST); + LLUI::setScissorRegionLocal(LLRect(mInnerRect.mLeft, + mInnerRect.mBottom + (show_h_scrollbar ? SCROLLBAR_SIZE : 0) + visible_height, + visible_width, + mInnerRect.mBottom + (show_h_scrollbar ? SCROLLBAR_SIZE : 0) + )); + drawChild(mScrolledView); + } + } + + // Highlight border if a child of this container has keyboard focus + if( mBorder->getVisible() ) + { + mBorder->setKeyboardFocusHighlight( gFocusMgr.childHasKeyboardFocus(this) ); + } + + // Draw all children except mScrolledView + // Note: scrollbars have been adjusted by above drawing code + for (child_list_const_reverse_iter_t child_iter = getChildList()->rbegin(); + child_iter != getChildList()->rend(); ++child_iter) + { + LLView *viewp = *child_iter; + if( sDebugRects ) + { + sDepth++; + } + if( (viewp != mScrolledView) && viewp->getVisible() ) + { + drawChild(viewp); + } + if( sDebugRects ) + { + sDepth--; + } + } + + if (sDebugRects) + { + drawDebugRect(); + } + } +} + +void LLScrollableContainerView::updateScroll() +{ + LLRect doc_rect = mScrolledView->getRect(); + S32 doc_width = doc_rect.getWidth(); + S32 doc_height = doc_rect.getHeight(); + S32 visible_width = 0; + S32 visible_height = 0; + BOOL show_v_scrollbar = FALSE; + BOOL show_h_scrollbar = FALSE; + calcVisibleSize( doc_rect, &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); + + S32 border_width = mBorder->getBorderWidth(); + if( show_v_scrollbar ) + { + if( doc_rect.mTop < mRect.getHeight() - border_width ) + { + mScrolledView->translate( 0, mRect.getHeight() - border_width - doc_rect.mTop ); + } + + scrollVertical( mScrollbar[VERTICAL]->getDocPos() ); + mScrollbar[VERTICAL]->setVisible( TRUE ); + + S32 v_scrollbar_height = visible_height; + if( !show_h_scrollbar && mReserveScrollCorner ) + { + v_scrollbar_height -= SCROLLBAR_SIZE; + } + mScrollbar[VERTICAL]->reshape( SCROLLBAR_SIZE, v_scrollbar_height, TRUE ); + + // Make room for the horizontal scrollbar (or not) + S32 v_scrollbar_offset = 0; + if( show_h_scrollbar || mReserveScrollCorner ) + { + v_scrollbar_offset = SCROLLBAR_SIZE; + } + LLRect r = mScrollbar[VERTICAL]->getRect(); + r.translate( 0, mInnerRect.mBottom - r.mBottom + v_scrollbar_offset ); + mScrollbar[VERTICAL]->setRect( r ); + } + else + { + mScrolledView->translate( 0, mRect.getHeight() - border_width - doc_rect.mTop ); + + mScrollbar[VERTICAL]->setVisible( FALSE ); + mScrollbar[VERTICAL]->setDocPos( 0 ); + } + + if( show_h_scrollbar ) + { + if( doc_rect.mLeft > border_width ) + { + mScrolledView->translate( border_width - doc_rect.mLeft, 0 ); + mScrollbar[HORIZONTAL]->setDocPos( 0 ); + } + else + { + scrollHorizontal( mScrollbar[HORIZONTAL]->getDocPos() ); + } + + mScrollbar[HORIZONTAL]->setVisible( TRUE ); + S32 h_scrollbar_width = visible_width; + if( !show_v_scrollbar && mReserveScrollCorner ) + { + h_scrollbar_width -= SCROLLBAR_SIZE; + } + mScrollbar[HORIZONTAL]->reshape( h_scrollbar_width, SCROLLBAR_SIZE, TRUE ); + } + else + { + mScrolledView->translate( border_width - doc_rect.mLeft, 0 ); + + mScrollbar[HORIZONTAL]->setVisible( FALSE ); + mScrollbar[HORIZONTAL]->setDocPos( 0 ); + } + + mScrollbar[HORIZONTAL]->setDocSize( doc_width ); + mScrollbar[HORIZONTAL]->setPageSize( visible_width ); + + mScrollbar[VERTICAL]->setDocSize( doc_height ); + mScrollbar[VERTICAL]->setPageSize( visible_height ); +} + +void LLScrollableContainerView::setBorderVisible(BOOL b) +{ + mBorder->setVisible( b ); +} + +// Scroll so that as much of rect as possible is showing (where rect is defined in the space of scroller view, not scrolled) +void LLScrollableContainerView::scrollToShowRect(const LLRect& rect, const LLCoordGL& desired_offset) +{ + if (!mScrolledView) + { + llwarns << "LLScrollableContainerView::scrollToShowRect with no view!" << llendl; + return; + } + + S32 visible_width = 0; + S32 visible_height = 0; + BOOL show_v_scrollbar = FALSE; + BOOL show_h_scrollbar = FALSE; + const LLRect& scrolled_rect = mScrolledView->getRect(); + calcVisibleSize( scrolled_rect, &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); + + // can't be so far left that right side of rect goes off screen, or so far right that left side does + S32 horiz_offset = llclamp(desired_offset.mX, llmin(0, -visible_width + rect.getWidth()), 0); + // can't be so high that bottom of rect goes off screen, or so low that top does + S32 vert_offset = llclamp(desired_offset.mY, 0, llmax(0, visible_height - rect.getHeight())); + + // Vertical + // 1. First make sure the top is visible + // 2. Then, if possible without hiding the top, make the bottom visible. + S32 vert_pos = mScrollbar[VERTICAL]->getDocPos(); + + // find scrollbar position to get top of rect on screen (scrolling up) + S32 top_offset = scrolled_rect.mTop - rect.mTop - vert_offset; + // find scrollbar position to get bottom of rect on screen (scrolling down) + S32 bottom_offset = vert_offset == 0 ? scrolled_rect.mTop - rect.mBottom - visible_height : top_offset; + // scroll up far enough to see top or scroll down just enough if item is bigger than visual area + if( vert_pos >= top_offset || visible_height < rect.getHeight()) + { + vert_pos = top_offset; + } + // else scroll down far enough to see bottom + else + if( vert_pos <= bottom_offset ) + { + vert_pos = bottom_offset; + } + + mScrollbar[VERTICAL]->setDocSize( scrolled_rect.getHeight() ); + mScrollbar[VERTICAL]->setPageSize( visible_height ); + mScrollbar[VERTICAL]->setDocPos( vert_pos ); + + // Horizontal + // 1. First make sure left side is visible + // 2. Then, if possible without hiding the left side, make the right side visible. + S32 horiz_pos = mScrollbar[HORIZONTAL]->getDocPos(); + S32 left_offset = rect.mLeft - scrolled_rect.mLeft + horiz_offset; + S32 right_offset = horiz_offset == 0 ? rect.mRight - scrolled_rect.mLeft - visible_width : left_offset; + + if( horiz_pos >= left_offset || visible_width < rect.getWidth() ) + { + horiz_pos = left_offset; + } + else if( horiz_pos <= right_offset ) + { + horiz_pos = right_offset; + } + + mScrollbar[HORIZONTAL]->setDocSize( scrolled_rect.getWidth() ); + mScrollbar[HORIZONTAL]->setPageSize( visible_width ); + mScrollbar[HORIZONTAL]->setDocPos( horiz_pos ); + + // propagate scroll to document + updateScroll(); +} + +void LLScrollableContainerView::pageUp(S32 overlap) +{ + mScrollbar[VERTICAL]->pageUp(overlap); +} + +void LLScrollableContainerView::pageDown(S32 overlap) +{ + mScrollbar[VERTICAL]->pageDown(overlap); +} + +void LLScrollableContainerView::goToTop() +{ + mScrollbar[VERTICAL]->setDocPos(0); +} + +void LLScrollableContainerView::goToBottom() +{ + mScrollbar[VERTICAL]->setDocPos(mScrollbar[VERTICAL]->getDocSize()); +} + +S32 LLScrollableContainerView::getBorderWidth() +{ + if (mBorder) + { + return mBorder->getBorderWidth(); + } + + return 0; +} + +// virtual +LLXMLNodePtr LLScrollableContainerView::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLView::getXML(); + + // Attributes + + node->createChild("opaque", TRUE)->setBoolValue(mIsOpaque); + + if (mIsOpaque) + { + node->createChild("color", TRUE)->setFloatValue(4, mBackgroundColor.mV); + } + + // Contents + + LLXMLNodePtr child_node = mScrolledView->getXML(); + + node->addChild(child_node); + + return node; +} + +LLView* LLScrollableContainerView::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("scroll_container"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + BOOL opaque = FALSE; + node->getAttributeBOOL("opaque", opaque); + + LLColor4 color(0,0,0,0); + LLUICtrlFactory::getAttributeColor(node,"color", color); + + // Create the scroll view + LLScrollableContainerView *ret = new LLScrollableContainerView(name, rect, (LLPanel*)NULL, opaque, color); + + LLPanel* panelp = NULL; + + // Find a child panel to add + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + LLView *control = factory->createCtrlWidget(panelp, child); + if (control && control->isPanel()) + { + if (panelp) + { + llinfos << "Warning! Attempting to put multiple panels into a scrollable container view!" << llendl; + delete control; + } + else + { + panelp = (LLPanel*)control; + } + } + } + + if (panelp == NULL) + { + panelp = new LLPanel("dummy", LLRect::null, FALSE); + } + + ret->mScrolledView = panelp; + + return ret; +} + +///---------------------------------------------------------------------------- +/// Local function definitions +///---------------------------------------------------------------------------- diff --git a/indra/llui/llscrollcontainer.h b/indra/llui/llscrollcontainer.h new file mode 100644 index 0000000000..5f23be4628 --- /dev/null +++ b/indra/llui/llscrollcontainer.h @@ -0,0 +1,109 @@ +/** + * @file llscrollcontainer.h + * @brief LLScrollableContainerView class header file. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSCROLLCONTAINER_H +#define LL_LLSCROLLCONTAINER_H + +#include "lluictrl.h" +#ifndef LL_V4COLOR_H +#include "v4color.h" +#endif +#include "stdenums.h" +#include "llcoord.h" + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLScrollableContainerView +// +// A view meant to encapsulate a clipped region which is +// scrollable. It automatically takes care of pixel perfect scrolling +// and cliipping, as well as turning the scrollbars on or off based on +// the width and height of the view you're scrolling. +// +// This class is a decorator class. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLScrollbar; +class LLViewBorder; + + +class LLScrollableContainerView : public LLUICtrl +{ +public: + LLScrollableContainerView( const LLString& name, const LLRect& rect, + LLView* scrolled_view, BOOL is_opaque = FALSE, + const LLColor4& bg_color = LLColor4(0,0,0,0) ); + LLScrollableContainerView( const LLString& name, const LLRect& rect, + LLUICtrl* scrolled_ctrl, BOOL is_opaque = FALSE, + const LLColor4& bg_color = LLColor4(0,0,0,0) ); + virtual ~LLScrollableContainerView( void ); + + void init(); + + void setScrolledView(LLView* view) { mScrolledView = view; } + + virtual void setValue(const LLSD& value) { mInnerRect.setValue(value); } + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_SCROLL_CONTAINER; } + virtual LLString getWidgetTag() const { return LL_SCROLLABLE_CONTAINER_VIEW_TAG; } + + // scrollbar handlers + static void horizontalChange( S32 new_pos, LLScrollbar* sb, void* user_data ); + static void verticalChange( S32 new_pos, LLScrollbar* sb, void* user_data ); + + void calcVisibleSize( S32 *visible_width, S32 *visible_height, BOOL* show_h_scrollbar, BOOL* show_v_scrollbar ); + void calcVisibleSize( const LLRect& doc_rect, S32 *visible_width, S32 *visible_height, BOOL* show_h_scrollbar, BOOL* show_v_scrollbar ); + void setBorderVisible( BOOL b ); + + void scrollToShowRect( const LLRect& rect, const LLCoordGL& desired_offset ); + void setReserveScrollCorner( BOOL b ) { mReserveScrollCorner = b; } + const LLRect& getScrolledViewRect() { return mScrolledView->getRect(); } + void pageUp(S32 overlap = 0); + void pageDown(S32 overlap = 0); + void goToTop(); + void goToBottom(); + S32 getBorderWidth(); + + // LLView functionality + virtual void reshape(S32 width, S32 height, BOOL called_from_parent); + virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleScrollWheel( S32 x, S32 y, S32 clicks ); + virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + LLString& tooltip_msg); + + virtual BOOL handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect); + virtual void draw(); + + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + +protected: + // internal scrollbar handlers + virtual void scrollHorizontal( S32 new_pos ); + virtual void scrollVertical( S32 new_pos ); + void updateScroll(); + + // Note: vertical comes before horizontal because vertical + // scrollbars have priority for mouse and keyboard events. + enum { VERTICAL, HORIZONTAL, SCROLLBAR_COUNT }; + + LLScrollbar* mScrollbar[SCROLLBAR_COUNT]; + LLView* mScrolledView; + S32 mSize; + BOOL mIsOpaque; + LLColor4 mBackgroundColor; + LLRect mInnerRect; + LLViewBorder* mBorder; + BOOL mReserveScrollCorner; + BOOL mAutoScrolling; + F32 mAutoScrollRate; +}; + + +#endif // LL_LLSCROLLCONTAINER_H diff --git a/indra/llui/llscrollingpanellist.cpp b/indra/llui/llscrollingpanellist.cpp new file mode 100644 index 0000000000..a4d20edfe9 --- /dev/null +++ b/indra/llui/llscrollingpanellist.cpp @@ -0,0 +1,150 @@ +/** + * @file llscrollingpanellist.cpp + * @brief + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llstl.h" + +#include "llscrollingpanellist.h" + +///////////////////////////////////////////////////////////////////// +// LLScrollingPanelList + +// This could probably be integrated with LLScrollContainer -SJB + +void LLScrollingPanelList::clearPanels() +{ + deleteAllChildren(); + mPanelList.clear(); + reshape( 1, 1, FALSE ); +} + +void LLScrollingPanelList::addPanel( LLScrollingPanel* panel ) +{ + addChildAtEnd( panel ); + mPanelList.push_front( panel ); + + const S32 GAP_BETWEEN_PANELS = 6; + + // Resize this view + S32 total_height = 0; + S32 max_width = 0; + S32 cur_gap = 0; + for (std::deque::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel *childp = *iter; + total_height += childp->getRect().getHeight() + cur_gap; + max_width = llmax( max_width, childp->getRect().getWidth() ); + cur_gap = GAP_BETWEEN_PANELS; + } + reshape( max_width, total_height, FALSE ); + + // Reposition each of the child views + S32 cur_y = total_height; + for (std::deque::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel *childp = *iter; + cur_y -= childp->getRect().getHeight(); + childp->translate( -childp->getRect().mLeft, cur_y - childp->getRect().mBottom); + cur_y -= GAP_BETWEEN_PANELS; + } +} + +void LLScrollingPanelList::updatePanels(BOOL allow_modify) +{ + for (std::deque::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel *childp = *iter; + childp->updatePanel(allow_modify); + } +} + +void LLScrollingPanelList::updatePanelVisiblilty() +{ + // Determine visibility of children. + S32 BORDER_WIDTH = 2; // HACK + + LLRect parent_local_rect = getParent()->getRect(); + parent_local_rect.stretch( -BORDER_WIDTH ); + + LLRect parent_screen_rect; + getParent()->localPointToScreen( + BORDER_WIDTH, 0, + &parent_screen_rect.mLeft, &parent_screen_rect.mBottom ); + getParent()->localPointToScreen( + parent_local_rect.getWidth() - BORDER_WIDTH, parent_local_rect.getHeight() - BORDER_WIDTH, + &parent_screen_rect.mRight, &parent_screen_rect.mTop ); + + for (std::deque::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel *childp = *iter; + const LLRect& local_rect = childp->getRect(); + LLRect screen_rect; + childp->localPointToScreen( + 0, 0, + &screen_rect.mLeft, &screen_rect.mBottom ); + childp->localPointToScreen( + local_rect.getWidth(), local_rect.getHeight(), + &screen_rect.mRight, &screen_rect.mTop ); + + BOOL intersects = + ( (screen_rect.mRight > parent_screen_rect.mLeft) && (screen_rect.mLeft < parent_screen_rect.mRight) ) && + ( (screen_rect.mTop > parent_screen_rect.mBottom) && (screen_rect.mBottom < parent_screen_rect.mTop) ); + + childp->setVisible( intersects ); + } +} + +void LLScrollingPanelList::setValue(const LLSD& value) +{ + +} + +EWidgetType LLScrollingPanelList::getWidgetType() const +{ + return WIDGET_TYPE_SCROLLING_PANEL_LIST; +} + +LLString LLScrollingPanelList::getWidgetTag() const +{ + return LL_SCROLLING_PANEL_LIST_TAG; +} + +void LLScrollingPanelList::draw() +{ + if( getVisible() ) + { + updatePanelVisiblilty(); + } + LLUICtrl::draw(); +} + + +// static +LLView* LLScrollingPanelList::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("scrolling_panel_list"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + LLScrollingPanelList* scrolling_panel_list = new LLScrollingPanelList(name, rect); + scrolling_panel_list->initFromXML(node, parent); + return scrolling_panel_list; +} + +// virtual +LLXMLNodePtr LLScrollingPanelList::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + return node; +} diff --git a/indra/llui/llscrollingpanellist.h b/indra/llui/llscrollingpanellist.h new file mode 100644 index 0000000000..b5f20ce172 --- /dev/null +++ b/indra/llui/llscrollingpanellist.h @@ -0,0 +1,53 @@ +/** + * @file llscrollingpanellist.h + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include + +#include "llui.h" +#include "lluictrlfactory.h" +#include "llview.h" +#include "llpanel.h" + +// virtual class for scrolling panels +class LLScrollingPanel : public LLPanel +{ +public: + LLScrollingPanel(const LLString& name, const LLRect& rect) + : LLPanel(name, rect) + { + } + virtual void updatePanel(BOOL allow_modify) = 0; + +}; + +// A set of panels that are displayed in a vertical sequence inside a scroll container. +class LLScrollingPanelList : public LLUICtrl +{ +public: + LLScrollingPanelList(const LLString& name, const LLRect& rect) + : LLUICtrl(name, rect, TRUE, NULL, NULL, FOLLOWS_LEFT | FOLLOWS_BOTTOM ) {} + + virtual void setValue(const LLSD& value); + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual LLXMLNodePtr getXML(bool save_children) const; + + virtual void draw(); + + void clearPanels(); + void addPanel( LLScrollingPanel* panel ); + void updatePanels(BOOL allow_modify); + + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + +protected: + void updatePanelVisiblilty(); + +protected: + std::deque mPanelList; +}; diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp new file mode 100644 index 0000000000..5d11973b88 --- /dev/null +++ b/indra/llui/llscrolllistctrl.cpp @@ -0,0 +1,2673 @@ +/** + * @file llscrolllistctrl.cpp + * @brief LLScrollListCtrl base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include + +#include "linden_common.h" +#include "llstl.h" +#include "llboost.h" + +#include "llscrolllistctrl.h" + +#include "indra_constants.h" + +#include "llcheckboxctrl.h" +#include "llclipboard.h" +#include "llfocusmgr.h" +#include "llgl.h" +#include "llglheaders.h" +#include "llresmgr.h" +#include "llscrollbar.h" +#include "llstring.h" +#include "llui.h" +#include "lluictrlfactory.h" +#include "llwindow.h" +#include "llcontrol.h" +#include "llkeyboard.h" + +const S32 LIST_BORDER_PAD = 2; // white space inside the border and to the left of the scrollbar + +U32 LLScrollListCtrl::sSortColumn = 1; +BOOL LLScrollListCtrl::sSortAscending = TRUE; + +// local structures & classes. +struct SortScrollListItem +{ + SortScrollListItem(const S32 sort_col, BOOL sort_ascending) + { + mSortCol = sort_col; + sSortAscending = sort_ascending; + } + + bool operator()(const LLScrollListItem* i1, const LLScrollListItem* i2) + { + const LLScrollListCell *cell1; + const LLScrollListCell *cell2; + + cell1 = i1->getColumn(mSortCol); + cell2 = i2->getColumn(mSortCol); + + S32 order = 1; + if (!sSortAscending) + { + order = -1; + } + + BOOL retval = FALSE; + + if (cell1 && cell2) + { + retval = ((order * LLString::compareDict(cell1->getText(), cell2->getText())) < 0); + } + + return (retval ? TRUE : FALSE); + } + +protected: + S32 mSortCol; + S32 sSortAscending; +}; + + + +// +// LLScrollListIcon +// +LLScrollListIcon::LLScrollListIcon(LLImageGL* icon, S32 width, LLUUID image_id) : +mIcon(icon), mImageUUID(image_id.getString()) +{ + if (width) + { + mWidth = width; + } + else + { + mWidth = icon->getWidth(); + } +} + +LLScrollListIcon::~LLScrollListIcon() +{ +} + +// +// LLScrollListCheck +// +LLScrollListCheck::LLScrollListCheck(LLCheckBoxCtrl* check_box, S32 width) +{ + mCheckBox = check_box; + LLRect rect(mCheckBox->getRect()); + if (width) + { + + rect.mRight = rect.mLeft + width; + mCheckBox->setRect(rect); + mWidth = width; + } + else + { + mWidth = rect.getWidth(); //check_box->getWidth(); + } +} + +LLScrollListCheck::~LLScrollListCheck() +{ + delete mCheckBox; +} + +void LLScrollListCheck::drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const +{ + mCheckBox->draw(); + +} + +BOOL LLScrollListCheck::handleClick() +{ + if ( mCheckBox->getEnabled() ) + { + LLCheckBoxCtrl::onButtonPress(mCheckBox); + } + return TRUE; +} + +// +// LLScrollListText +// +U32 LLScrollListText::sCount = 0; + +LLScrollListText::LLScrollListText( const LLString& text, const LLFontGL* font, S32 width, U8 font_style, LLColor4& color, BOOL use_color, BOOL visible) +: mText( text ), + mFont( font ), + mFontStyle( font_style ), + mWidth( width ), + mVisible( visible ), + mHighlightChars( 0 ) +{ + if (use_color) + { + mColor = new LLColor4(); + mColor->setVec(color); + } + else + { + mColor = NULL; + } + + sCount++; + + // initialize rounded rect image + if (!mRoundedRectImage) + { + mRoundedRectImage = LLUI::sImageProvider->getUIImageByID(LLUUID(LLUI::sAssetsGroup->getString("rounded_square.tga"))); + } + + // Yes, that's four dots, because we want it to have a little + // padding, in proportion to the font size. + mEllipsisWidth = (S32)mFont->getWidth("...."); +} + +LLScrollListText::~LLScrollListText() +{ + sCount--; + delete mColor; +} + +void LLScrollListText::setText(const LLString& text) +{ + mText = text; +} + +void LLScrollListText::drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const +{ + // If the user has specified a small minimum width, use that. + if (mWidth > 0 && mWidth < width) + { + width = mWidth; + } + + const LLColor4* display_color; + if (mColor) + { + display_color = mColor; + } + else + { + display_color = &color; + } + + if (mHighlightChars > 0) + { + mRoundedRectImage->bind(); + glColor4fv(highlight_color.mV); + gl_segmented_rect_2d_tex(-2, + llround(mFont->getLineHeight()) + 1, + mFont->getWidth(mText.getString(), 0, mHighlightChars) + 1, + 1, + mRoundedRectImage->getWidth(), + mRoundedRectImage->getHeight(), + 16); + } + + // Try to draw the entire string + F32 right_x; + U32 string_chars = mText.length(); + U32 drawn_chars = mFont->render(mText.getWString(), 0, 0, 2, + *display_color, + LLFontGL::LEFT, + LLFontGL::BOTTOM, + mFontStyle, + string_chars, + width - mEllipsisWidth, + &right_x, FALSE); + + // If we didn't get the whole string, abbreviate + if (drawn_chars < string_chars && drawn_chars) + { + mFont->renderUTF8("...", 0, right_x, 0.f, color, LLFontGL::LEFT, LLFontGL::BOTTOM, mFontStyle, + S32_MAX, S32_MAX, NULL, FALSE); + } +} + + +LLScrollListItem::~LLScrollListItem() +{ + std::for_each(mColumns.begin(), mColumns.end(), DeletePointer()); +} + +BOOL LLScrollListItem::handleMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + S32 left = 0; + S32 right = 0; + S32 width = 0; + + std::vector::iterator iter = mColumns.begin(); + std::vector::iterator end = mColumns.end(); + for ( ; iter != end; ++iter) + { + width = (*iter)->getWidth(); + right += width; + if (left <= x && x < right ) + { + handled = (*iter)->handleClick(); + break; + } + + left += width; + } + return handled; +} + +void LLScrollListItem::setNumColumns(S32 columns) +{ + S32 prev_columns = mColumns.size(); + if (columns < prev_columns) + { + std::for_each(mColumns.begin()+columns, mColumns.end(), DeletePointer()); + } + + mColumns.resize(columns); + + for (S32 col = prev_columns; col < columns; ++col) + { + mColumns[col] = NULL; + } +} + +void LLScrollListItem::setColumn( S32 column, LLScrollListCell *cell ) +{ + if (column < (S32)mColumns.size()) + { + delete mColumns[column]; + mColumns[column] = cell; + } + else + { + llerrs << "LLScrollListItem::setColumn: bad column: " << column << llendl; + } +} + +LLString LLScrollListItem::getContentsCSV() +{ + LLString ret; + + S32 count = getNumColumns(); + for (S32 i=0; igetText(); + if (i < count-1) + { + ret += ", "; + } + } + + return ret; +} + +void LLScrollListItem::setEnabled(BOOL b) +{ + if (b != mEnabled) + { + std::vector::iterator iter = mColumns.begin(); + std::vector::iterator end = mColumns.end(); + for ( ; iter != end; ++iter) + { + (*iter)->setEnabled(b); + } + mEnabled = b; + } +} + +//--------------------------------------------------------------------------- +// LLScrollListCtrl +//--------------------------------------------------------------------------- + +LLScrollListCtrl::LLScrollListCtrl(const LLString& name, const LLRect& rect, + void (*commit_callback)(LLUICtrl* ctrl, void* userdata), + void* callback_user_data, + BOOL allow_multiple_selection, + BOOL show_border + ) + : LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data), + mLineHeight(0), + mScrollLines(0), + mPageLines(0), + mHeadingHeight(20), + mMaxSelectable(0), + mHeadingFont(NULL), + mAllowMultipleSelection( allow_multiple_selection ), + mAllowKeyboardMovement(TRUE), + mCommitOnKeyboardMovement(TRUE), + mCommitOnSelectionChange(FALSE), + mSelectionChanged(FALSE), + mCanSelect(TRUE), + mDisplayColumnButtons(FALSE), + mCollapseEmptyColumns(FALSE), + mIsPopup(FALSE), + mMaxItemCount(INT_MAX), + //mItemCount(0), + mBackgroundVisible( TRUE ), + mDrawStripes(TRUE), + mBgWriteableColor( LLUI::sColorsGroup->getColor( "ScrollBgWriteableColor" ) ), + mBgReadOnlyColor( LLUI::sColorsGroup->getColor( "ScrollBgReadOnlyColor" ) ), + mBgSelectedColor( LLUI::sColorsGroup->getColor("ScrollSelectedBGColor") ), + mBgStripeColor( LLUI::sColorsGroup->getColor("ScrollBGStripeColor") ), + mFgSelectedColor( LLUI::sColorsGroup->getColor("ScrollSelectedFGColor") ), + mFgUnselectedColor( LLUI::sColorsGroup->getColor("ScrollUnselectedColor") ), + mFgDisabledColor( LLUI::sColorsGroup->getColor("ScrollDisabledColor") ), + mHighlightedColor( LLUI::sColorsGroup->getColor("ScrollHighlightedColor") ), + mBorderThickness( 2 ), + mOnDoubleClickCallback( NULL ), + mOnMaximumSelectCallback( NULL ), + mHighlightedItem(-1), + mBorder(NULL), + mDefaultColumn("SIMPLE"), + mSearchColumn(0), + mNumDynamicWidthColumns(0), + mTotalStaticColumnWidth(0), + mDrewSelected(FALSE) +{ + mItemListRect.setOriginAndSize( + mBorderThickness + LIST_BORDER_PAD, + mBorderThickness + LIST_BORDER_PAD, + mRect.getWidth() - 2*( mBorderThickness + LIST_BORDER_PAD ) - SCROLLBAR_SIZE, + mRect.getHeight() - 2*( mBorderThickness + LIST_BORDER_PAD ) ); + + updateLineHeight(); + + mPageLines = mLineHeight? (mItemListRect.getHeight()) / mLineHeight : 0; + + // Init the scrollbar + LLRect scroll_rect; + scroll_rect.setOriginAndSize( + mRect.getWidth() - mBorderThickness - SCROLLBAR_SIZE, + mItemListRect.mBottom, + SCROLLBAR_SIZE, + mItemListRect.getHeight()); + mScrollbar = new LLScrollbar( "Scrollbar", scroll_rect, + LLScrollbar::VERTICAL, + getItemCount(), + mScrollLines, + mPageLines, + &LLScrollListCtrl::onScrollChange, this ); + mScrollbar->setFollowsRight(); + mScrollbar->setFollowsTop(); + mScrollbar->setFollowsBottom(); + mScrollbar->setEnabled( TRUE ); + mScrollbar->setVisible( TRUE ); + addChild(mScrollbar); + + // Border + if (show_border) + { + LLRect border_rect( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + mBorder = new LLViewBorder( "dlg border", border_rect, LLViewBorder::BEVEL_IN, LLViewBorder::STYLE_LINE, 1 ); + addChild(mBorder); + } + + mColumnPadding = 5; + + mLastSelected = NULL; +} + +LLScrollListCtrl::~LLScrollListCtrl() +{ + std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); + + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } +} + + +BOOL LLScrollListCtrl::setMaxItemCount(S32 max_count) +{ + if (max_count >= getItemCount()) + { + mMaxItemCount = max_count; + } + return (max_count == mMaxItemCount); +} + +S32 LLScrollListCtrl::isEmpty() const +{ + return mItemList.empty(); +} + +S32 LLScrollListCtrl::getItemCount() const +{ + return mItemList.size(); +} + +// virtual LLScrolListInterface function (was deleteAllItems) +void LLScrollListCtrl::clearRows() +{ + std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); + mItemList.clear(); + //mItemCount = 0; + + // Scroll the bar back up to the top. + mScrollbar->setDocParams(0, 0); + + mScrollLines = 0; + mLastSelected = NULL; +} + + +LLScrollListItem* LLScrollListCtrl::getFirstSelected() const +{ + item_list::const_iterator iter; + for(iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getSelected()) + { + return item; + } + } + return NULL; +} + +std::vector LLScrollListCtrl::getAllSelected() const +{ + std::vector ret; + item_list::const_iterator iter; + for(iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getSelected()) + { + ret.push_back(item); + } + } + return ret; +} + +S32 LLScrollListCtrl::getFirstSelectedIndex() +{ + S32 CurSelectedIndex = 0; + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getSelected()) + { + return CurSelectedIndex; + } + CurSelectedIndex++; + } + + return -1; +} + + +LLScrollListItem* LLScrollListCtrl::getFirstData() const +{ + if (mItemList.size() == 0) + { + return NULL; + } + return mItemList[0]; +} + +std::vector LLScrollListCtrl::getAllData() const +{ + std::vector ret; + item_list::const_iterator iter; + for(iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + ret.push_back(item); + } + return ret; +} + + +void LLScrollListCtrl::reshape( S32 width, S32 height, BOOL called_from_parent ) +{ + LLUICtrl::reshape( width, height, called_from_parent ); + + S32 heading_size = (mDisplayColumnButtons ? mHeadingHeight : 0); + + mItemListRect.setOriginAndSize( + mBorderThickness + LIST_BORDER_PAD, + mBorderThickness + LIST_BORDER_PAD, + mRect.getWidth() - 2*( mBorderThickness + LIST_BORDER_PAD ) - SCROLLBAR_SIZE, + mRect.getHeight() - 2*( mBorderThickness + LIST_BORDER_PAD ) - heading_size ); + + mPageLines = mLineHeight? mItemListRect.getHeight() / mLineHeight : 0; + mScrollbar->setVisible(mPageLines < getItemCount()); + mScrollbar->setPageSize( mPageLines ); + + updateColumns(); + updateColumnButtons(); +} + + +// Attempt to size the control to show all items. +// Do not make larger than width or height. +void LLScrollListCtrl::arrange(S32 max_width, S32 max_height) +{ + S32 height = mLineHeight * (getItemCount() + 1); + height = llmin( height, max_height ); + + S32 width = mRect.getWidth(); + + reshape( width, height ); +} + + +LLRect LLScrollListCtrl::getRequiredRect() +{ + S32 height = mLineHeight * (getItemCount() + 1); + S32 width = mRect.getWidth(); + + return LLRect(0, height, width, 0); +} + + +BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos ) +{ + BOOL not_too_big = getItemCount() < mMaxItemCount; + if (not_too_big) + { + switch( pos ) + { + case ADD_TOP: + mItemList.push_front(item); + break; + + case ADD_SORTED: + LLScrollListCtrl::sSortColumn = 0; + LLScrollListCtrl::sSortAscending = TRUE; + mItemList.push_back(item); + std::sort(mItemList.begin(), mItemList.end(), SortScrollListItem(sSortColumn, sSortAscending)); + break; + + case ADD_BOTTOM: + mItemList.push_back(item); + break; + + default: + llassert(0); + mItemList.push_back(item); + break; + } + + updateLineHeight(); + mPageLines = mLineHeight ? mItemListRect.getHeight() / mLineHeight : 0; + mScrollbar->setVisible(mPageLines < getItemCount()); + mScrollbar->setPageSize( mPageLines ); + + mScrollbar->setDocSize( getItemCount() ); + } + return not_too_big; +} + + +// Line height is the max height of all the cells in all the items. +void LLScrollListCtrl::updateLineHeight() +{ + const S32 ROW_PAD = 2; + + mLineHeight = 0; + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + S32 num_cols = itemp->getNumColumns(); + S32 i = 0; + for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i)) + { + mLineHeight = llmax( mLineHeight, cell->getHeight() + ROW_PAD ); + } + } +} + +void LLScrollListCtrl::updateColumns() +{ + mColumnsIndexed.resize(mColumns.size()); + + std::map::iterator column_itor; + for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor) + { + LLScrollListColumn *column = &column_itor->second; + if (column->mRelWidth >= 0) + { + column->mWidth = (S32)llround(column->mRelWidth*mItemListRect.getWidth()); + } + else if (column->mDynamicWidth) + { + column->mWidth = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns; + + } + mColumnsIndexed[column_itor->second.mIndex] = column; + } +} + +void LLScrollListCtrl::updateColumnButtons() +{ + std::map::iterator column_itor; + for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor) + { + LLScrollListColumn* column = &column_itor->second; + LLButton *button = column->mButton; + + if (button) + { + mColumnsIndexed[column->mIndex] = column; + + S32 top = mItemListRect.mTop; + S32 left = mItemListRect.mLeft; + { + std::map::iterator itor; + for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) + { + if (itor->second.mIndex < column->mIndex && + itor->second.mWidth > 0) + { + left += itor->second.mWidth + mColumnPadding; + } + } + } + S32 right = left+column->mWidth; + if (column->mIndex != (S32)mColumns.size()-1) + { + right += mColumnPadding; + } + LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top); + button->setRect(temp_rect); + button->setFont(mHeadingFont); + button->setVisible(mDisplayColumnButtons); + } + } +} + +void LLScrollListCtrl::setDisplayHeading(BOOL display) +{ + mDisplayColumnButtons = display; + + updateColumns(); + + setHeadingHeight(mHeadingHeight); +} + +void LLScrollListCtrl::setHeadingHeight(S32 heading_height) +{ + mHeadingHeight = heading_height; + + reshape(mRect.getWidth(), mRect.getHeight()); + + // Resize + mScrollbar->reshape(SCROLLBAR_SIZE, mItemListRect.getHeight()); + + updateColumnButtons(); +} + +void LLScrollListCtrl::setHeadingFont(const LLFontGL* heading_font) +{ + mHeadingFont = heading_font; + updateColumnButtons(); +} + +void LLScrollListCtrl::setCollapseEmptyColumns(BOOL collapse) +{ + mCollapseEmptyColumns = collapse; +} + +BOOL LLScrollListCtrl::selectFirstItem() +{ + BOOL success = FALSE; + + // our $%&@#$()^%#$()*^ iterators don't let us check against the first item inside out iteration + BOOL first_item = TRUE; + + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + if( first_item && itemp->getEnabled() ) + { + if (!itemp->getSelected()) + { + selectItem(itemp); + } + success = TRUE; + } + else + { + deselectItem(itemp); + } + first_item = FALSE; + } + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + return success; +} + + +BOOL LLScrollListCtrl::selectNthItem( S32 target_index ) +{ + // Deselects all other items + BOOL success = FALSE; + S32 index = 0; + + target_index = llclamp(target_index, 0, (S32)mItemList.size() - 1); + + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + if( target_index == index ) + { + if( itemp->getEnabled() ) + { + selectItem(itemp); + success = TRUE; + } + } + else + { + deselectItem(itemp); + } + index++; + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + mSearchString.clear(); + + return success; +} + + +void LLScrollListCtrl::swapWithNext(S32 index) +{ + if (index >= ((S32)mItemList.size() - 1)) + { + // At end of list, doesn't do anything + return; + } + LLScrollListItem *cur_itemp = mItemList[index]; + mItemList[index] = mItemList[index + 1]; + mItemList[index + 1] = cur_itemp; +} + + +void LLScrollListCtrl::swapWithPrevious(S32 index) +{ + if (index <= 0) + { + // At beginning of list, don't do anything + } + + LLScrollListItem *cur_itemp = mItemList[index]; + mItemList[index] = mItemList[index - 1]; + mItemList[index - 1] = cur_itemp; +} + + +void LLScrollListCtrl::deleteSingleItem(S32 target_index) +{ + if (target_index >= (S32)mItemList.size()) + { + return; + } + + LLScrollListItem *itemp; + itemp = mItemList[target_index]; + if (itemp == mLastSelected) + { + mLastSelected = NULL; + } + delete itemp; + mItemList.erase(mItemList.begin() + target_index); +} + +void LLScrollListCtrl::deleteSelectedItems() +{ + item_list::iterator iter; + for (iter = mItemList.begin(); iter < mItemList.end(); ) + { + LLScrollListItem* itemp = *iter; + if (itemp->getSelected()) + { + delete itemp; + iter = mItemList.erase(iter); + } + else + { + iter++; + } + } + mLastSelected = NULL; +} + +void LLScrollListCtrl::highlightNthItem(S32 target_index) +{ + if (mHighlightedItem != target_index) + { + mHighlightedItem = target_index; + } +} + +S32 LLScrollListCtrl::getItemIndex( LLScrollListItem* target_item ) +{ + S32 index = 0; + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + if (target_item == itemp) + { + return index; + } + index++; + } + return -1; +} + +S32 LLScrollListCtrl::getItemIndex( LLUUID& target_id ) +{ + S32 index = 0; + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + if (target_id == itemp->getUUID()) + { + return index; + } + index++; + } + return -1; +} + +void LLScrollListCtrl::selectPrevItem( BOOL extend_selection) +{ + LLScrollListItem* prev_item = NULL; + + if (!getFirstSelected()) + { + selectFirstItem(); + } + else + { + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* cur_item = *iter; + + if (cur_item->getSelected()) + { + if (prev_item) + { + selectItem(prev_item, !extend_selection); + } + else + { + reportInvalidInput(); + } + break; + } + + prev_item = cur_item; + } + } + + if ((mCommitOnSelectionChange || mCommitOnKeyboardMovement)) + { + commitIfChanged(); + } + + mSearchString.clear(); +} + + +void LLScrollListCtrl::selectNextItem( BOOL extend_selection) +{ + if (!getFirstSelected()) + { + selectFirstItem(); + } + else + { + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getSelected()) + { + if (++iter != mItemList.end()) + { + LLScrollListItem *next_item = *iter; + if (next_item) + { + selectItem(next_item, !extend_selection); + } + else + { + reportInvalidInput(); + } + } + break; + } + } + } + + if ((mCommitOnSelectionChange || mCommitOnKeyboardMovement)) + { + onCommit(); + } + + mSearchString.clear(); +} + + + +void LLScrollListCtrl::deselectAllItems(BOOL no_commit_on_change) +{ + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + deselectItem(item); + } + + if (mCommitOnSelectionChange && !no_commit_on_change) + { + commitIfChanged(); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// "Simple" interface: use this when you're creating a list that contains only unique strings, only +// one of which can be selected at a time. + +LLScrollListItem* LLScrollListCtrl::addSimpleItem(const LLString& item_text, EAddPosition pos, BOOL enabled) +{ + LLScrollListItem* item = NULL; + if (getItemCount() < mMaxItemCount) + { + // simple items have their LLSD data set to their label + item = new LLScrollListItem( LLSD(item_text) ); + item->setEnabled(enabled); + item->addColumn( item_text, gResMgr->getRes( LLFONT_SANSSERIF_SMALL ) ); + addItem( item, pos ); + } + return item; +} + + +// Selects first enabled item of the given name. +// Returns false if item not found. +BOOL LLScrollListCtrl::selectSimpleItem(const LLString& label, BOOL case_sensitive) +{ + //RN: assume no empty items + if (label.empty()) + { + return FALSE; + } + + LLString target_text = label; + if (!case_sensitive) + { + LLString::toLower(target_text); + } + + BOOL found = FALSE; + + item_list::iterator iter; + S32 index = 0; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + // Only select enabled items with matching names + LLString item_text = item->getColumn(0)->getText(); + if (!case_sensitive) + { + LLString::toLower(item_text); + } + BOOL select = !found && item->getEnabled() && item_text == target_text; + if (select) + { + selectItem(item); + } + found = found || select; + index++; + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + return found; +} + + +BOOL LLScrollListCtrl::selectSimpleItemByPrefix(const LLString& target, BOOL case_sensitive) +{ + return selectSimpleItemByPrefix(utf8str_to_wstring(target), case_sensitive); +} + +// Selects first enabled item that has a name where the name's first part matched the target string. +// Returns false if item not found. +BOOL LLScrollListCtrl::selectSimpleItemByPrefix(const LLWString& target, BOOL case_sensitive) +{ + BOOL found = FALSE; + + LLWString target_trimmed( target ); + S32 target_len = target_trimmed.size(); + + if( 0 == target_len ) + { + // Is "" a valid choice? + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + // Only select enabled items with matching names + LLScrollListCell* cellp = item->getColumn(mSearchColumn); + BOOL select = cellp ? item->getEnabled() && ('\0' == cellp->getText()[0]) : FALSE; + if (select) + { + selectItem(item); + found = TRUE; + break; + } + } + } + else + { + if (!case_sensitive) + { + // do comparisons in lower case + LLWString::toLower(target_trimmed); + } + + for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + + // Only select enabled items with matching names + LLScrollListCell* cellp = item->getColumn(mSearchColumn); + if (!cellp) + { + continue; + } + LLWString item_label = utf8str_to_wstring(cellp->getText()); + if (!case_sensitive) + { + LLWString::toLower(item_label); + } + + BOOL select = item->getEnabled() && !item_label.compare(0, target_len, target_trimmed); + + if (select) + { + selectItem(item); + found = TRUE; + break; + } + } + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + return found; +} + +const LLString& LLScrollListCtrl::getSimpleSelectedItem(S32 column) const +{ + LLScrollListItem* item; + + item = getFirstSelected(); + if (item) + { + return item->getColumn(column)->getText(); + } + + return LLString::null; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// "StringUUID" interface: use this when you're creating a list that contains non-unique strings each of which +// has an associated, unique UUID, and only one of which can be selected at a time. + +LLScrollListItem* LLScrollListCtrl::addStringUUIDItem(const LLString& item_text, const LLUUID& id, EAddPosition pos, BOOL enabled, S32 column_width) +{ + LLScrollListItem* item = NULL; + if (getItemCount() < mMaxItemCount) + { + item = new LLScrollListItem( enabled, NULL, id ); + item->addColumn(item_text, gResMgr->getRes(LLFONT_SANSSERIF_SMALL), column_width); + addItem( item, pos ); + } + return item; +} + +LLScrollListItem* LLScrollListCtrl::addSimpleItem(const LLString& item_text, LLSD sd, EAddPosition pos, BOOL enabled, S32 column_width) +{ + LLScrollListItem* item = NULL; + if (getItemCount() < mMaxItemCount) + { + item = new LLScrollListItem( sd ); + item->setEnabled(enabled); + item->addColumn(item_text, gResMgr->getRes(LLFONT_SANSSERIF_SMALL), column_width); + addItem( item, pos ); + } + return item; +} + + +// Select the line or lines that match this UUID +BOOL LLScrollListCtrl::selectByID( const LLUUID& id ) +{ + return selectByValue( LLSD(id) ); +} + +BOOL LLScrollListCtrl::setSelectedByValue(LLSD value, BOOL selected) +{ + BOOL found = FALSE; + + if (selected && !mAllowMultipleSelection) deselectAllItems(TRUE); + + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getEnabled() && (item->getValue().asString() == value.asString())) + { + if (selected) + { + selectItem(item); + } + else + { + deselectItem(item); + } + found = TRUE; + break; + } + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + return found; +} + +BOOL LLScrollListCtrl::isSelected(LLSD value) +{ + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getValue().asString() == value.asString()) + { + return item->getSelected(); + } + } + return FALSE; +} + +LLUUID LLScrollListCtrl::getStringUUIDSelectedItem() +{ + LLScrollListItem* item = getFirstSelected(); + + if (item) + { + return item->getUUID(); + } + + return LLUUID::null; +} + +LLSD LLScrollListCtrl::getSimpleSelectedValue() +{ + LLScrollListItem* item = getFirstSelected(); + + if (item) + { + return item->getValue(); + } + else + { + return LLSD(); + } +} + +void LLScrollListCtrl::drawItems() +{ + S32 x = mItemListRect.mLeft; + S32 y = mItemListRect.mTop - mLineHeight; + + S32 num_page_lines = mPageLines; + + LLRect item_rect; + + LLGLSUIDefault gls_ui; + + { + + S32 cur_x = x; + S32 cur_y = y; + + mDrewSelected = FALSE; + + S32 line = 0; + LLColor4 color; + S32 max_columns = 0; + + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + + item_rect.setOriginAndSize( + cur_x, + cur_y, + mScrollbar->getVisible() ? mItemListRect.getWidth() : mItemListRect.getWidth() + mScrollbar->getRect().getWidth(), + mLineHeight ); + + lldebugs << mItemListRect.getWidth() << llendl; + + if (item->getSelected()) + { + mDrewSelected = TRUE; + } + + max_columns = llmax(max_columns, item->getNumColumns()); + + LLRect bg_rect = item_rect; + // pad background rectangle to separate it from contents + bg_rect.stretch(LIST_BORDER_PAD, 0); + + if( mScrollLines <= line && line < mScrollLines + num_page_lines ) + { + if( item->getSelected() && mCanSelect) + { + // Draw background of selected item + LLGLSNoTexture no_texture; + glColor4fv(mBgSelectedColor.mV); + gl_rect_2d( bg_rect ); + + color = mFgSelectedColor; + } + else if (mHighlightedItem == line && mCanSelect) + { + LLGLSNoTexture no_texture; + glColor4fv(mHighlightedColor.mV); + gl_rect_2d( bg_rect ); + color = (item->getEnabled() ? mFgUnselectedColor : mFgDisabledColor); + } + else + { + color = (item->getEnabled() ? mFgUnselectedColor : mFgDisabledColor); + if (mDrawStripes && (line%2 == 0) && (max_columns > 1)) + { + LLGLSNoTexture no_texture; + glColor4fv(mBgStripeColor.mV); + gl_rect_2d( bg_rect ); + } + } + + S32 line_x = cur_x; + { + S32 num_cols = item->getNumColumns(); + S32 cur_col = 0; + S32 dynamic_width = 0; + S32 dynamic_remainder = 0; + if(mNumDynamicWidthColumns > 0) + { + dynamic_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns; + dynamic_remainder = (mItemListRect.getWidth() - mTotalStaticColumnWidth) % mNumDynamicWidthColumns; + } + for (LLScrollListCell* cell = item->getColumn(0); cur_col < num_cols; cell = item->getColumn(++cur_col)) + { + S32 cell_width = cell->getWidth(); + if(mColumnsIndexed.size() > (U32)cur_col && mColumnsIndexed[cur_col] && mColumnsIndexed[cur_col]->mDynamicWidth) + { + cell_width = dynamic_width + (--dynamic_remainder ? 1 : 0); + cell->setWidth(cell_width); + } + // Two ways a cell could be hidden + if (cell_width < 0 + || !cell->getVisible()) continue; + LLUI::pushMatrix(); + LLUI::translate((F32) cur_x, (F32) cur_y, 0.0f); + S32 space_left = mItemListRect.mRight - cur_x; + LLColor4 highlight_color = LLColor4::white; + F32 type_ahead_timeout = LLUI::sConfigGroup->getF32("TypeAheadTimeout"); + + highlight_color.mV[VALPHA] = clamp_rescale(mSearchTimer.getElapsedTimeF32(), type_ahead_timeout * 0.7f, type_ahead_timeout, 0.4f, 0.f); + cell->drawToWidth( space_left, color, highlight_color ); + LLUI::popMatrix(); + + cur_x += cell_width + mColumnPadding; + + } + } + cur_x = line_x; + cur_y -= mLineHeight; + } + line++; + } + } +} + + +void LLScrollListCtrl::draw() +{ + if( getVisible() ) + { + LLRect background(0, mRect.getHeight(), mRect.getWidth(), 0); + // Draw background + if (mBackgroundVisible) + { + LLGLSNoTexture no_texture; + glColor4fv( getEnabled() ? mBgWriteableColor.mV : mBgReadOnlyColor.mV ); + gl_rect_2d(background); + } + + drawItems(); + + if (mBorder) + { + mBorder->setKeyboardFocusHighlight(gFocusMgr.getKeyboardFocus() == this); + } + + LLUICtrl::draw(); + } +} + +void LLScrollListCtrl::setEnabled(BOOL enabled) +{ + mCanSelect = enabled; + setTabStop(enabled); + mScrollbar->setTabStop(!enabled && mScrollbar->getPageSize() < mScrollbar->getDocSize()); +} + +BOOL LLScrollListCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + BOOL handled = FALSE; + // Pretend the mouse is over the scrollbar + handled = mScrollbar->handleScrollWheel( 0, 0, clicks ); + return handled; +} + + +BOOL LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; + + if( !handled && mCanSelect) + { + LLScrollListItem* hit_item = hitItem(x, y); + if( hit_item ) + { + if( mAllowMultipleSelection ) + { + if (mask & MASK_SHIFT) + { + if (mLastSelected == NULL) + { + selectItem(hit_item); + } + else + { + // Select everthing between mLastSelected and hit_item + bool selecting = false; + item_list::iterator itor; + // If we multiselect backwards, we'll stomp on mLastSelected, + // meaning that we never stop selecting until hitting max or + // the end of the list. + LLScrollListItem* lastSelected = mLastSelected; + for (itor = mItemList.begin(); itor != mItemList.end(); ++itor) + { + if(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable) + { + if(mOnMaximumSelectCallback) + { + mOnMaximumSelectCallback(mCallbackUserData); + } + break; + } + LLScrollListItem *item = *itor; + if (item == hit_item || item == lastSelected) + { + selectItem(item, FALSE); + selecting = !selecting; + } + if (selecting) + { + selectItem(item, FALSE); + } + } + } + } + else if (mask & MASK_CONTROL) + { + if (hit_item->getSelected()) + { + deselectItem(hit_item); + } + else + { + if(!(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable)) + { + selectItem(hit_item, FALSE); + } + else + { + if(mOnMaximumSelectCallback) + { + mOnMaximumSelectCallback(mCallbackUserData); + } + } + } + } + else + { + deselectAllItems(TRUE); + selectItem(hit_item); + } + } + else + { + selectItem(hit_item); + } + + hit_item->handleMouseDown(x - mBorderThickness - LIST_BORDER_PAD, + 1, mask); + // always commit on mousedown + onCommit(); + mSelectionChanged = FALSE; + + // clear search string on mouse operations + mSearchString.clear(); + } + else + { + mLastSelected = NULL; + } + } + + gFocusMgr.setKeyboardFocus(this, NULL); + + return TRUE; +} + +BOOL LLScrollListCtrl::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + //BOOL handled = FALSE; + if(getVisible()) + { + // Offer the click to the children, even if we aren't enabled + // so the scroll bars will work. + if (NULL == LLView::childrenHandleDoubleClick(x, y, mask)) + { + if( mCanSelect && mOnDoubleClickCallback ) + { + mOnDoubleClickCallback( mCallbackUserData ); + } + } + } + return TRUE; +} + +LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y ) +{ + // Excludes disabled items. + LLScrollListItem* hit_item = NULL; + + LLRect item_rect; + item_rect.setLeftTopAndSize( + mItemListRect.mLeft, + mItemListRect.mTop, + mItemListRect.getWidth(), + mLineHeight ); + + int num_page_lines = mPageLines; + + S32 line = 0; + item_list::iterator iter; + for(iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if( mScrollLines <= line && line < mScrollLines + num_page_lines ) + { + if( item->getEnabled() && item_rect.pointInRect( x, y ) ) + { + hit_item = item; + break; + } + + item_rect.translate(0, -mLineHeight); + } + line++; + } + + return hit_item; +} + + +BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask) +{ + BOOL handled = FALSE; + + if(getVisible()) + { + if (mCanSelect) + { + LLScrollListItem* item = hitItem(x, y); + if (item) + { + highlightNthItem(getItemIndex(item)); + } + else + { + highlightNthItem(-1); + } + } + + handled = LLView::handleHover( x, y, mask ); + + if( !handled ) + { + // Opaque + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; + handled = TRUE; + } + } + return handled; +} + + +BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask, BOOL called_from_parent ) +{ + BOOL handled = FALSE; + + // not called from parent means we have keyboard focus or a child does + if (mCanSelect && !called_from_parent) + { + // Ignore capslock + mask = mask; + + if (mask == MASK_NONE) + { + switch(key) + { + case KEY_UP: + if (mAllowKeyboardMovement || hasFocus()) + { + // commit implicit in call + selectPrevItem(FALSE); + scrollToShowSelected(); + handled = TRUE; + } + break; + case KEY_DOWN: + if (mAllowKeyboardMovement || hasFocus()) + { + // commit implicit in call + selectNextItem(FALSE); + scrollToShowSelected(); + handled = TRUE; + } + break; + case KEY_PAGE_UP: + if (mAllowKeyboardMovement || hasFocus()) + { + selectNthItem(getFirstSelectedIndex() - (mScrollbar->getPageSize() - 1)); + scrollToShowSelected(); + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + handled = TRUE; + } + break; + case KEY_PAGE_DOWN: + if (mAllowKeyboardMovement || hasFocus()) + { + selectNthItem(getFirstSelectedIndex() + (mScrollbar->getPageSize() - 1)); + scrollToShowSelected(); + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + handled = TRUE; + } + break; + case KEY_HOME: + if (mAllowKeyboardMovement || hasFocus()) + { + selectFirstItem(); + scrollToShowSelected(); + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + handled = TRUE; + } + break; + case KEY_END: + if (mAllowKeyboardMovement || hasFocus()) + { + selectNthItem(getItemCount() - 1); + scrollToShowSelected(); + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + handled = TRUE; + } + break; + case KEY_RETURN: + // JC - Special case: Only claim to have handled it + // if we're the special non-commit-on-move + // type. AND we are visible + if (!mCommitOnKeyboardMovement && mask == MASK_NONE && getVisible()) + { + onCommit(); + mSearchString.clear(); + handled = TRUE; + } + break; + case KEY_BACKSPACE: + mSearchTimer.reset(); + if (mSearchString.size()) + { + mSearchString.erase(mSearchString.size() - 1, 1); + } + if (mSearchString.empty()) + { + if (getFirstSelected()) + { + LLScrollListCell* cellp = getFirstSelected()->getColumn(mSearchColumn); + if (cellp) + { + cellp->highlightText(0); + } + } + } + else if (selectSimpleItemByPrefix(wstring_to_utf8str(mSearchString), FALSE)) + { + // update search string only on successful match + mSearchTimer.reset(); + + // highlight current search on matching item + LLScrollListCell* cellp = getFirstSelected()->getColumn(mSearchColumn); + if (cellp) + { + cellp->highlightText(mSearchString.size()); + } + + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + } + break; + default: + break; + } + } + // TODO: multiple: shift-up, shift-down, shift-home, shift-end, select all + } + + return handled; +} + +BOOL LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent) +{ + if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL + { + return FALSE; + } + + // perform incremental search based on keyboard input + if (mSearchTimer.getElapsedTimeF32() > LLUI::sConfigGroup->getF32("TypeAheadTimeout")) + { + mSearchString.clear(); + } + + // type ahead search is case insensitive + uni_char = LLStringOps::toLower((llwchar)uni_char); + + if (selectSimpleItemByPrefix(wstring_to_utf8str(mSearchString + (llwchar)uni_char), FALSE)) + { + // update search string only on successful match + mSearchString += uni_char; + mSearchTimer.reset(); + + // highlight current search on matching item + LLScrollListCell* cellp = getFirstSelected()->getColumn(mSearchColumn); + if (cellp) + { + cellp->highlightText(mSearchString.size()); + } + + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + } + // handle iterating over same starting character + else if (isRepeatedChars(mSearchString + (llwchar)uni_char) && !mItemList.empty()) + { + // start from last selected item, in case we previously had a successful match against + // duplicated characters ('AA' matches 'Aaron') + item_list::iterator start_iter = mItemList.begin(); + S32 first_selected = getFirstSelectedIndex(); + + // if we have a selection (> -1) then point iterator at the selected item + if (first_selected > 0) + { + // point iterator to first selected item + start_iter += first_selected; + } + + // start search at first item after current selection + item_list::iterator iter = start_iter; + ++iter; + if (iter == mItemList.end()) + { + iter = mItemList.begin(); + } + + // loop around once, back to previous selection + while(iter != start_iter) + { + LLScrollListItem* item = *iter; + + LLScrollListCell* cellp = item->getColumn(mSearchColumn); + if (cellp) + { + // Only select enabled items with matching first characters + LLWString item_label = utf8str_to_wstring(cellp->getText()); + if (item->getEnabled() && LLStringOps::toLower(item_label[0]) == uni_char) + { + selectItem(item); + cellp->highlightText(1); + mSearchTimer.reset(); + + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + + break; + } + } + + ++iter; + if (iter == mItemList.end()) + { + iter = mItemList.begin(); + } + } + } + + // make sure selected item is on screen + scrollToShowSelected(); + return TRUE; +} + + +void LLScrollListCtrl::reportInvalidInput() +{ + make_ui_sound("UISndBadKeystroke"); +} + +BOOL LLScrollListCtrl::isRepeatedChars(const LLWString& string) const +{ + if (string.empty()) + { + return FALSE; + } + + llwchar first_char = string[0]; + + for (U32 i = 0; i < string.size(); i++) + { + if (string[i] != first_char) + { + return FALSE; + } + } + + return TRUE; +} + +void LLScrollListCtrl::selectItem(LLScrollListItem* itemp, BOOL select_single_item) +{ + if (!itemp) return; + + if (!itemp->getSelected()) + { + if (mLastSelected) + { + LLScrollListCell* cellp = mLastSelected->getColumn(mSearchColumn); + if (cellp) + { + cellp->highlightText(0); + } + } + if (select_single_item) + { + deselectAllItems(TRUE); + } + itemp->setSelected(TRUE); + mLastSelected = itemp; + mSelectionChanged = TRUE; + } +} + +void LLScrollListCtrl::deselectItem(LLScrollListItem* itemp) +{ + if (!itemp) return; + + if (itemp->getSelected()) + { + if (mLastSelected == itemp) + { + mLastSelected = NULL; + } + + itemp->setSelected(FALSE); + LLScrollListCell* cellp = itemp->getColumn(mSearchColumn); + if (cellp) + { + cellp->highlightText(0); + } + mSelectionChanged = TRUE; + } +} + +void LLScrollListCtrl::commitIfChanged() +{ + if (mSelectionChanged) + { + mSelectionChanged = FALSE; + onCommit(); + } +} + +// Called by scrollbar +//static +void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar, void* userdata ) +{ + LLScrollListCtrl* self = (LLScrollListCtrl*) userdata; + self->mScrollLines = new_pos; +} + + +// First column is column 0 +void LLScrollListCtrl::sortByColumn(U32 column, BOOL ascending) +{ + LLScrollListCtrl::sSortColumn = column; + LLScrollListCtrl::sSortAscending = ascending; + std::sort(mItemList.begin(), mItemList.end(), SortScrollListItem(sSortColumn, sSortAscending)); +} + +void LLScrollListCtrl::sortByColumn(LLString name, BOOL ascending) +{ + std::map::iterator itor = mColumns.find(name); + if (itor != mColumns.end()) + { + sortByColumn((*itor).second.mIndex, ascending); + } +} + +S32 LLScrollListCtrl::getScrollPos() +{ + return mScrollbar->getDocPos(); +} + + +void LLScrollListCtrl::setScrollPos( S32 pos ) +{ + mScrollbar->setDocPos( pos ); + + onScrollChange(mScrollbar->getDocPos(), mScrollbar, this); +} + + +void LLScrollListCtrl::scrollToShowSelected() +{ + S32 index = getFirstSelectedIndex(); + if (index < 0) + { + return; + } + + LLScrollListItem* item = mItemList[index]; + if (!item) + { + // I don't THINK this should ever happen. + return; + } + + S32 lowest = mScrollLines; + S32 highest = mScrollLines + mPageLines; + + if (index < lowest) + { + // need to scroll to show item + setScrollPos(index); + } + else if (highest <= index) + { + setScrollPos(index - mPageLines + 1); + } +} + +// virtual +LLXMLNodePtr LLScrollListCtrl::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + // Attributes + + node->createChild("multi_select", TRUE)->setBoolValue(mAllowMultipleSelection); + + node->createChild("draw_border", TRUE)->setBoolValue((mBorder != NULL)); + + node->createChild("draw_heading", TRUE)->setBoolValue(mDisplayColumnButtons); + + node->createChild("background_visible", TRUE)->setBoolValue(mBackgroundVisible); + + node->createChild("draw_stripes", TRUE)->setBoolValue(mDrawStripes); + + node->createChild("column_padding", TRUE)->setIntValue(mColumnPadding); + + addColorXML(node, mBgWriteableColor, "bg_writeable_color", "ScrollBgWriteableColor"); + addColorXML(node, mBgReadOnlyColor, "bg_read_only_color", "ScrollBgReadOnlyColor"); + addColorXML(node, mBgSelectedColor, "bg_selected_color", "ScrollSelectedBGColor"); + addColorXML(node, mBgStripeColor, "bg_stripe_color", "ScrollBGStripeColor"); + addColorXML(node, mFgSelectedColor, "fg_selected_color", "ScrollSelectedFGColor"); + addColorXML(node, mFgUnselectedColor, "fg_unselected_color", "ScrollUnselectedColor"); + addColorXML(node, mFgDisabledColor, "fg_disable_color", "ScrollDisabledColor"); + addColorXML(node, mHighlightedColor, "highlighted_color", "ScrollHighlightedColor"); + + // Contents + + std::map::const_iterator itor; + std::vector sorted_list; + sorted_list.resize(mColumns.size()); + for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) + { + sorted_list[itor->second.mIndex] = &itor->second; + } + + std::vector::iterator itor2; + for (itor2 = sorted_list.begin(); itor2 != sorted_list.end(); ++itor2) + { + LLXMLNodePtr child_node = node->createChild("column", FALSE); + const LLScrollListColumn *column = *itor2; + + child_node->createChild("name", TRUE)->setStringValue(column->mName); + child_node->createChild("label", TRUE)->setStringValue(column->mLabel); + child_node->createChild("width", TRUE)->setIntValue(column->mWidth); + } + + return node; +} + +void LLScrollListCtrl::setScrollListParameters(LLXMLNodePtr node) +{ + // James: This is not a good way to do colors. We need a central "UI style" + // manager that sets the colors for ALL scroll lists, buttons, etc. + + LLColor4 color; + if(node->hasAttribute("fg_unselected_color")) + { + LLUICtrlFactory::getAttributeColor(node,"fg_unselected_color", color); + setFgUnselectedColor(color); + } + if(node->hasAttribute("fg_selected_color")) + { + LLUICtrlFactory::getAttributeColor(node,"fg_selected_color", color); + setFgSelectedColor(color); + } + if(node->hasAttribute("bg_selected_color")) + { + LLUICtrlFactory::getAttributeColor(node,"bg_selected_color", color); + setBgSelectedColor(color); + } + if(node->hasAttribute("fg_disable_color")) + { + LLUICtrlFactory::getAttributeColor(node,"fg_disable_color", color); + setFgDisableColor(color); + } + if(node->hasAttribute("bg_writeable_color")) + { + LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color); + setBgWriteableColor(color); + } + if(node->hasAttribute("bg_read_only_color")) + { + LLUICtrlFactory::getAttributeColor(node,"bg_read_only_color", color); + setReadOnlyBgColor(color); + } + if (LLUICtrlFactory::getAttributeColor(node,"bg_stripe_color", color)) + { + setBgStripeColor(color); + } + if (LLUICtrlFactory::getAttributeColor(node,"highlighted_color", color)) + { + setHighlightedColor(color); + } + + if(node->hasAttribute("background_visible")) + { + BOOL background_visible; + node->getAttributeBOOL("background_visible", background_visible); + setBackgroundVisible(background_visible); + } + + if(node->hasAttribute("draw_stripes")) + { + BOOL draw_stripes; + node->getAttributeBOOL("draw_stripes", draw_stripes); + setDrawStripes(draw_stripes); + } + + if(node->hasAttribute("column_padding")) + { + S32 column_padding; + node->getAttributeS32("column_padding", column_padding); + setColumnPadding(column_padding); + } +} + +// static +LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("scroll_list"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + BOOL multi_select = FALSE; + node->getAttributeBOOL("multi_select", multi_select); + + BOOL draw_border = TRUE; + node->getAttributeBOOL("draw_border", draw_border); + + BOOL draw_heading = FALSE; + node->getAttributeBOOL("draw_heading", draw_heading); + + BOOL collapse_empty_columns = FALSE; + node->getAttributeBOOL("collapse_empty_columns", collapse_empty_columns); + + S32 search_column = 0; + node->getAttributeS32("search_column", search_column); + + LLUICtrlCallback callback = NULL; + + LLScrollListCtrl* scroll_list = new LLScrollListCtrl( + name, + rect, + callback, + NULL, + multi_select, + draw_border); + + scroll_list->setDisplayHeading(draw_heading); + if (node->hasAttribute("heading_height")) + { + S32 heading_height; + node->getAttributeS32("heading_height", heading_height); + scroll_list->setHeadingHeight(heading_height); + } + if (node->hasAttribute("heading_font")) + { + LLString heading_font(""); + node->getAttributeString("heading_font", heading_font); + LLFontGL* gl_font = LLFontGL::fontFromName(heading_font.c_str()); + scroll_list->setHeadingFont(gl_font); + } + scroll_list->setCollapseEmptyColumns(collapse_empty_columns); + + scroll_list->setScrollListParameters(node); + + scroll_list->initFromXML(node, parent); + + scroll_list->setSearchColumn(search_column); + + LLSD columns; + S32 index = 0; + LLXMLNodePtr child; + S32 total_static = 0, num_dynamic = 0; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + if (child->hasName("column")) + { + LLString labelname(""); + child->getAttributeString("label", labelname); + + LLString columnname(labelname); + child->getAttributeString("name", columnname); + + LLString sortname(columnname); + child->getAttributeString("sort", sortname); + + LLString imagename; + child->getAttributeString("image", imagename); + + BOOL columndynamicwidth = FALSE; + child->getAttributeBOOL("dynamicwidth", columndynamicwidth); + + S32 columnwidth = -1; + child->getAttributeS32("width", columnwidth); + + if(!columndynamicwidth) total_static += columnwidth; + else ++num_dynamic; + + F32 columnrelwidth = 0.f; + child->getAttributeF32("relwidth", columnrelwidth); + + + columns[index]["name"] = columnname; + columns[index]["sort"] = sortname; + columns[index]["image"] = imagename; + columns[index]["label"] = labelname; + columns[index]["width"] = columnwidth; + columns[index]["relwidth"] = columnrelwidth; + columns[index]["dynamicwidth"] = columndynamicwidth; + index++; + } + } + scroll_list->setNumDynamicColumns(num_dynamic); + scroll_list->setTotalStaticColumnWidth(total_static); + scroll_list->setColumnHeadings(columns); + + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + if (child->hasName("row")) + { + LLUUID id; + child->getAttributeUUID("id", id); + + LLSD row; + + row["id"] = id; + + S32 column_idx = 0; + LLXMLNodePtr row_child; + for (row_child = child->getFirstChild(); row_child.notNull(); row_child = row_child->getNextSibling()) + { + if (row_child->hasName("column")) + { + LLString value = row_child->getTextContents(); + + LLString columnname(""); + row_child->getAttributeString("name", columnname); + + LLString font(""); + row_child->getAttributeString("font", font); + + LLString font_style(""); + row_child->getAttributeString("font-style", font_style); + + row["columns"][column_idx]["column"] = columnname; + row["columns"][column_idx]["value"] = value; + row["columns"][column_idx]["font"] = font; + row["columns"][column_idx]["font-style"] = font_style; + column_idx++; + } + } + scroll_list->addElement(row); + } + } + + LLString contents = node->getTextContents(); + if (!contents.empty()) + { + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("\t\n"); + tokenizer tokens(contents, sep); + tokenizer::iterator token_iter = tokens.begin(); + + while(token_iter != tokens.end()) + { + const char* line = token_iter->c_str(); + scroll_list->addSimpleItem(line); + ++token_iter; + } + } + + return scroll_list; +} + +// LLEditMenuHandler functions + +// virtual +void LLScrollListCtrl::copy() +{ + LLString buffer; + + std::vector items = getAllSelected(); + std::vector::iterator itor; + for (itor = items.begin(); itor != items.end(); ++itor) + { + buffer += (*itor)->getContentsCSV() + "\n"; + } + gClipboard.copyFromSubstring(utf8str_to_wstring(buffer), 0, buffer.length()); +} + +// virtual +BOOL LLScrollListCtrl::canCopy() +{ + return (getFirstSelected() != NULL); +} + +// virtual +void LLScrollListCtrl::cut() +{ + copy(); + doDelete(); +} + +// virtual +BOOL LLScrollListCtrl::canCut() +{ + return canCopy() && canDoDelete(); +} + +// virtual +void LLScrollListCtrl::doDelete() +{ + // Not yet implemented +} + +// virtual +BOOL LLScrollListCtrl::canDoDelete() +{ + // Not yet implemented + return FALSE; +} + +// virtual +void LLScrollListCtrl::selectAll() +{ + // Deselects all other items + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + if( itemp->getEnabled() ) + { + selectItem(itemp, FALSE); + } + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } +} + +// virtual +BOOL LLScrollListCtrl::canSelectAll() +{ + return getCanSelect() && mAllowMultipleSelection && !(mMaxSelectable > 0 && mItemList.size() > mMaxSelectable); +} + +// virtual +void LLScrollListCtrl::deselect() +{ + deselectAllItems(); +} + +// virtual +BOOL LLScrollListCtrl::canDeselect() +{ + return getCanSelect(); +} + +void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos) +{ + LLString name = column["name"].asString(); + if (mColumns.size() == 0) + { + mDefaultColumn = 0; + } + if (mColumns.find(name) == mColumns.end()) + { + // Add column + mColumns[name] = LLScrollListColumn(column); + LLScrollListColumn* new_column = &mColumns[name]; + new_column->mParentCtrl = this; + new_column->mIndex = mColumns.size()-1; + + // Add button + if (new_column->mWidth > 0 || new_column->mRelWidth > 0 || new_column->mDynamicWidth) + { + if (new_column->mRelWidth >= 0) + { + new_column->mWidth = (S32)llround(new_column->mRelWidth*mItemListRect.getWidth()); + } + else if(new_column->mDynamicWidth) + { + new_column->mWidth = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns; + } + S32 top = mItemListRect.mTop; + S32 left = mItemListRect.mLeft; + { + std::map::iterator itor; + for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) + { + if (itor->second.mIndex < new_column->mIndex && + itor->second.mWidth > 0) + { + left += itor->second.mWidth + mColumnPadding; + } + } + } + LLString button_name = "btn_" + name; + S32 right = left+new_column->mWidth; + if (new_column->mIndex != (S32)mColumns.size()-1) + { + right += mColumnPadding; + } + LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top); + new_column->mButton = new LLSquareButton(button_name, temp_rect, "", mHeadingFont, "", onClickColumn, NULL); + if(column["image"].asString() != "") + { + //new_column->mButton->setScaleImage(false); + new_column->mButton->setImageSelected(column["image"].asString()); + new_column->mButton->setImageUnselected(column["image"].asString()); + } + else + { + new_column->mButton->setLabelSelected(new_column->mLabel); + new_column->mButton->setLabelUnselected(new_column->mLabel); + } + //RN: although it might be useful to change sort order with the keyboard, + // mixing tab stops on child items along with the parent item is not supported yet + new_column->mButton->setTabStop(FALSE); + addChild(new_column->mButton); + new_column->mButton->setVisible(mDisplayColumnButtons); + + + // Move scroll to front + removeChild(mScrollbar); + addChild(mScrollbar); + + new_column->mButton->setCallbackUserData(new_column); + } + } + updateColumns(); +} + +// static +void LLScrollListCtrl::onClickColumn(void *userdata) +{ + LLScrollListColumn *info = (LLScrollListColumn*)userdata; + if (!info) return; + + U32 column_index = info->mIndex; + + LLScrollListColumn* column = info->mParentCtrl->mColumnsIndexed[info->mIndex]; + if (column->mSortingColumn != column->mName) + { + if (info->mParentCtrl->mColumns.find(column->mSortingColumn) != info->mParentCtrl->mColumns.end()) + { + LLScrollListColumn& info_redir = info->mParentCtrl->mColumns[column->mSortingColumn]; + column_index = info_redir.mIndex; + } + } + + // TomY TODO: shouldn't these be non-static members? + bool ascending = true; + if (column_index == LLScrollListCtrl::sSortColumn) + { + ascending = !LLScrollListCtrl::sSortAscending; + } + + info->mParentCtrl->sortByColumn(column_index, ascending); +} + +void LLScrollListCtrl::clearColumns() +{ + std::map::iterator itor; + for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) + { + LLButton *button = itor->second.mButton; + if (button) + { + removeChild(button); + delete button; + } + } + mColumns.clear(); +} + +void LLScrollListCtrl::setColumnLabel(const LLString& column, const LLString& label) +{ + std::map::iterator itor = mColumns.find(column); + if (itor != mColumns.end()) + { + itor->second.mLabel = label; + if (itor->second.mButton) + { + itor->second.mButton->setLabelSelected(label); + itor->second.mButton->setLabelUnselected(label); + } + } +} + +void LLScrollListCtrl::setColumnHeadings(LLSD headings) +{ + mColumns.clear(); + LLSD::array_const_iterator itor; + for (itor = headings.beginArray(); itor != headings.endArray(); ++itor) + { + addColumn(*itor); + } +} + +LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition pos, void* userdata) +{ + // ID + LLSD id = value["id"]; + + LLScrollListItem *new_item = new LLScrollListItem(id, userdata); + if (value.has("enabled")) + { + new_item->setEnabled( value["enabled"].asBoolean() ); + } + + new_item->setNumColumns(mColumns.size()); + + // Add any columns we don't already have + LLSD columns = value["columns"]; + LLSD::array_const_iterator itor; + for (itor = columns.beginArray(); itor != columns.endArray(); ++itor) + { + LLString column = (*itor)["column"].asString(); + + if (mColumns.size() == 0) + { + mDefaultColumn = 0; + } + std::map::iterator column_itor = mColumns.find(column); + if (column_itor == mColumns.end()) + { + LLSD new_column; + new_column["name"] = column; + new_column["label"] = column; + new_column["width"] = 0; + addColumn(new_column); + column_itor = mColumns.find(column); + new_item->setNumColumns(mColumns.size()); + } + + S32 index = column_itor->second.mIndex; + S32 width = column_itor->second.mWidth; + + LLSD value = (*itor)["value"]; + LLString fontname = (*itor)["font"].asString(); + LLString fontstyle = (*itor)["font-style"].asString(); + LLString type = (*itor)["type"].asString(); + + const LLFontGL *font = gResMgr->getRes(fontname); + if (!font) + { + font = gResMgr->getRes( LLFONT_SANSSERIF_SMALL ); + } + U8 font_style = LLFontGL::getStyleFromString(fontstyle); + + if (type == "icon") + { + LLUUID image_id = value.asUUID(); + LLImageGL* icon = LLUI::sImageProvider->getUIImageByID(image_id); + new_item->setColumn(index, new LLScrollListIcon(icon, width, image_id)); + } + else if (type == "checkbox") + { + LLCheckBoxCtrl* ctrl = new LLCheckBoxCtrl(value.asString(), + LLRect(0, 0, width, width), "label"); + new_item->setColumn(index, new LLScrollListCheck(ctrl,width)); + } + else + { + new_item->setColumn(index, new LLScrollListText(value.asString(), font, width, font_style)); + } + } + + S32 num_columns = mColumns.size(); + for (S32 column = 0; column < num_columns; ++column) + { + if (new_item->getColumn(column) == NULL) + { + LLScrollListColumn* column_ptr = mColumnsIndexed[column]; + new_item->setColumn(column, new LLScrollListText("", gResMgr->getRes( LLFONT_SANSSERIF_SMALL ), column_ptr->mWidth, LLFontGL::NORMAL)); + } + } + + addItem(new_item, pos); + + return new_item; +} + +LLScrollListItem* LLScrollListCtrl::addSimpleElement(const LLString& value, EAddPosition pos, const LLSD& id) +{ + LLSD entry_id = id; + + if (id.isUndefined()) + { + entry_id = value; + } + + LLScrollListItem *new_item = new LLScrollListItem(entry_id); + + const LLFontGL *font = gResMgr->getRes( LLFONT_SANSSERIF_SMALL ); + + new_item->addColumn(value, font, getRect().getWidth()); + + addItem(new_item, pos); + return new_item; +} + +void LLScrollListCtrl::setValue(const LLSD& value ) +{ + LLSD::array_const_iterator itor; + for (itor = value.beginArray(); itor != value.endArray(); ++itor) + { + addElement(*itor); + } +} + +LLSD LLScrollListCtrl::getValue() const +{ + LLScrollListItem *item = getFirstSelected(); + if (!item) return LLSD(); + return item->getValue(); +} + +BOOL LLScrollListCtrl::operateOnSelection(EOperation op) +{ + if (op == OP_DELETE) + { + deleteSelectedItems(); + return TRUE; + } + else if (op == OP_DESELECT) + { + deselectAllItems(); + } + return FALSE; +} + +BOOL LLScrollListCtrl::operateOnAll(EOperation op) +{ + if (op == OP_DELETE) + { + clearRows(); + return TRUE; + } + else if (op == OP_DESELECT) + { + deselectAllItems(); + } + else if (op == OP_SELECT) + { + selectAll(); + } + return FALSE; +} +//virtual +void LLScrollListCtrl::setFocus(BOOL b) +{ + mSearchString.clear(); + // for tabbing into pristine scroll lists (Finder) + if (!getFirstSelected()) + { + selectFirstItem(); + onCommit(); + } + LLUICtrl::setFocus(b); +} +//virtual +void LLScrollListCtrl::onFocusLost() +{ + if (mIsPopup) + { + if (getParent()) + { + getParent()->onFocusLost(); + } + } +} diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h new file mode 100644 index 0000000000..426e817215 --- /dev/null +++ b/indra/llui/llscrolllistctrl.h @@ -0,0 +1,527 @@ +/** + * @file llscrolllistctrl.h + * @brief LLScrollListCtrl base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_SCROLLLISTCTRL_H +#define LL_SCROLLLISTCTRL_H + +#include +#include + +#include "lluictrl.h" +#include "llctrlselectioninterface.h" +#include "llfontgl.h" +#include "llui.h" +#include "llstring.h" +#include "llimagegl.h" +#include "lleditmenuhandler.h" +#include "llviewborder.h" +#include "llframetimer.h" +#include "llcheckboxctrl.h" + +class LLScrollbar; +class LLScrollListCtrl; + +class LLScrollListCell +{ +public: + virtual ~LLScrollListCell() {}; + virtual void drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const = 0; // truncate to given width, if possible + virtual S32 getWidth() const = 0; + virtual S32 getHeight() const = 0; + virtual const LLString& getText() const { return LLString::null; } + virtual const LLString& getTextLower() const { return LLString::null; } + virtual const BOOL getVisible() const { return TRUE; } + virtual void setWidth(S32 width) = 0; + virtual void highlightText(S32 num_chars) {} + + virtual BOOL handleClick() { return FALSE; } + virtual void setEnabled(BOOL enable) { } +}; + +class LLScrollListText : public LLScrollListCell +{ + static U32 sCount; +public: + LLScrollListText( const LLString& text, const LLFontGL* font, S32 width = 0, U8 font_style = LLFontGL::NORMAL, LLColor4& color = LLColor4::black, BOOL use_color = FALSE, BOOL visible = TRUE); + /*virtual*/ ~LLScrollListText(); + + virtual void drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const; + virtual S32 getWidth() const { return mWidth; } + virtual void setWidth(S32 width) { mWidth = width; } + virtual S32 getHeight() const { return llround(mFont->getLineHeight()); } + virtual const LLString& getText() const { return mText.getString(); } + virtual const BOOL getVisible() const { return mVisible; } + virtual void highlightText(S32 num_chars) {mHighlightChars = num_chars;} + void setText(const LLString& text); + +private: + LLUIString mText; + const LLFontGL* mFont; + LLColor4* mColor; + const U8 mFontStyle; + S32 mWidth; + S32 mEllipsisWidth; // in pixels, of "..." + BOOL mVisible; + S32 mHighlightChars; + + LLPointer mRoundedRectImage; +}; + +class LLScrollListIcon : public LLScrollListCell +{ +public: + LLScrollListIcon( LLImageGL* icon, S32 width = 0, LLUUID image_id = LLUUID::null); + /*virtual*/ ~LLScrollListIcon(); + virtual void drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const { gl_draw_image(0, 0, mIcon); } + virtual S32 getWidth() const { return mWidth; } + virtual S32 getHeight() const { return mIcon->getHeight(); } + virtual const LLString& getText() const { return mImageUUID; } + virtual const LLString& getTextLower() const { return mImageUUID; } + virtual void setWidth(S32 width) { mWidth = width; } + +private: + LLPointer mIcon; + LLString mImageUUID; + S32 mWidth; +}; + +class LLScrollListCheck : public LLScrollListCell +{ +public: + LLScrollListCheck( LLCheckBoxCtrl* check_box, S32 width = 0); + /*virtual*/ ~LLScrollListCheck(); + virtual void drawToWidth(S32 width, const LLColor4& color, const LLColor4& highlight_color) const; + virtual S32 getWidth() const { return mWidth; } + virtual S32 getHeight() const { return 0; } + virtual void setWidth(S32 width) { mWidth = width; } + + virtual BOOL handleClick(); + virtual void setEnabled(BOOL enable) { if (mCheckBox) mCheckBox->setEnabled(enable); } + + LLCheckBoxCtrl* getCheckBox() { return mCheckBox; } + +private: + LLCheckBoxCtrl* mCheckBox; + S32 mWidth; +}; + +class LLScrollListColumn +{ +public: + // Default constructor + LLScrollListColumn() : mName(""), mSortingColumn(""), mLabel(""), mWidth(-1), mRelWidth(-1.0), mDynamicWidth(FALSE), mIndex(-1), mParentCtrl(NULL), mButton(NULL) { } + + LLScrollListColumn(LLString name, LLString label, S32 width, F32 relwidth) + : mName(name), mSortingColumn(name), mLabel(label), mWidth(width), mRelWidth(relwidth), mDynamicWidth(FALSE), mIndex(-1), mParentCtrl(NULL), mButton(NULL) { } + + LLScrollListColumn(const LLSD &sd) + { + mName = sd.get("name").asString(); + mSortingColumn = mName; + if (sd.has("sort")) + { + mSortingColumn = sd.get("sort").asString(); + } + mLabel = sd.get("label").asString(); + if (sd.has("relwidth") && (F32)sd.get("relwidth").asReal() > 0) + { + mRelWidth = (F32)sd.get("relwidth").asReal(); + if (mRelWidth < 0) mRelWidth = 0; + if (mRelWidth > 1) mRelWidth = 1; + mDynamicWidth = FALSE; + mWidth = 0; + } + else if(sd.has("dynamicwidth") && (BOOL)sd.get("dynamicwidth").asBoolean() == TRUE) + { + mDynamicWidth = TRUE; + mRelWidth = -1; + mWidth = 0; + } + else + { + mWidth = sd.get("width").asInteger(); + mDynamicWidth = FALSE; + mRelWidth = -1; + } + mIndex = -1; + mParentCtrl = NULL; + mButton = NULL; + } + + LLString mName; + LLString mSortingColumn; + LLString mLabel; + S32 mWidth; + F32 mRelWidth; + BOOL mDynamicWidth; + S32 mIndex; + LLScrollListCtrl *mParentCtrl; + LLButton *mButton; +}; + +class LLScrollListItem +{ +public: + LLScrollListItem( BOOL enabled = TRUE, void* userdata = NULL, const LLUUID& uuid = LLUUID::null ) + : mSelected(FALSE), mEnabled( enabled ), mUserdata( userdata ), mItemValue( uuid ), mColumns() {} + LLScrollListItem( LLSD item_value, void* userdata = NULL ) + : mSelected(FALSE), mEnabled( TRUE ), mUserdata( userdata ), mItemValue( item_value ), mColumns() {} + + virtual ~LLScrollListItem(); + + void setSelected( BOOL b ) { mSelected = b; } + BOOL getSelected() const { return mSelected; } + + void setEnabled( BOOL b ); + BOOL getEnabled() const { return mEnabled; } + + void setUserdata( void* userdata ) { mUserdata = userdata; } + void* getUserdata() const { return mUserdata; } + + LLUUID getUUID() const { return mItemValue.asUUID(); } + LLSD getValue() const { return mItemValue; } + + // If width = 0, just use the width of the text. Otherwise override with + // specified width in pixels. + void addColumn( const LLString& text, const LLFontGL* font, S32 width = 0 , U8 font_style = LLFontGL::NORMAL, BOOL visible = TRUE) + { mColumns.push_back( new LLScrollListText(text, font, width, font_style, LLColor4::black, FALSE, visible) ); } + + void addColumn( LLImageGL* icon, S32 width = 0 ) + { mColumns.push_back( new LLScrollListIcon(icon, width) ); } + + void addColumn( LLCheckBoxCtrl* check, S32 width = 0 ) + { mColumns.push_back( new LLScrollListCheck(check,width) ); } + + void setNumColumns(S32 columns); + + void setColumn( S32 column, LLScrollListCell *cell ); + + S32 getNumColumns() const { return mColumns.size(); } + + LLScrollListCell *getColumn(const S32 i) const { if (i < (S32)mColumns.size()) { return mColumns[i]; } return NULL; } + + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + + LLString getContentsCSV(); + +private: + BOOL mSelected; + BOOL mEnabled; + void* mUserdata; + LLSD mItemValue; + std::vector mColumns; +}; + + +class LLScrollListCtrl : public LLUICtrl, public LLEditMenuHandler, + public LLCtrlListInterface, public LLCtrlScrollInterface +{ +public: + LLScrollListCtrl( + const LLString& name, + const LLRect& rect, + void (*commit_callback)(LLUICtrl*, void*), + void* callback_userdata, + BOOL allow_multiple_selection, + BOOL draw_border = TRUE); + + virtual ~LLScrollListCtrl(); + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_SCROLL_LIST; } + virtual LLString getWidgetTag() const { return LL_SCROLL_LIST_CTRL_TAG; } + virtual LLXMLNodePtr getXML(bool save_children = true) const; + void setScrollListParameters(LLXMLNodePtr node); + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + S32 isEmpty() const; + + void deleteAllItems() { clearRows(); } + + // Sets an array of column descriptors + void setColumnHeadings(LLSD headings); + // Numerical based sort by column function (used by LLComboBox) + void sortByColumn(U32 column, BOOL ascending); + + // LLCtrlListInterface functions + virtual S32 getItemCount() const; + // Adds a single column descriptor: ["name" : string, "label" : string, "width" : integer, "relwidth" : integer ] + virtual void addColumn(const LLSD& column, EAddPosition pos = ADD_BOTTOM); + virtual void clearColumns(); + virtual void setColumnLabel(const LLString& column, const LLString& label); + // Adds a single element, from an array of: + // "columns" => [ "column" => column name, "value" => value, "type" => type, "font" => font, "font-style" => style ], "id" => uuid + // Creates missing columns automatically. + virtual LLScrollListItem* addElement(const LLSD& value, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL); + // Simple add element. Takes a single array of: + // [ "value" => value, "font" => font, "font-style" => style ] + virtual LLScrollListItem* addSimpleElement(const LLString& value, EAddPosition pos = ADD_BOTTOM, const LLSD& id = LLSD()); + virtual void clearRows(); // clears all elements + virtual void sortByColumn(LLString name, BOOL ascending); + + // These functions take and return an array of arrays of elements, as above + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + + LLCtrlSelectionInterface* getSelectionInterface() { return (LLCtrlSelectionInterface*)this; } + LLCtrlListInterface* getListInterface() { return (LLCtrlListInterface*)this; } + LLCtrlScrollInterface* getScrollInterface() { return (LLCtrlScrollInterface*)this; } + + // DEPRECATED: Use setSelectedByValue() below. + BOOL setCurrentByID( const LLUUID& id ) { return selectByID(id); } + virtual LLUUID getCurrentID() { return getStringUUIDSelectedItem(); } + + BOOL operateOnSelection(EOperation op); + BOOL operateOnAll(EOperation op); + + // returns FALSE if unable to set the max count so low + BOOL setMaxItemCount(S32 max_count); + + BOOL selectByID( const LLUUID& id ); // FALSE if item not found + + // Match item by value.asString(), which should work for string, integer, uuid. + // Returns FALSE if not found. + BOOL setSelectedByValue(LLSD value, BOOL selected); + + virtual BOOL isSelected(LLSD value); + + BOOL selectFirstItem(); + BOOL selectNthItem( S32 index ); + + void deleteSingleItem( S32 index ) ; + void deleteSelectedItems(); + void deselectAllItems(BOOL no_commit_on_change = FALSE); // by default, go ahead and commit on selection change + + void highlightNthItem( S32 index ); + void setDoubleClickCallback( void (*cb)(void*) ) { mOnDoubleClickCallback = cb; } + void setMaxiumumSelectCallback( void (*cb)(void*) ) { mOnMaximumSelectCallback = cb; } + + void swapWithNext(S32 index); + void swapWithPrevious(S32 index); + + void setCanSelect(BOOL can_select) { mCanSelect = can_select; } + virtual BOOL getCanSelect() const { return mCanSelect; } + + S32 getItemIndex( LLScrollListItem* item ); + S32 getItemIndex( LLUUID& item_id ); + + // "Simple" interface: use this when you're creating a list that contains only unique strings, only + // one of which can be selected at a time. + LLScrollListItem* addSimpleItem( const LLString& item_text, EAddPosition pos = ADD_BOTTOM, BOOL enabled = TRUE ); + // Add an item with an associated LLSD + LLScrollListItem* addSimpleItem(const LLString& item_text, LLSD sd, EAddPosition pos = ADD_BOTTOM, BOOL enabled = TRUE, S32 column_width = 0 ); + + BOOL selectSimpleItem( const LLString& item, BOOL case_sensitive = TRUE ); // FALSE if item not found + BOOL selectSimpleItemByPrefix(const LLString& target, BOOL case_sensitive); + BOOL selectSimpleItemByPrefix(const LLWString& target, BOOL case_sensitive); + const LLString& getSimpleSelectedItem(S32 column = 0) const; + LLSD getSimpleSelectedValue(); + + // DEPRECATED: Use LLSD versions of addSimpleItem() and getSimpleSelectedValue(). + // "StringUUID" interface: use this when you're creating a list that contains non-unique strings each of which + // has an associated, unique UUID, and only one of which can be selected at a time. + LLScrollListItem* addStringUUIDItem(const LLString& item_text, const LLUUID& id, EAddPosition pos = ADD_BOTTOM, BOOL enabled = TRUE, S32 column_width = 0); + LLUUID getStringUUIDSelectedItem(); + + // "Full" interface: use this when you're creating a list that has one or more of the following: + // * contains icons + // * contains multiple columns + // * allows multiple selection + // * has items that are not guarenteed to have unique names + // * has additional per-item data (e.g. a UUID or void* userdata) + // + // To add items using this approach, create new LLScrollListItems and LLScrollListCells. Add the + // cells (column entries) to each item, and add the item to the LLScrollListCtrl. + // + // The LLScrollListCtrl owns its items and is responsible for deleting them + // (except in the case that the addItem() call fails, in which case it is up + // to the caller to delete the item) + + // returns FALSE if item faile to be added to list, does NOT delete 'item' + // TomY TODO - Deprecate this API and remove it + BOOL addItem( LLScrollListItem* item, EAddPosition pos = ADD_BOTTOM ); + LLScrollListItem* getFirstSelected() const; + virtual S32 getFirstSelectedIndex(); + std::vector getAllSelected() const; + + LLScrollListItem* getLastSelectedItem() const { return mLastSelected; } + + // iterate over all items + LLScrollListItem* getFirstData() const; + std::vector getAllData() const; + + void setAllowMultipleSelection(BOOL mult ) { mAllowMultipleSelection = mult; } + + void setBgWriteableColor(const LLColor4 &c) { mBgWriteableColor = c; } + void setReadOnlyBgColor(const LLColor4 &c) { mBgReadOnlyColor = c; } + void setBgSelectedColor(const LLColor4 &c) { mBgSelectedColor = c; } + void setBgStripeColor(const LLColor4& c) { mBgStripeColor = c; } + void setFgSelectedColor(const LLColor4 &c) { mFgSelectedColor = c; } + void setFgUnselectedColor(const LLColor4 &c){ mFgUnselectedColor = c; } + void setHighlightedColor(const LLColor4 &c) { mHighlightedColor = c; } + void setFgDisableColor(const LLColor4 &c) { mFgDisabledColor = c; } + + void setBackgroundVisible(BOOL b) { mBackgroundVisible = b; } + void setDrawStripes(BOOL b) { mDrawStripes = b; } + void setColumnPadding(const S32 c) { mColumnPadding = c; } + void setCommitOnKeyboardMovement(BOOL b) { mCommitOnKeyboardMovement = b; } + void setCommitOnSelectionChange(BOOL b) { mCommitOnSelectionChange = b; } + void setAllowKeyboardMovement(BOOL b) { mAllowKeyboardMovement = b; } + + void setMaxSelectable(U32 max_selected) { mMaxSelectable = max_selected; } + S32 getMaxSelectable() { return mMaxSelectable; } + + + virtual S32 getScrollPos(); + virtual void setScrollPos( S32 pos ); + + S32 getSearchColumn() { return mSearchColumn; } + void setSearchColumn(S32 column) { mSearchColumn = column; } + + void clearSearchString() { mSearchString.clear(); } + + // Overridden from LLView + virtual void draw(); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent); + virtual BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); + virtual void setEnabled(BOOL enabled); + virtual void setFocus( BOOL b ); + virtual void onFocusLost(); + + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + virtual void arrange(S32 max_width, S32 max_height); + virtual LLRect getRequiredRect(); + static BOOL rowPreceeds(LLScrollListItem *new_row, LLScrollListItem *test_row); + + // Used "internally" by the scroll bar. + static void onScrollChange( S32 new_pos, LLScrollbar* src, void* userdata ); + + static void onClickColumn(void *userdata); + + void updateColumns(); + void updateColumnButtons(); + + void setDisplayHeading(BOOL display); + void setHeadingHeight(S32 heading_height); + void setHeadingFont(const LLFontGL* heading_font); + void setCollapseEmptyColumns(BOOL collapse); + void setIsPopup(BOOL is_popup) { mIsPopup = is_popup; } + + LLScrollListItem* hitItem(S32 x,S32 y); + virtual void scrollToShowSelected(); + + // LLEditMenuHandler functions + virtual void copy(); + virtual BOOL canCopy(); + + virtual void cut(); + virtual BOOL canCut(); + + virtual void doDelete(); + virtual BOOL canDoDelete(); + + virtual void selectAll(); + virtual BOOL canSelectAll(); + + virtual void deselect(); + virtual BOOL canDeselect(); + + void setNumDynamicColumns(int num) { mNumDynamicWidthColumns = num; } + void setTotalStaticColumnWidth(int width) { mTotalStaticColumnWidth = width; } + +protected: + void selectPrevItem(BOOL extend_selection); + void selectNextItem(BOOL extend_selection); + void drawItems(); + void updateLineHeight(); + void reportInvalidInput(); + BOOL isRepeatedChars(const LLWString& string) const; + void selectItem(LLScrollListItem* itemp, BOOL single_select = TRUE); + void deselectItem(LLScrollListItem* itemp); + void commitIfChanged(); + +protected: + S32 mCurIndex; // For get[First/Next]Data + S32 mCurSelectedIndex; // For get[First/Next]Selected + + S32 mLineHeight; // the max height of a single line + S32 mScrollLines; // how many lines we've scrolled down + S32 mPageLines; // max number of lines is it possible to see on the screen given mRect and mLineHeight + S32 mHeadingHeight; // the height of the column header buttons, if visible + U32 mMaxSelectable; + const LLFontGL* mHeadingFont; // the font to use for column head buttons, if visible + LLScrollbar* mScrollbar; + BOOL mAllowMultipleSelection; + BOOL mAllowKeyboardMovement; + BOOL mCommitOnKeyboardMovement; + BOOL mCommitOnSelectionChange; + BOOL mSelectionChanged; + BOOL mCanSelect; + BOOL mDisplayColumnButtons; + BOOL mCollapseEmptyColumns; + BOOL mIsPopup; + + typedef std::deque item_list; + item_list mItemList; + + LLScrollListItem *mLastSelected; + + S32 mMaxItemCount; + + LLRect mItemListRect; + + S32 mColumnPadding; + + BOOL mBackgroundVisible; + BOOL mDrawStripes; + + LLColor4 mBgWriteableColor; + LLColor4 mBgReadOnlyColor; + LLColor4 mBgSelectedColor; + LLColor4 mBgStripeColor; + LLColor4 mFgSelectedColor; + LLColor4 mFgUnselectedColor; + LLColor4 mFgDisabledColor; + LLColor4 mHighlightedColor; + + S32 mBorderThickness; + void (*mOnDoubleClickCallback)(void* userdata); + void (*mOnMaximumSelectCallback)(void* userdata ); + + S32 mHighlightedItem; + LLViewBorder* mBorder; + + LLWString mSearchString; + LLFrameTimer mSearchTimer; + + LLString mDefaultColumn; + + S32 mSearchColumn; + S32 mNumDynamicWidthColumns; + S32 mTotalStaticColumnWidth; + + static U32 sSortColumn; + static BOOL sSortAscending; + + std::map mColumns; + std::vector mColumnsIndexed; + +public: + // HACK: Did we draw one selected item this frame? + BOOL mDrewSelected; +}; + +const BOOL MULTIPLE_SELECT_YES = TRUE; +const BOOL MULTIPLE_SELECT_NO = FALSE; + +const BOOL SHOW_BORDER_YES = TRUE; +const BOOL SHOW_BORDER_NO = FALSE; + +#endif // LL_SCROLLLISTCTRL_H diff --git a/indra/llui/llslider.cpp b/indra/llui/llslider.cpp new file mode 100644 index 0000000000..2a1c2f7845 --- /dev/null +++ b/indra/llui/llslider.cpp @@ -0,0 +1,352 @@ +/** + * @file llslider.cpp + * @brief LLSlider base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llslider.h" +#include "llui.h" + +#include "llgl.h" +#include "llwindow.h" +#include "llfocusmgr.h" +#include "llkeyboard.h" // for the MASK constants +#include "llcontrol.h" +#include "llimagegl.h" + +const S32 THUMB_WIDTH = 8; +const S32 TRACK_HEIGHT = 6; + +LLSlider::LLSlider( + const LLString& name, + const LLRect& rect, + void (*on_commit_callback)(LLUICtrl* ctrl, void* userdata), + void* callback_userdata, + F32 initial_value, + F32 min_value, + F32 max_value, + F32 increment, + const LLString& control_name) + : + LLUICtrl( name, rect, TRUE, on_commit_callback, callback_userdata, + FOLLOWS_LEFT | FOLLOWS_TOP), + mValue( initial_value ), + mInitialValue( initial_value ), + mMinValue( min_value ), + mMaxValue( max_value ), + mIncrement( increment ), + mMouseOffset( 0 ), + mDragStartThumbRect( 0, mRect.getHeight(), THUMB_WIDTH, 0 ), + mThumbRect( 0, mRect.getHeight(), THUMB_WIDTH, 0 ), + mTrackColor( LLUI::sColorsGroup->getColor( "SliderTrackColor" ) ), + mThumbOutlineColor( LLUI::sColorsGroup->getColor( "SliderThumbOutlineColor" ) ), + mThumbCenterColor( LLUI::sColorsGroup->getColor( "SliderThumbCenterColor" ) ), + mDisabledThumbColor(LLUI::sColorsGroup->getColor( "SliderDisabledThumbColor" ) ), + mMouseDownCallback( NULL ), + mMouseUpCallback( NULL ) +{ + // prperly handle setting the starting thumb rect + // do it this way to handle both the operating-on-settings + // and standalone ways of using this + setControlName(control_name, NULL); + setValue(getValueF32()); +} + +EWidgetType LLSlider::getWidgetType() const +{ + return WIDGET_TYPE_SLIDER_BAR; +} + +LLString LLSlider::getWidgetTag() const +{ + return LL_SLIDER_TAG; +} + +void LLSlider::setValue(F32 value, BOOL from_event) +{ + value = llclamp( value, mMinValue, mMaxValue ); + + // Round to nearest increment (bias towards rounding down) + value -= mMinValue; + value += mIncrement/2.0001f; + value -= fmod(value, mIncrement); + mValue = mMinValue + value; + + if (!from_event) + { + setControlValue(mValue); + } + + F32 t = (mValue - mMinValue) / (mMaxValue - mMinValue); + + S32 left_edge = THUMB_WIDTH/2; + S32 right_edge = mRect.getWidth() - (THUMB_WIDTH/2); + + S32 x = left_edge + S32( t * (right_edge - left_edge) ); + mThumbRect.mLeft = x - (THUMB_WIDTH/2); + mThumbRect.mRight = x + (THUMB_WIDTH/2); +} + +F32 LLSlider::getValueF32() const +{ + return mValue; +} + +BOOL LLSlider::handleHover(S32 x, S32 y, MASK mask) +{ + if( gFocusMgr.getMouseCapture() == this ) + { + S32 left_edge = THUMB_WIDTH/2; + S32 right_edge = mRect.getWidth() - (THUMB_WIDTH/2); + + x += mMouseOffset; + x = llclamp( x, left_edge, right_edge ); + + F32 t = F32(x - left_edge) / (right_edge - left_edge); + setValue(t * (mMaxValue - mMinValue) + mMinValue ); + onCommit(); + + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; + } + else + { + getWindow()->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl; + } + return TRUE; +} + +BOOL LLSlider::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + if( gFocusMgr.getMouseCapture() == this ) + { + gFocusMgr.setMouseCapture( NULL, NULL ); + + if( mMouseUpCallback ) + { + mMouseUpCallback( this, mCallbackUserData ); + } + handled = TRUE; + make_ui_sound("UISndClickRelease"); + } + else + { + handled = TRUE; + } + + return handled; +} + +BOOL LLSlider::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // only do sticky-focus on non-chrome widgets + if (!getIsChrome()) + { + setFocus(TRUE); + } + if( mMouseDownCallback ) + { + mMouseDownCallback( this, mCallbackUserData ); + } + + if (MASK_CONTROL & mask) // if CTRL is modifying + { + setValue(mInitialValue); + onCommit(); + } + else + { + // Find the offset of the actual mouse location from the center of the thumb. + if (mThumbRect.pointInRect(x,y)) + { + mMouseOffset = (mThumbRect.mLeft + THUMB_WIDTH/2) - x; + } + else + { + mMouseOffset = 0; + } + + // Start dragging the thumb + // No handler needed for focus lost since this class has no state that depends on it. + gFocusMgr.setMouseCapture( this, NULL ); + mDragStartThumbRect = mThumbRect; + } + make_ui_sound("UISndClick"); + + return TRUE; +} + +BOOL LLSlider::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + BOOL handled = FALSE; + if( getVisible() && mEnabled && !called_from_parent ) + { + switch(key) + { + case KEY_UP: + case KEY_DOWN: + // eat up and down keys to be consistent + handled = TRUE; + break; + case KEY_LEFT: + setValue(getValueF32() - getIncrement()); + onCommit(); + handled = TRUE; + break; + case KEY_RIGHT: + setValue(getValueF32() + getIncrement()); + onCommit(); + handled = TRUE; + break; + default: + break; + } + } + return handled; +} + +void LLSlider::draw() +{ + if( getVisible() ) + { + // Draw background and thumb. + + // drawing solids requires texturing be disabled + LLGLSNoTexture no_texture; + + LLRect rect(mDragStartThumbRect); + + F32 opacity = mEnabled ? 1.f : 0.3f; + + // Track + + LLUUID thumb_image_id; + thumb_image_id.set(LLUI::sAssetsGroup->getString("rounded_square.tga")); + LLImageGL* thumb_imagep = LLUI::sImageProvider->getUIImageByID(thumb_image_id); + + S32 height_offset = (mRect.getHeight() - TRACK_HEIGHT) / 2; + LLRect track_rect(0, mRect.getHeight() - height_offset, mRect.getWidth(), height_offset ); + + track_rect.stretch(-1); + gl_draw_scaled_image_with_border(track_rect.mLeft, track_rect.mBottom, 16, 16, track_rect.getWidth(), track_rect.getHeight(), + thumb_imagep, mTrackColor % opacity); + //gl_rect_2d( track_rect, mThumbOutlineColor % opacity ); + + if (!thumb_imagep) + { + gl_rect_2d(mThumbRect, mThumbCenterColor, TRUE); + if (gFocusMgr.getMouseCapture() == this) + { + gl_rect_2d(mDragStartThumbRect, mThumbCenterColor % opacity, FALSE); + } + } + else if( gFocusMgr.getMouseCapture() == this ) + { + gl_draw_scaled_image_with_border(mDragStartThumbRect.mLeft, mDragStartThumbRect.mBottom, 16, 16, mDragStartThumbRect.getWidth(), mDragStartThumbRect.getHeight(), + thumb_imagep, mThumbCenterColor % 0.3f, TRUE); + + if (hasFocus()) + { + F32 lerp_amt = gFocusMgr.getFocusFlashAmt(); + LLRect highlight_rect = mThumbRect; + highlight_rect.stretch(llround(lerp(1.f, 3.f, lerp_amt))); + gl_draw_scaled_image_with_border(highlight_rect.mLeft, highlight_rect.mBottom, 16, 16, highlight_rect.getWidth(), highlight_rect.getHeight(), + thumb_imagep, gFocusMgr.getFocusColor()); + } + + + gl_draw_scaled_image_with_border(mThumbRect.mLeft, mThumbRect.mBottom, 16, 16, mThumbRect.getWidth(), mThumbRect.getHeight(), + thumb_imagep, mThumbOutlineColor, TRUE); + + //// Start Thumb + //gl_rect_2d( mDragStartThumbRect, mThumbOutlineColor % 0.3f ); + //rect.stretch(-1); + //gl_rect_2d( rect, mThumbCenterColor % 0.3f ); + + //// Thumb + //gl_rect_2d( mThumbRect, mThumbOutlineColor ); + } + else + { + if (hasFocus()) + { + F32 lerp_amt = gFocusMgr.getFocusFlashAmt(); + LLRect highlight_rect = mThumbRect; + highlight_rect.stretch(llround(lerp(1.f, 3.f, lerp_amt))); + gl_draw_scaled_image_with_border(highlight_rect.mLeft, highlight_rect.mBottom, 16, 16, highlight_rect.getWidth(), highlight_rect.getHeight(), + thumb_imagep, gFocusMgr.getFocusColor()); + } + + gl_draw_scaled_image_with_border(mThumbRect.mLeft, mThumbRect.mBottom, 16, 16, mThumbRect.getWidth(), mThumbRect.getHeight(), + thumb_imagep, mThumbCenterColor % opacity, TRUE); + //rect = mThumbRect; + + //gl_rect_2d( mThumbRect, mThumbOutlineColor % opacity ); + // + //rect.stretch(-1); + + //// Thumb + //gl_rect_2d( rect, mThumbCenterColor % opacity ); + + } + + LLUICtrl::draw(); + } +} + +// virtual +LLXMLNodePtr LLSlider::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + node->createChild("initial_val", TRUE)->setFloatValue(getInitialValue()); + node->createChild("min_val", TRUE)->setFloatValue(getMinValue()); + node->createChild("max_val", TRUE)->setFloatValue(getMaxValue()); + node->createChild("increment", TRUE)->setFloatValue(getIncrement()); + + return node; +} + + +//static +LLView* LLSlider::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("slider_bar"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + F32 initial_value = 0.f; + node->getAttributeF32("initial_val", initial_value); + + F32 min_value = 0.f; + node->getAttributeF32("min_val", min_value); + + F32 max_value = 1.f; + node->getAttributeF32("max_val", max_value); + + F32 increment = 0.1f; + node->getAttributeF32("increment", increment); + + + LLSlider* slider = new LLSlider(name, + rect, + NULL, + NULL, + initial_value, + min_value, + max_value, + increment); + + slider->initFromXML(node, parent); + + return slider; +} diff --git a/indra/llui/llslider.h b/indra/llui/llslider.h new file mode 100644 index 0000000000..a437cf2886 --- /dev/null +++ b/indra/llui/llslider.h @@ -0,0 +1,79 @@ +/** + * @file llslider.h + * @brief A simple slider with no label. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSLIDER_H +#define LL_LLSLIDER_H + +#include "lluictrl.h" +#include "v4color.h" + +class LLSlider : public LLUICtrl +{ +public: + LLSlider( + const LLString& name, + const LLRect& rect, + void (*on_commit_callback)(LLUICtrl* ctrl, void* userdata), + void* callback_userdata, + F32 initial_value, + F32 min_value, + F32 max_value, + F32 increment, + const LLString& control_name = LLString::null ); + + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + void setValue( F32 value, BOOL from_event = FALSE ); + F32 getValueF32() const; + + virtual void setValue(const LLSD& value ) { setValue((F32)value.asReal(), TRUE); } + virtual LLSD getValue() const { return LLSD(getValueF32()); } + + virtual void setMinValue(LLSD min_value) { setMinValue((F32)min_value.asReal()); } + virtual void setMaxValue(LLSD max_value) { setMaxValue((F32)max_value.asReal()); } + + F32 getInitialValue() const { return mInitialValue; } + F32 getMinValue() const { return mMinValue; } + F32 getMaxValue() const { return mMaxValue; } + F32 getIncrement() const { return mIncrement; } + void setMinValue(F32 min_value) {mMinValue = min_value;} + void setMaxValue(F32 max_value) {mMaxValue = max_value;} + void setIncrement(F32 increment) {mIncrement = increment;} + void setMouseDownCallback( void (*cb)(LLUICtrl* ctrl, void* userdata) ) { mMouseDownCallback = cb; } + void setMouseUpCallback( void (*cb)(LLUICtrl* ctrl, void* userdata) ) { mMouseUpCallback = cb; } + + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + virtual void draw(); + +protected: + F32 mValue; + F32 mInitialValue; + F32 mMinValue; + F32 mMaxValue; + F32 mIncrement; + + S32 mMouseOffset; + LLRect mDragStartThumbRect; + + LLRect mThumbRect; + LLColor4 mTrackColor; + LLColor4 mThumbOutlineColor; + LLColor4 mThumbCenterColor; + LLColor4 mDisabledThumbColor; + + void (*mMouseDownCallback)(LLUICtrl* ctrl, void* userdata); + void (*mMouseUpCallback)(LLUICtrl* ctrl, void* userdata); +}; + +#endif // LL_LLSLIDER_H diff --git a/indra/llui/llsliderctrl.cpp b/indra/llui/llsliderctrl.cpp new file mode 100644 index 0000000000..6c740aa39e --- /dev/null +++ b/indra/llui/llsliderctrl.cpp @@ -0,0 +1,538 @@ +/** + * @file llsliderctrl.cpp + * @brief LLSliderCtrl base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llsliderctrl.h" + +#include "audioengine.h" +#include "sound_ids.h" + +#include "llmath.h" +#include "llfontgl.h" +#include "llgl.h" +#include "llkeyboard.h" +#include "lllineeditor.h" +#include "llslider.h" +#include "llstring.h" +#include "lltextbox.h" +#include "llui.h" +#include "lluiconstants.h" +#include "llcontrol.h" +#include "llfocusmgr.h" +#include "llresmgr.h" + +const U32 MAX_STRING_LENGTH = 10; + + +LLSliderCtrl::LLSliderCtrl(const LLString& name, const LLRect& rect, + const LLString& label, + const LLFontGL* font, + S32 label_width, + S32 text_left, + BOOL show_text, + BOOL can_edit_text, + void (*commit_callback)(LLUICtrl*, void*), + void* callback_user_data, + F32 initial_value, F32 min_value, F32 max_value, F32 increment, + const LLString& control_which) + : LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data ), + mFont(font), + mShowText( show_text ), + mCanEditText( can_edit_text ), + mPrecision( 3 ), + mLabelBox( NULL ), + mLabelWidth( label_width ), + mValue( initial_value ), + mEditor( NULL ), + mTextBox( NULL ), + mTextEnabledColor( LLUI::sColorsGroup->getColor( "LabelTextColor" ) ), + mTextDisabledColor( LLUI::sColorsGroup->getColor( "LabelDisabledColor" ) ), + mSliderMouseUpCallback( NULL ), + mSliderMouseDownCallback( NULL ) +{ + S32 top = mRect.getHeight(); + S32 bottom = 0; + S32 left = 0; + + // Label + if( !label.empty() ) + { + if (label_width == 0) + { + label_width = font->getWidth(label); + } + LLRect label_rect( left, top, label_width, bottom ); + mLabelBox = new LLTextBox( "SliderCtrl Label", label_rect, label.c_str(), font ); + addChild(mLabelBox); + } + + S32 slider_right = mRect.getWidth(); + if( show_text ) + { + slider_right = text_left - SLIDERCTRL_SPACING; + } + + S32 slider_left = label_width ? label_width + SLIDERCTRL_SPACING : 0; + LLRect slider_rect( slider_left, top, slider_right, bottom ); + mSlider = new LLSlider( + "slider", + slider_rect, + LLSliderCtrl::onSliderCommit, this, + initial_value, min_value, max_value, increment, + control_which ); + addChild( mSlider ); + + if( show_text ) + { + LLRect text_rect( text_left, top, mRect.getWidth(), bottom ); + if( can_edit_text ) + { + mEditor = new LLLineEditor( "SliderCtrl Editor", text_rect, + "", font, + MAX_STRING_LENGTH, + &LLSliderCtrl::onEditorCommit, NULL, NULL, this, + &LLLineEditor::prevalidateFloat ); + mEditor->setFollowsLeft(); + mEditor->setFollowsBottom(); + mEditor->setFocusReceivedCallback( &LLSliderCtrl::onEditorGainFocus ); + mEditor->setIgnoreTab(TRUE); + // don't do this, as selecting the entire text is single clicking in some cases + // and double clicking in others + //mEditor->setSelectAllonFocusReceived(TRUE); + addChild(mEditor); + } + else + { + mTextBox = new LLTextBox( "SliderCtrl Text", text_rect, "", font); + mTextBox->setFollowsLeft(); + mTextBox->setFollowsBottom(); + addChild(mTextBox); + } + } + + updateText(); +} + +LLSliderCtrl::~LLSliderCtrl() +{ + // Children all cleaned up by default view destructor. +} + +// static +void LLSliderCtrl::onEditorGainFocus( LLUICtrl* caller, void *userdata ) +{ + LLSliderCtrl* self = (LLSliderCtrl*) userdata; + llassert( caller == self->mEditor ); + + self->onFocusReceived(); +} + +F32 LLSliderCtrl::getValueF32() const +{ + return mSlider->getValueF32(); +} + +void LLSliderCtrl::setValue(F32 v, BOOL from_event) +{ + mSlider->setValue( v, from_event ); + mValue = mSlider->getValueF32(); + updateText(); +} + +BOOL LLSliderCtrl::setLabelArg( const LLString& key, const LLString& text ) +{ + BOOL res = FALSE; + if (mLabelBox) + { + res = mLabelBox->setTextArg(key, text); + if (res && mLabelWidth == 0) + { + S32 label_width = mFont->getWidth(mLabelBox->getText()); + LLRect rect = mLabelBox->getRect(); + S32 prev_right = rect.mRight; + rect.mRight = rect.mLeft + label_width; + mLabelBox->setRect(rect); + + S32 delta = rect.mRight - prev_right; + rect = mSlider->getRect(); + S32 left = rect.mLeft + delta; + left = llclamp(left, 0, rect.mRight-SLIDERCTRL_SPACING); + rect.mLeft = left; + mSlider->setRect(rect); + } + } + return res; +} + +void LLSliderCtrl::clear() +{ + setValue(0.0f); + if( mEditor ) + { + mEditor->setText( "" ); + } + if( mTextBox ) + { + mTextBox->setText( "" ); + } + +} + +BOOL LLSliderCtrl::isMouseHeldDown() +{ + return gFocusMgr.getMouseCapture() == mSlider; +} + +void LLSliderCtrl::updateText() +{ + if( mEditor || mTextBox ) + { + LLLocale locale(LLLocale::USER_LOCALE); + + // Don't display very small negative values as -0.000 + F32 displayed_value = (F32)(floor(getValueF32() * pow(10, mPrecision) + 0.5) / pow(10, mPrecision)); + + LLString format = llformat("%%.%df", mPrecision); + LLString text = llformat(format.c_str(), displayed_value); + if( mEditor ) + { + mEditor->setText( text ); + } + else + { + mTextBox->setText( text ); + } + } +} + +// static +void LLSliderCtrl::onEditorCommit( LLUICtrl* caller, void *userdata ) +{ + LLSliderCtrl* self = (LLSliderCtrl*) userdata; + llassert( caller == self->mEditor ); + + BOOL success = FALSE; + F32 val = self->mValue; + F32 saved_val = self->mValue; + + LLString text = self->mEditor->getText(); + if( LLLineEditor::postvalidateFloat( text ) ) + { + LLLocale locale(LLLocale::USER_LOCALE); + val = (F32) atof( text.c_str() ); + if( self->mSlider->getMinValue() <= val && val <= self->mSlider->getMaxValue() ) + { + if( self->mValidateCallback ) + { + self->setValue( val ); // set the value temporarily so that the callback can retrieve it. + if( self->mValidateCallback( self, self->mCallbackUserData ) ) + { + success = TRUE; + } + } + else + { + self->setValue( val ); + success = TRUE; + } + } + } + + if( success ) + { + self->onCommit(); + } + else + { + if( self->getValueF32() != saved_val ) + { + self->setValue( saved_val ); + } + self->reportInvalidData(); + } + self->updateText(); +} + +// static +void LLSliderCtrl::onSliderCommit( LLUICtrl* caller, void *userdata ) +{ + LLSliderCtrl* self = (LLSliderCtrl*) userdata; + llassert( caller == self->mSlider ); + + BOOL success = FALSE; + F32 saved_val = self->mValue; + F32 new_val = self->mSlider->getValueF32(); + + if( self->mValidateCallback ) + { + self->mValue = new_val; // set the value temporarily so that the callback can retrieve it. + if( self->mValidateCallback( self, self->mCallbackUserData ) ) + { + success = TRUE; + } + } + else + { + self->mValue = new_val; + success = TRUE; + } + + if( success ) + { + self->onCommit(); + } + else + { + if( self->mValue != saved_val ) + { + self->setValue( saved_val ); + } + self->reportInvalidData(); + } + self->updateText(); +} + +void LLSliderCtrl::setEnabled(BOOL b) +{ + LLUICtrl::setEnabled( b ); + + if( mLabelBox ) + { + mLabelBox->setColor( b ? mTextEnabledColor : mTextDisabledColor ); + } + + mSlider->setEnabled( b ); + + if( mEditor ) + { + mEditor->setEnabled( b ); + } + + if( mTextBox ) + { + mTextBox->setColor( b ? mTextEnabledColor : mTextDisabledColor ); + } +} + + +void LLSliderCtrl::setTentative(BOOL b) +{ + if( mEditor ) + { + mEditor->setTentative(b); + } + LLUICtrl::setTentative(b); +} + + +void LLSliderCtrl::onCommit() +{ + setTentative(FALSE); + + if( mEditor ) + { + mEditor->setTentative(FALSE); + } + + LLUICtrl::onCommit(); +} + + +void LLSliderCtrl::setPrecision(S32 precision) +{ + if (precision < 0 || precision > 10) + { + llerrs << "LLSliderCtrl::setPrecision - precision out of range" << llendl; + return; + } + + mPrecision = precision; + updateText(); +} + +void LLSliderCtrl::setSliderMouseDownCallback( void (*slider_mousedown_callback)(LLUICtrl* caller, void* userdata) ) +{ + mSliderMouseDownCallback = slider_mousedown_callback; + mSlider->setMouseDownCallback( LLSliderCtrl::onSliderMouseDown ); +} + +// static +void LLSliderCtrl::onSliderMouseDown(LLUICtrl* caller, void* userdata) +{ + LLSliderCtrl* self = (LLSliderCtrl*) userdata; + if( self->mSliderMouseDownCallback ) + { + self->mSliderMouseDownCallback( self, self->mCallbackUserData ); + } +} + + +void LLSliderCtrl::setSliderMouseUpCallback( void (*slider_mouseup_callback)(LLUICtrl* caller, void* userdata) ) +{ + mSliderMouseUpCallback = slider_mouseup_callback; + mSlider->setMouseUpCallback( LLSliderCtrl::onSliderMouseUp ); +} + +// static +void LLSliderCtrl::onSliderMouseUp(LLUICtrl* caller, void* userdata) +{ + LLSliderCtrl* self = (LLSliderCtrl*) userdata; + if( self->mSliderMouseUpCallback ) + { + self->mSliderMouseUpCallback( self, self->mCallbackUserData ); + } +} + +void LLSliderCtrl::onTabInto() +{ + if( mEditor ) + { + mEditor->onTabInto(); + } +} + +void LLSliderCtrl::reportInvalidData() +{ + make_ui_sound("UISndBadKeystroke"); +} + +//virtual +LLString LLSliderCtrl::getControlName() const +{ + return mSlider->getControlName(); +} + +// virtual +void LLSliderCtrl::setControlName(const LLString& control_name, LLView* context) +{ + mSlider->setControlName(control_name, context); +} + +// virtual +LLXMLNodePtr LLSliderCtrl::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + node->createChild("show_text", TRUE)->setBoolValue(mShowText); + + node->createChild("can_edit_text", TRUE)->setBoolValue(mCanEditText); + + node->createChild("decimal_digits", TRUE)->setIntValue(mPrecision); + + if (mLabelBox) + { + node->createChild("label", TRUE)->setStringValue(mLabelBox->getText()); + } + + // TomY TODO: Do we really want to export the transient state of the slider? + node->createChild("value", TRUE)->setFloatValue(mValue); + + if (mSlider) + { + node->createChild("initial_val", TRUE)->setFloatValue(mSlider->getInitialValue()); + node->createChild("min_val", TRUE)->setFloatValue(mSlider->getMinValue()); + node->createChild("max_val", TRUE)->setFloatValue(mSlider->getMaxValue()); + node->createChild("increment", TRUE)->setFloatValue(mSlider->getIncrement()); + } + addColorXML(node, mTextEnabledColor, "text_enabled_color", "LabelTextColor"); + addColorXML(node, mTextDisabledColor, "text_disabled_color", "LabelDisabledColor"); + + return node; +} + +LLView* LLSliderCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("slider"); + node->getAttributeString("name", name); + + LLString label; + node->getAttributeString("label", label); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + LLFontGL* font = LLView::selectFont(node); + + // HACK: Font might not be specified. + if (!font) + { + font = LLFontGL::sSansSerifSmall; + } + + S32 label_width = 0; + node->getAttributeS32("label_width", label_width); + + BOOL show_text = TRUE; + node->getAttributeBOOL("show_text", show_text); + + BOOL can_edit_text = FALSE; + node->getAttributeBOOL("can_edit_text", can_edit_text); + + F32 initial_value = 0.f; + node->getAttributeF32("initial_val", initial_value); + + F32 min_value = 0.f; + node->getAttributeF32("min_val", min_value); + + F32 max_value = 1.f; + node->getAttributeF32("max_val", max_value); + + F32 increment = 0.1f; + node->getAttributeF32("increment", increment); + + U32 precision = 3; + node->getAttributeU32("decimal_digits", precision); + + S32 text_left = 0; + if (show_text) + { + // calculate the size of the text box (log max_value is number of digits - 1 so plus 1) + if ( max_value ) + text_left = font->getWidth("0") * ( static_cast < S32 > ( log10 ( max_value ) ) + precision + 1 ); + + if ( increment < 1.0f ) + text_left += font->getWidth("."); // (mostly) take account of decimal point in value + + if ( min_value < 0.0f || max_value < 0.0f ) + text_left += font->getWidth("-"); // (mostly) take account of minus sign + + // padding to make things look nicer + text_left += 8; + } + + LLUICtrlCallback callback = NULL; + + if (label.empty()) + { + label.assign(node->getTextContents()); + } + + LLSliderCtrl* slider = new LLSliderCtrl(name, + rect, + label, + font, + label_width, + rect.getWidth() - text_left, + show_text, + can_edit_text, + callback, + NULL, + initial_value, + min_value, + max_value, + increment); + + slider->setPrecision(precision); + + slider->initFromXML(node, parent); + + slider->updateText(); + + return slider; +} diff --git a/indra/llui/llsliderctrl.h b/indra/llui/llsliderctrl.h new file mode 100644 index 0000000000..2185e42eb1 --- /dev/null +++ b/indra/llui/llsliderctrl.h @@ -0,0 +1,124 @@ +/** + * @file llsliderctrl.h + * @brief LLSliderCtrl base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSLIDERCTRL_H +#define LL_LLSLIDERCTRL_H + +#include "lluictrl.h" +#include "v4color.h" +#include "llslider.h" +#include "lltextbox.h" +#include "llrect.h" + +// +// Constants +// +const S32 SLIDERCTRL_SPACING = 4; // space between label, slider, and text +const S32 SLIDERCTRL_HEIGHT = 16; + +// +// Classes +// +class LLFontGL; +class LLLineEditor; +class LLSlider; + + +class LLSliderCtrl : public LLUICtrl +{ +public: + LLSliderCtrl(const LLString& name, + const LLRect& rect, + const LLString& label, + const LLFontGL* font, + S32 slider_left, + S32 text_left, + BOOL show_text, + BOOL can_edit_text, + void (*commit_callback)(LLUICtrl*, void*), + void* callback_userdata, + F32 initial_value, F32 min_value, F32 max_value, F32 increment, + const LLString& control_which = LLString::null ); + + virtual ~LLSliderCtrl(); + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_SLIDER; } + virtual LLString getWidgetTag() const { return LL_SLIDER_CTRL_TAG; } + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + F32 getValueF32() const; + void setValue(F32 v, BOOL from_event = FALSE); + + virtual void setValue(const LLSD& value ) { setValue((F32)value.asReal(), TRUE); } + virtual LLSD getValue() const { return LLSD(getValueF32()); } + virtual BOOL setLabelArg( const LLString& key, const LLString& text ); + + virtual void setMinValue(LLSD min_value) { setMinValue((F32)min_value.asReal()); } + virtual void setMaxValue(LLSD max_value) { setMaxValue((F32)max_value.asReal()); } + + BOOL isMouseHeldDown(); + + virtual void setEnabled( BOOL b ); + virtual void clear(); + virtual void setPrecision(S32 precision); + void setMinValue(F32 min_value) {mSlider->setMinValue(min_value);} + void setMaxValue(F32 max_value) {mSlider->setMaxValue(max_value);} + void setIncrement(F32 increment) {mSlider->setIncrement(increment);} + + F32 getMinValue() { return mSlider->getMinValue(); } + F32 getMaxValue() { return mSlider->getMaxValue(); } + + void setLabel(const LLString& label) { if (mLabelBox) mLabelBox->setText(label); } + void setLabelColor(const LLColor4& c) { mTextEnabledColor = c; } + void setDisabledLabelColor(const LLColor4& c) { mTextDisabledColor = c; } + + void setSliderMouseDownCallback( void (*slider_mousedown_callback)(LLUICtrl* caller, void* userdata) ); + void setSliderMouseUpCallback( void (*slider_mouseup_callback)(LLUICtrl* caller, void* userdata) ); + + virtual void onTabInto(); + + virtual void setTentative(BOOL b); // marks value as tentative + virtual void onCommit(); // mark not tentative, then commit + + virtual void setControlName(const LLString& control_name, LLView* context); + virtual LLString getControlName() const; + + static void onSliderCommit(LLUICtrl* caller, void* userdata); + static void onSliderMouseDown(LLUICtrl* caller,void* userdata); + static void onSliderMouseUp(LLUICtrl* caller,void* userdata); + + static void onEditorCommit(LLUICtrl* caller, void* userdata); + static void onEditorGainFocus(LLUICtrl* caller, void *userdata); + static void onEditorChangeFocus(LLUICtrl* caller, S32 direction, void *userdata); + +private: + void updateText(); + void reportInvalidData(); + +private: + const LLFontGL* mFont; + BOOL mShowText; + BOOL mCanEditText; + + S32 mPrecision; + LLTextBox* mLabelBox; + S32 mLabelWidth; + + F32 mValue; + LLSlider* mSlider; + LLLineEditor* mEditor; + LLTextBox* mTextBox; + + LLColor4 mTextEnabledColor; + LLColor4 mTextDisabledColor; + + void (*mSliderMouseUpCallback)( LLUICtrl* ctrl, void* userdata ); + void (*mSliderMouseDownCallback)( LLUICtrl* ctrl, void* userdata ); +}; + +#endif // LL_LLSLIDERCTRL_H diff --git a/indra/llui/llspinctrl.cpp b/indra/llui/llspinctrl.cpp new file mode 100644 index 0000000000..332372011e --- /dev/null +++ b/indra/llui/llspinctrl.cpp @@ -0,0 +1,509 @@ +/** + * @file llspinctrl.cpp + * @brief LLSpinCtrl base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llspinctrl.h" + +#include "llgl.h" +#include "llui.h" +#include "lluiconstants.h" + +#include "llstring.h" +#include "llfontgl.h" +#include "lllineeditor.h" +#include "llbutton.h" +#include "lltextbox.h" +#include "llkeyboard.h" +#include "llmath.h" +#include "sound_ids.h" +#include "audioengine.h" +#include "llcontrol.h" +#include "llfocusmgr.h" +#include "llresmgr.h" + +const U32 MAX_STRING_LENGTH = 32; + + +LLSpinCtrl::LLSpinCtrl( const LLString& name, const LLRect& rect, const LLString& label, const LLFontGL* font, + void (*commit_callback)(LLUICtrl*, void*), + void* callback_user_data, + F32 initial_value, F32 min_value, F32 max_value, F32 increment, + const LLString& control_name, + S32 label_width) + : + LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data, FOLLOWS_LEFT | FOLLOWS_TOP ), + mValue( initial_value ), + mInitialValue( initial_value ), + mMaxValue( max_value ), + mMinValue( min_value ), + mIncrement( increment ), + mPrecision( 3 ), + mLabelBox( NULL ), + mTextEnabledColor( LLUI::sColorsGroup->getColor( "LabelTextColor" ) ), + mTextDisabledColor( LLUI::sColorsGroup->getColor( "LabelDisabledColor" ) ), + mbHasBeenSet( FALSE ) +{ + S32 top = mRect.getHeight(); + S32 bottom = top - 2 * SPINCTRL_BTN_HEIGHT; + S32 centered_top = top; + S32 centered_bottom = bottom; + S32 btn_left = 0; + + // Label + if( !label.empty() ) + { + LLRect label_rect( 0, centered_top, label_width, centered_bottom ); + mLabelBox = new LLTextBox( "SpinCtrl Label", label_rect, label.c_str(), font ); + addChild(mLabelBox); + + btn_left += label_rect.mRight + SPINCTRL_SPACING; + } + + S32 btn_right = btn_left + SPINCTRL_BTN_WIDTH; + + // Spin buttons + LLRect up_rect( btn_left, top, btn_right, top - SPINCTRL_BTN_HEIGHT ); + LLString out_id = "UIImgBtnSpinUpOutUUID"; + LLString in_id = "UIImgBtnSpinUpInUUID"; + mUpBtn = new LLButton( + "SpinCtrl Up", up_rect, + out_id, + in_id, + "", + &LLSpinCtrl::onUpBtn, this, LLFontGL::sSansSerif ); + mUpBtn->setFollowsLeft(); + mUpBtn->setFollowsBottom(); + mUpBtn->setHeldDownCallback( &LLSpinCtrl::onUpBtn ); + mUpBtn->setTabStop(FALSE); + addChild(mUpBtn); + + LLRect down_rect( btn_left, top - SPINCTRL_BTN_HEIGHT, btn_right, bottom ); + out_id = "UIImgBtnSpinDownOutUUID"; + in_id = "UIImgBtnSpinDownInUUID"; + mDownBtn = new LLButton( + "SpinCtrl Down", down_rect, + out_id, + in_id, + "", + &LLSpinCtrl::onDownBtn, this, LLFontGL::sSansSerif ); + mDownBtn->setFollowsLeft(); + mDownBtn->setFollowsBottom(); + mDownBtn->setHeldDownCallback( &LLSpinCtrl::onDownBtn ); + mDownBtn->setTabStop(FALSE); + addChild(mDownBtn); + + LLRect editor_rect( btn_right + 1, centered_top, mRect.getWidth(), centered_bottom ); + mEditor = new LLLineEditor( "SpinCtrl Editor", editor_rect, "", font, + MAX_STRING_LENGTH, + &LLSpinCtrl::onEditorCommit, NULL, NULL, this, + &LLLineEditor::prevalidateFloat ); + mEditor->setFollowsLeft(); + mEditor->setFollowsBottom(); + mEditor->setFocusReceivedCallback( &LLSpinCtrl::onEditorGainFocus ); + //RN: this seems to be a BAD IDEA, as it makes the editor behavior different when it has focus + // than when it doesn't. Instead, if you always have to double click to select all the text, + // it's easier to understand + //mEditor->setSelectAllonFocusReceived(TRUE); + mEditor->setIgnoreTab(TRUE); + addChild(mEditor); + + updateEditor(); + setSpanChildren( TRUE ); +} + +LLSpinCtrl::~LLSpinCtrl() +{ + // Children all cleaned up by default view destructor. +} + + +// static +void LLSpinCtrl::onUpBtn( void *userdata ) +{ + LLSpinCtrl* self = (LLSpinCtrl*) userdata; + if( self->getEnabled() ) + { + // use getValue()/setValue() to force reload from/to control + F32 val = (F32)self->getValue().asReal() + self->mIncrement; + val = llmin( val, self->mMaxValue ); + + if( self->mValidateCallback ) + { + F32 saved_val = (F32)self->getValue().asReal(); + self->setValue(val); + if( !self->mValidateCallback( self, self->mCallbackUserData ) ) + { + self->setValue( saved_val ); + self->reportInvalidData(); + self->updateEditor(); + return; + } + } + else + { + self->setValue(val); + } + + self->updateEditor(); + self->onCommit(); + } +} + +// static +void LLSpinCtrl::onDownBtn( void *userdata ) +{ + LLSpinCtrl* self = (LLSpinCtrl*) userdata; + + if( self->getEnabled() ) + { + F32 val = (F32)self->getValue().asReal() - self->mIncrement; + val = llmax( val, self->mMinValue ); + + if( self->mValidateCallback ) + { + F32 saved_val = (F32)self->getValue().asReal(); + self->setValue(val); + if( !self->mValidateCallback( self, self->mCallbackUserData ) ) + { + self->setValue( saved_val ); + self->reportInvalidData(); + self->updateEditor(); + return; + } + } + else + { + self->setValue(val); + } + + self->updateEditor(); + self->onCommit(); + } +} + +// static +void LLSpinCtrl::onEditorGainFocus( LLUICtrl* caller, void *userdata ) +{ + LLSpinCtrl* self = (LLSpinCtrl*) userdata; + llassert( caller == self->mEditor ); + + self->onFocusReceived(); +} + +void LLSpinCtrl::setValue(const LLSD& value ) +{ + F32 v = (F32)value.asReal(); + if (mValue != v || !mbHasBeenSet) + { + mbHasBeenSet = TRUE; + mValue = v; + + if (!mEditor->hasFocus()) + { + updateEditor(); + } + } +} + +LLSD LLSpinCtrl::getValue() const +{ + return mValue; +} + +void LLSpinCtrl::clear() +{ + setValue(mMinValue); + mEditor->clear(); + mbHasBeenSet = FALSE; +} + + +void LLSpinCtrl::updateEditor() +{ + LLLocale locale(LLLocale::USER_LOCALE); + + // Don't display very small negative values as -0.000 + F32 displayed_value = (F32)floor(getValue().asReal() * pow(10, mPrecision) + 0.5) / (F32)pow(10, mPrecision); + +// if( S32( displayed_value * pow( 10, mPrecision ) ) == 0 ) +// { +// displayed_value = 0.f; +// } + + LLString format = llformat("%%.%df", mPrecision); + LLString text = llformat(format.c_str(), displayed_value); + mEditor->setText( text ); +} + +void LLSpinCtrl::onEditorCommit( LLUICtrl* caller, void *userdata ) +{ + BOOL success = FALSE; + + LLSpinCtrl* self = (LLSpinCtrl*) userdata; + llassert( caller == self->mEditor ); + + LLString text = self->mEditor->getText(); + if( LLLineEditor::postvalidateFloat( text ) ) + { + LLLocale locale(LLLocale::USER_LOCALE); + F32 val = (F32) atof(text.c_str()); + + if (val < self->mMinValue) val = self->mMinValue; + if (val > self->mMaxValue) val = self->mMaxValue; + + if( self->mValidateCallback ) + { + F32 saved_val = self->mValue; + self->mValue = val; + if( self->mValidateCallback( self, self->mCallbackUserData ) ) + { + success = TRUE; + self->onCommit(); + } + else + { + self->mValue = saved_val; + } + } + else + { + self->mValue = val; + self->onCommit(); + success = TRUE; + } + } + self->updateEditor(); + + if( !success ) + { + self->reportInvalidData(); + } +} + + +void LLSpinCtrl::forceEditorCommit() +{ + onEditorCommit(mEditor, this); +} + + +void LLSpinCtrl::setFocus(BOOL b) +{ + LLUICtrl::setFocus( b ); + mEditor->setFocus( b ); +} + +void LLSpinCtrl::setEnabled(BOOL b) +{ + LLUICtrl::setEnabled( b ); + mEditor->setEnabled( b ); +} + + +void LLSpinCtrl::setTentative(BOOL b) +{ + mEditor->setTentative(b); + LLUICtrl::setTentative(b); +} + + +BOOL LLSpinCtrl::isMouseHeldDown() +{ + return + gFocusMgr.getMouseCapture() == mDownBtn || + gFocusMgr.getMouseCapture() == mUpBtn; +} + +void LLSpinCtrl::onCommit() +{ + setTentative(FALSE); + + setControlValue(mValue); + + LLUICtrl::onCommit(); +} + + +void LLSpinCtrl::setPrecision(S32 precision) +{ + if (precision < 0 || precision > 10) + { + llerrs << "LLSpinCtrl::setPrecision - precision out of range" << llendl; + return; + } + + mPrecision = precision; + updateEditor(); +} + +void LLSpinCtrl::setLabel(const LLString& label) +{ + if (mLabelBox) + { + mLabelBox->setText(label); + } + else + { + llwarns << "Attempting to set label on LLSpinCtrl constructed without one " << getName() << llendl; + } +} + +void LLSpinCtrl::onTabInto() +{ + mEditor->onTabInto(); +} + + +void LLSpinCtrl::reportInvalidData() +{ + make_ui_sound("UISndBadKeystroke"); +} + +void LLSpinCtrl::draw() +{ + if( mLabelBox ) + { + mLabelBox->setColor( mEnabled ? mTextEnabledColor : mTextDisabledColor ); + } + LLUICtrl::draw(); +} + + +BOOL LLSpinCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + if( mEnabled ) + { + if( clicks > 0 ) + { + while( clicks-- ) + { + LLSpinCtrl::onDownBtn(this); + } + } + else + while( clicks++ ) + { + LLSpinCtrl::onUpBtn(this); + } + } + + return TRUE; +} + +BOOL LLSpinCtrl::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + if (mEditor->hasFocus()) + { + if(key == KEY_ESCAPE) + { + // text editors don't support revert normally (due to user confusion) + // but not allowing revert on a spinner seems dangerous + updateEditor(); + mEditor->setFocus(FALSE); + return TRUE; + } + if(key == KEY_UP) + { + LLSpinCtrl::onUpBtn(this); + return TRUE; + } + if(key == KEY_DOWN) + { + LLSpinCtrl::onDownBtn(this); + return TRUE; + } + } + return FALSE; +} + +// virtual +LLXMLNodePtr LLSpinCtrl::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + node->createChild("decimal_digits", TRUE)->setIntValue(mPrecision); + + if (mLabelBox) + { + node->createChild("label", TRUE)->setStringValue(mLabelBox->getText()); + + node->createChild("label_width", TRUE)->setIntValue(mLabelBox->getRect().getWidth()); + } + + node->createChild("initial_val", TRUE)->setFloatValue(mInitialValue); + + node->createChild("min_val", TRUE)->setFloatValue(mMinValue); + + node->createChild("max_val", TRUE)->setFloatValue(mMaxValue); + + node->createChild("increment", TRUE)->setFloatValue(mIncrement); + + addColorXML(node, mTextEnabledColor, "text_enabled_color", "LabelTextColor"); + addColorXML(node, mTextDisabledColor, "text_disabled_color", "LabelDisabledColor"); + + return node; +} + +LLView* LLSpinCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("spinner"); + node->getAttributeString("name", name); + + LLString label; + node->getAttributeString("label", label); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + LLFontGL* font = LLView::selectFont(node); + + F32 initial_value = 0.f; + node->getAttributeF32("initial_val", initial_value); + + F32 min_value = 0.f; + node->getAttributeF32("min_val", min_value); + + F32 max_value = 1.f; + node->getAttributeF32("max_val", max_value); + + F32 increment = 0.1f; + node->getAttributeF32("increment", increment); + + U32 precision = 3; + node->getAttributeU32("decimal_digits", precision); + + S32 label_width = llmin(40, rect.getWidth() - 40); + node->getAttributeS32("label_width", label_width); + + LLUICtrlCallback callback = NULL; + + if(label.empty()) + { + label.assign( node->getValue() ); + } + + LLSpinCtrl* spinner = new LLSpinCtrl(name, + rect, + label, + font, + callback, + NULL, + initial_value, + min_value, + max_value, + increment, + "", + label_width); + + spinner->setPrecision(precision); + + spinner->initFromXML(node, parent); + + return spinner; +} diff --git a/indra/llui/llspinctrl.h b/indra/llui/llspinctrl.h new file mode 100644 index 0000000000..d6ccd4d6bf --- /dev/null +++ b/indra/llui/llspinctrl.h @@ -0,0 +1,121 @@ +/** + * @file llspinctrl.h + * @brief LLSpinCtrl base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSPINCTRL_H +#define LL_LLSPINCTRL_H + + +#include "stdtypes.h" +#include "lluictrl.h" +#include "v4color.h" +#include "llrect.h" + +// +// Constants +// +const S32 SPINCTRL_BTN_HEIGHT = 8; +const S32 SPINCTRL_BTN_WIDTH = 16; +const S32 SPINCTRL_SPACING = 2; // space between label right and button left +const S32 SPINCTRL_HEIGHT = 2 * SPINCTRL_BTN_HEIGHT; +const S32 SPINCTRL_DEFAULT_LABEL_WIDTH = 10; + +// +// Classes +// +class LLFontGL; +class LLButton; +class LLTextBox; +class LLLineEditor; + + +class LLSpinCtrl +: public LLUICtrl +{ +public: + LLSpinCtrl(const LLString& name, const LLRect& rect, + const LLString& label, + const LLFontGL* font, + void (*commit_callback)(LLUICtrl*, void*), + void* callback_userdata, + F32 initial_value, F32 min_value, F32 max_value, F32 increment, + const LLString& control_name = LLString(), + S32 label_width = SPINCTRL_DEFAULT_LABEL_WIDTH ); + + virtual ~LLSpinCtrl(); + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_SPINNER; } + virtual LLString getWidgetTag() const { return LL_SPIN_CTRL_TAG; } + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + F32 get() { return (F32)getValue().asReal(); } + void set(F32 value) { setValue(value); } + + virtual void setMinValue(LLSD min_value) { setMinValue((F32)min_value.asReal()); } + virtual void setMaxValue(LLSD max_value) { setMaxValue((F32)max_value.asReal()); } + + BOOL isMouseHeldDown(); + + virtual void setEnabled( BOOL b ); + virtual void setFocus( BOOL b ); + virtual void clear(); + virtual void setPrecision(S32 precision); + virtual void setMinValue(F32 min) { mMinValue = min; } + virtual void setMaxValue(F32 max) { mMaxValue = max; } + virtual void setIncrement(F32 inc) { mIncrement = inc; } + + void setLabel(const LLString& label); + void setLabelColor(const LLColor4& c) { mTextEnabledColor = c; } + void setDisabledLabelColor(const LLColor4& c) { mTextDisabledColor = c; } + + virtual void onTabInto(); + + virtual void setTentative(BOOL b); // marks value as tentative + virtual void onCommit(); // mark not tentative, then commit + + void forceEditorCommit(); // for commit on external button + + virtual BOOL handleScrollWheel(S32 x,S32 y,S32 clicks); + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + + virtual void draw(); + + static void onEditorCommit(LLUICtrl* caller, void* userdata); + static void onEditorGainFocus(LLUICtrl* caller, void *userdata); + static void onEditorChangeFocus(LLUICtrl* caller, S32 direction, void *userdata); + + static void onUpBtn(void *userdata); + static void onDownBtn(void *userdata); + +protected: + void updateEditor(); + void reportInvalidData(); + +protected: + + F32 mValue; + F32 mInitialValue; + F32 mMaxValue; + F32 mMinValue; + F32 mIncrement; + + S32 mPrecision; + LLTextBox* mLabelBox; + + LLLineEditor* mEditor; + LLColor4 mTextEnabledColor; + LLColor4 mTextDisabledColor; + + LLButton* mUpBtn; + LLButton* mDownBtn; + + BOOL mbHasBeenSet; +}; + +#endif // LL_LLSPINCTRL_H diff --git a/indra/llui/llstyle.cpp b/indra/llui/llstyle.cpp new file mode 100644 index 0000000000..e03dd987d7 --- /dev/null +++ b/indra/llui/llstyle.cpp @@ -0,0 +1,223 @@ +/** + * @file llstyle.cpp + * @brief Text style class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llstyle.h" +#include "llstring.h" +#include "llui.h" + +//#include "llviewerimagelist.h" + +LLStyle::LLStyle() +{ + init(TRUE, LLColor4(0,0,0,1),""); +} + +LLStyle::LLStyle(const LLStyle &style) +{ + if (this != &style) + { + init(style.isVisible(),style.getColor(),style.getFontString()); + if (style.isLink()) + { + setLinkHREF(style.getLinkHREF()); + } + mItalic = style.mItalic; + mBold = style.mBold; + mUnderline = style.mUnderline; + mDropShadow = style.mDropShadow; + mImageHeight = style.mImageHeight; + mImageWidth = style.mImageWidth; + mImagep = style.mImagep; + mIsEmbeddedItem = style.mIsEmbeddedItem; + } + else + { + init(TRUE, LLColor4(0,0,0,1),""); + } +} + +LLStyle::LLStyle(BOOL is_visible, const LLColor4 &color, const LLString& font_name) +{ + init(is_visible, color, font_name); +} + +LLStyle::~LLStyle() +{ + free(); +} + +void LLStyle::init(BOOL is_visible, const LLColor4 &color, const LLString& font_name) +{ + mVisible = is_visible; + mColor = color; + setFontName(font_name); + setLinkHREF(""); + mItalic = FALSE; + mBold = FALSE; + mUnderline = FALSE; + mDropShadow = FALSE; + mImageHeight = 0; + mImageWidth = 0; + mIsEmbeddedItem = FALSE; +} + +void LLStyle::free() +{ +} + +LLFONT_ID LLStyle::getFontID() const +{ + return mFontID; +} + +// Copy assignment +LLStyle &LLStyle::operator=(const LLStyle &rhs) +{ + if (this != &rhs) + { + setVisible(rhs.isVisible()); + setColor(rhs.getColor()); + this->mFontName = rhs.getFontString(); + this->mLink = rhs.getLinkHREF(); + mImagep = rhs.mImagep; + mImageHeight = rhs.mImageHeight; + mImageWidth = rhs.mImageWidth; + mItalic = rhs.mItalic; + mBold = rhs.mBold; + mUnderline = rhs.mUnderline; + mDropShadow = rhs.mUnderline; + mIsEmbeddedItem = rhs.mIsEmbeddedItem; + } + + return *this; +} + +// Compare +bool LLStyle::operator==(const LLStyle &rhs) const +{ + if ((mVisible != rhs.isVisible()) + || (mColor != rhs.getColor()) + || (mFontName != rhs.getFontString()) + || (mLink != rhs.getLinkHREF()) + || (mImagep != rhs.mImagep) + || (mImageHeight != rhs.mImageHeight) + || (mImageWidth != rhs.mImageWidth) + || (mItalic != rhs.mItalic) + || (mBold != rhs.mBold) + || (mUnderline != rhs.mUnderline) + || (mDropShadow != rhs.mDropShadow) + || (mIsEmbeddedItem != rhs.mIsEmbeddedItem) + ) + { + return FALSE; + } + return TRUE; +} + +bool LLStyle::operator!=(const LLStyle& rhs) const +{ + return !(*this == rhs); +} + + +const LLColor4& LLStyle::getColor() const +{ + return(mColor); +} + +void LLStyle::setColor(const LLColor4 &color) +{ + mColor = color; +} + +const LLString& LLStyle::getFontString() const +{ + return mFontName; +} + +void LLStyle::setFontName(const LLString& fontname) +{ + mFontName = fontname; + + LLString fontname_lc = fontname; + LLString::toLower(fontname_lc); + + mFontID = LLFONT_OCRA; // default + + if ((fontname_lc == "sansserif") || (fontname_lc == "sans-serif")) + { + mFontID = LLFONT_SANSSERIF; + } + else if ((fontname_lc == "serif")) + { + mFontID = LLFONT_SMALL; + } + else if ((fontname_lc == "sansserifbig")) + { + mFontID = LLFONT_SANSSERIF_BIG; + } + else if (fontname_lc == "small") + { + mFontID = LLFONT_SANSSERIF_SMALL; + } +} + +const LLString& LLStyle::getLinkHREF() const +{ + return mLink; +} + +void LLStyle::setLinkHREF(const LLString& href) +{ + mLink = href; +} + +BOOL LLStyle::isLink() const +{ + return mLink.size(); +} + +BOOL LLStyle::isVisible() const +{ + return mVisible; +} + +void LLStyle::setVisible(BOOL is_visible) +{ + mVisible = is_visible; +} + +LLImageGL *LLStyle::getImage() const +{ + return mImagep; +} + +void LLStyle::setImage(const LLString& src) +{ + if (src.size() < UUID_STR_LENGTH - 1) + { + return; + } + else + { + mImagep = LLUI::sImageProvider->getUIImageByID(LLUUID(src)); + } +} + +BOOL LLStyle::isImage() const +{ + return ((mImageWidth != 0) && (mImageHeight != 0)); +} + +void LLStyle::setImageSize(S32 width, S32 height) +{ + mImageWidth = width; + mImageHeight = height; +} diff --git a/indra/llui/llstyle.h b/indra/llui/llstyle.h new file mode 100644 index 0000000000..6a689dab98 --- /dev/null +++ b/indra/llui/llstyle.h @@ -0,0 +1,75 @@ +/** + * @file llstyle.h + * @brief Text style class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSTYLE_H +#define LL_LLSTYLE_H + +#include "v4color.h" +#include "llresmgr.h" +#include "llfont.h" +#include "llimagegl.h" + +class LLStyle +{ +public: + LLStyle(); + LLStyle(const LLStyle &style); + LLStyle(BOOL is_visible, const LLColor4 &color, const LLString& font_name); + + LLStyle &operator=(const LLStyle &rhs); + + virtual ~LLStyle(); + + virtual void init (BOOL is_visible, const LLColor4 &color, const LLString& font_name); + virtual void free (); + + bool operator==(const LLStyle &rhs) const; + bool operator!=(const LLStyle &rhs) const; + + virtual const LLColor4& getColor() const; + virtual void setColor(const LLColor4 &color); + + virtual BOOL isVisible() const; + virtual void setVisible(BOOL is_visible); + + virtual const LLString& getFontString() const; + virtual void setFontName(const LLString& fontname); + virtual LLFONT_ID getFontID() const; + + virtual const LLString& getLinkHREF() const; + virtual void setLinkHREF(const LLString& fontname); + virtual BOOL isLink() const; + + virtual LLImageGL *getImage() const; + virtual void setImage(const LLString& src); + virtual BOOL isImage() const; + virtual void setImageSize(S32 width, S32 height); + + BOOL getIsEmbeddedItem() const { return mIsEmbeddedItem; } + void setIsEmbeddedItem( BOOL b ) { mIsEmbeddedItem = b; } + +public: + BOOL mItalic; + BOOL mBold; + BOOL mUnderline; + BOOL mDropShadow; + S32 mImageWidth; + S32 mImageHeight; + +protected: + BOOL mVisible; + LLColor4 mColor; + LLString mFontName; + LLFONT_ID mFontID; + LLString mLink; + LLPointer mImagep; + + BOOL mIsEmbeddedItem; +}; + +#endif // LL_LLSTYLE_H diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp new file mode 100644 index 0000000000..fd85dbb2f4 --- /dev/null +++ b/indra/llui/lltabcontainer.cpp @@ -0,0 +1,1469 @@ +/** + * @file lltabcontainer.cpp + * @brief LLTabContainerCommon base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lltabcontainer.h" + +#include "llfocusmgr.h" +#include "llfontgl.h" +#include "llgl.h" + +#include "llbutton.h" +#include "llrect.h" +#include "llpanel.h" +#include "llresmgr.h" +#include "llkeyboard.h" +#include "llresizehandle.h" +#include "llui.h" +#include "lltextbox.h" +#include "llcontrol.h" +#include "llcriticaldamp.h" +#include "lluictrlfactory.h" + +#include "lltabcontainervertical.h" + +#include "llglheaders.h" + +const F32 SCROLL_STEP_TIME = 0.4f; +const S32 TAB_PADDING = 15; +const S32 TABCNTR_TAB_MIN_WIDTH = 60; +const S32 TABCNTR_TAB_MAX_WIDTH = 150; +const S32 TABCNTR_TAB_PARTIAL_WIDTH = 12; // When tabs are parially obscured, how much can you still see. +const S32 TABCNTR_TAB_HEIGHT = 16; +const S32 TABCNTR_ARROW_BTN_SIZE = 16; +const S32 TABCNTR_BUTTON_PANEL_OVERLAP = 1; // how many pixels the tab buttons and tab panels overlap. +const S32 TABCNTR_TAB_H_PAD = 4; + + +LLTabContainerCommon::LLTabContainerCommon( + const LLString& name, const LLRect& rect, + TabPosition pos, + void(*close_callback)(void*), void* callback_userdata, + BOOL bordered ) + : + LLPanel(name, rect, bordered), + mCurrentTabIdx(-1), + mScrolled(FALSE), + mScrollPos(0), + mScrollPosPixels(0), + mMaxScrollPos(0), + mCloseCallback( close_callback ), + mCallbackUserdata( callback_userdata ), + mTitleBox(NULL), + mTopBorderHeight(LLPANEL_BORDER_WIDTH), + mTabPosition(pos) +{ + setMouseOpaque(FALSE); +} + + +LLTabContainerCommon::LLTabContainerCommon( + const LLString& name, + const LLString& rect_control, + TabPosition pos, + void(*close_callback)(void*), void* callback_userdata, + BOOL bordered ) + : + LLPanel(name, rect_control, bordered), + mCurrentTabIdx(-1), + mScrolled(FALSE), + mScrollPos(0), + mScrollPosPixels(0), + mMaxScrollPos(0), + mCloseCallback( close_callback ), + mCallbackUserdata( callback_userdata ), + mTitleBox(NULL), + mTopBorderHeight(LLPANEL_BORDER_WIDTH), + mTabPosition(pos) +{ + setMouseOpaque(FALSE); +} + + +LLTabContainerCommon::~LLTabContainerCommon() +{ + std::for_each(mTabList.begin(), mTabList.end(), DeletePointer()); +} + +LLView* LLTabContainerCommon::getChildByName(const LLString& name, BOOL recurse) const +{ + tuple_list_t::const_iterator itor; + for (itor = mTabList.begin(); itor != mTabList.end(); ++itor) + { + LLPanel *panel = (*itor)->mTabPanel; + if (panel->getName() == name) + { + return panel; + } + } + if (recurse) + { + for (itor = mTabList.begin(); itor != mTabList.end(); ++itor) + { + LLPanel *panel = (*itor)->mTabPanel; + LLView *child = panel->getChildByName(name, recurse); + if (child) + { + return child; + } + } + } + return LLView::getChildByName(name, recurse); +} + +void LLTabContainerCommon::addPlaceholder(LLPanel* child, const LLString& label) +{ + addTabPanel(child, label, FALSE, NULL, NULL, 0, TRUE); +} + +void LLTabContainerCommon::removeTabPanel(LLPanel* child) +{ + BOOL has_focus = gFocusMgr.childHasKeyboardFocus(this); + + // If the tab being deleted is the selected one, select a different tab. + for(std::vector::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + if( tuple->mTabPanel == child ) + { + removeChild( tuple->mButton ); + delete tuple->mButton; + + removeChild( tuple->mTabPanel ); +// delete tuple->mTabPanel; + + mTabList.erase( iter ); + delete tuple; + + break; + } + } + if (mCurrentTabIdx >= (S32)mTabList.size()) + { + mCurrentTabIdx = mTabList.size()-1; + } + selectTab(mCurrentTabIdx); + if (has_focus) + { + LLPanel* panelp = getPanelByIndex(mCurrentTabIdx); + if (panelp) + { + panelp->setFocus(TRUE); + } + } + + updateMaxScrollPos(); +} + +void LLTabContainerCommon::deleteAllTabs() +{ + // Remove all the tab buttons and delete them. Also, unlink all the child panels. + for(std::vector::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + + removeChild( tuple->mButton ); + delete tuple->mButton; + + removeChild( tuple->mTabPanel ); +// delete tuple->mTabPanel; + } + + // Actually delete the tuples themselves + std::for_each(mTabList.begin(), mTabList.end(), DeletePointer()); + mTabList.clear(); + + // And there isn't a current tab any more + mCurrentTabIdx = -1; +} + + +LLPanel* LLTabContainerCommon::getCurrentPanel() +{ + if (mCurrentTabIdx < 0 || mCurrentTabIdx >= (S32) mTabList.size()) return NULL; + + return mTabList[mCurrentTabIdx]->mTabPanel; +} + +LLTabContainerCommon::LLTabTuple* LLTabContainerCommon::getTabByPanel(LLPanel* child) +{ + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + if( tuple->mTabPanel == child ) + { + return tuple; + } + } + return NULL; +} + +void LLTabContainerCommon::setTabChangeCallback(LLPanel* tab, void (*on_tab_clicked)(void*, bool)) +{ + LLTabTuple* tuplep = getTabByPanel(tab); + if (tuplep) + { + tuplep->mOnChangeCallback = on_tab_clicked; + } +} + +void LLTabContainerCommon::setTabUserData(LLPanel* tab, void* userdata) +{ + LLTabTuple* tuplep = getTabByPanel(tab); + if (tuplep) + { + tuplep->mUserData = userdata; + } +} + +S32 LLTabContainerCommon::getCurrentPanelIndex() +{ + return mCurrentTabIdx; +} + +S32 LLTabContainerCommon::getTabCount() +{ + return mTabList.size(); +} + +LLPanel* LLTabContainerCommon::getPanelByIndex(S32 index) +{ + if (index >= 0 && index < (S32)mTabList.size()) + { + return mTabList[index]->mTabPanel; + } + return NULL; +} + +S32 LLTabContainerCommon::getIndexForPanel(LLPanel* panel) +{ + for (S32 index = 0; index < (S32)mTabList.size(); index++) + { + if (mTabList[index]->mTabPanel == panel) + { + return index; + } + } + return -1; +} + +S32 LLTabContainerCommon::getPanelIndexByTitle(const LLString& title) +{ + for (S32 index = 0 ; index < (S32)mTabList.size(); index++) + { + if (title == mTabList[index]->mButton->getLabelSelected()) + { + return index; + } + } + return -1; +} + +LLPanel *LLTabContainerCommon::getPanelByName(const LLString& name) +{ + for (S32 index = 0 ; index < (S32)mTabList.size(); index++) + { + LLPanel *panel = mTabList[index]->mTabPanel; + if (name == panel->getName()) + { + return panel; + } + } + return NULL; +} + + +void LLTabContainerCommon::scrollNext() +{ + // No wrap + if( mScrollPos < mMaxScrollPos ) + { + mScrollPos++; + } +} + +void LLTabContainerCommon::scrollPrev() +{ + // No wrap + if( mScrollPos > 0 ) + { + mScrollPos--; + } +} + +void LLTabContainerCommon::enableTabButton(S32 which, BOOL enable) +{ + if (which >= 0 && which < (S32)mTabList.size()) + { + mTabList[which]->mButton->setEnabled(enable); + } +} + +BOOL LLTabContainerCommon::selectTabPanel(LLPanel* child) +{ + S32 idx = 0; + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + if( tuple->mTabPanel == child ) + { + return selectTab( idx ); + } + idx++; + } + return FALSE; +} + +BOOL LLTabContainerCommon::selectTabByName(const LLString& name) +{ + LLPanel* panel = getPanelByName(name); + if (!panel) + { + llwarns << "LLTabContainerCommon::selectTabByName(" + << name << ") failed" << llendl; + return FALSE; + } + + BOOL result = selectTabPanel(panel); + return result; +} + + +void LLTabContainerCommon::selectFirstTab() +{ + selectTab( 0 ); +} + + +void LLTabContainerCommon::selectLastTab() +{ + selectTab( mTabList.size()-1 ); +} + + +void LLTabContainerCommon::selectNextTab() +{ + BOOL tab_has_focus = FALSE; + if (mCurrentTabIdx >= 0 && mTabList[mCurrentTabIdx]->mButton->hasFocus()) + { + tab_has_focus = TRUE; + } + S32 idx = mCurrentTabIdx+1; + if (idx >= (S32)mTabList.size()) + idx = 0; + while (!selectTab(idx) && idx != mCurrentTabIdx) + { + idx = (idx + 1 ) % (S32)mTabList.size(); + } + + if (tab_has_focus) + { + mTabList[idx]->mButton->setFocus(TRUE); + } +} + +void LLTabContainerCommon::selectPrevTab() +{ + BOOL tab_has_focus = FALSE; + if (mCurrentTabIdx >= 0 && mTabList[mCurrentTabIdx]->mButton->hasFocus()) + { + tab_has_focus = TRUE; + } + S32 idx = mCurrentTabIdx-1; + if (idx < 0) + idx = mTabList.size()-1; + while (!selectTab(idx) && idx != mCurrentTabIdx) + { + idx = idx - 1; + if (idx < 0) + idx = mTabList.size()-1; + } + if (tab_has_focus) + { + mTabList[idx]->mButton->setFocus(TRUE); + } +} + + +void LLTabContainerCommon::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLPanel::reshape( width, height, called_from_parent ); + updateMaxScrollPos(); +} + +// static +void LLTabContainerCommon::onTabBtn( void* userdata ) +{ + LLTabTuple* tuple = (LLTabTuple*) userdata; + LLTabContainerCommon* self = tuple->mTabContainer; + self->selectTabPanel( tuple->mTabPanel ); + + if( tuple->mOnChangeCallback ) + { + tuple->mOnChangeCallback( tuple->mUserData, true ); + } + + tuple->mTabPanel->setFocus(TRUE); +} + +// static +void LLTabContainerCommon::onCloseBtn( void* userdata ) +{ + LLTabContainer* self = (LLTabContainer*) userdata; + if( self->mCloseCallback ) + { + self->mCloseCallback( self->mCallbackUserdata ); + } +} + +// static +void LLTabContainerCommon::onNextBtn( void* userdata ) +{ + // Scroll tabs to the left + LLTabContainer* self = (LLTabContainer*) userdata; + if (!self->mScrolled) + { + self->scrollNext(); + } + self->mScrolled = FALSE; +} + +// static +void LLTabContainerCommon::onNextBtnHeld( void* userdata ) +{ + LLTabContainer* self = (LLTabContainer*) userdata; + if (self->mScrollTimer.getElapsedTimeF32() > SCROLL_STEP_TIME) + { + self->mScrollTimer.reset(); + self->scrollNext(); + self->mScrolled = TRUE; + } +} + +// static +void LLTabContainerCommon::onPrevBtn( void* userdata ) +{ + LLTabContainer* self = (LLTabContainer*) userdata; + if (!self->mScrolled) + { + self->scrollPrev(); + } + self->mScrolled = FALSE; +} + +// static +void LLTabContainerCommon::onPrevBtnHeld( void* userdata ) +{ + LLTabContainer* self = (LLTabContainer*) userdata; + if (self->mScrollTimer.getElapsedTimeF32() > SCROLL_STEP_TIME) + { + self->mScrollTimer.reset(); + self->scrollPrev(); + self->mScrolled = TRUE; + } +} + +BOOL LLTabContainerCommon::getTabPanelFlashing(LLPanel *child) +{ + LLTabTuple* tuple = getTabByPanel(child); + if( tuple ) + { + return tuple->mButton->getFlashing(); + } + return FALSE; +} + +void LLTabContainerCommon::setTabPanelFlashing(LLPanel* child, BOOL state ) +{ + LLTabTuple* tuple = getTabByPanel(child); + if( tuple ) + { + tuple->mButton->setFlashing( state ); + } +} + +void LLTabContainerCommon::setTitle(const LLString& title) +{ + if (mTitleBox) + { + mTitleBox->setText( title ); + } +} + +const LLString LLTabContainerCommon::getPanelTitle(S32 index) +{ + if (index >= 0 && index < (S32)mTabList.size()) + { + LLButton* tab_button = mTabList[index]->mButton; + return tab_button->getLabelSelected(); + } + return LLString::null; +} + +void LLTabContainerCommon::setTopBorderHeight(S32 height) +{ + mTopBorderHeight = height; +} + +// Change the name of the button for the current tab. +void LLTabContainerCommon::setCurrentTabName(const LLString& name) +{ + // Might not have a tab selected + if (mCurrentTabIdx < 0) return; + + mTabList[mCurrentTabIdx]->mButton->setLabelSelected(name); + mTabList[mCurrentTabIdx]->mButton->setLabelUnselected(name); +} + +LLView* LLTabContainerCommon::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("tab_container"); + node->getAttributeString("name", name); + + // Figure out if we are creating a vertical or horizontal tab container. + bool is_vertical = false; + LLTabContainer::TabPosition tab_position = LLTabContainer::TOP; + if (node->hasAttribute("tab_position")) + { + LLString tab_position_string; + node->getAttributeString("tab_position", tab_position_string); + LLString::toLower(tab_position_string); + + if ("top" == tab_position_string) + { + tab_position = LLTabContainer::TOP; + is_vertical = false; + } + else if ("bottom" == tab_position_string) + { + tab_position = LLTabContainer::BOTTOM; + is_vertical = false; + } + else if ("left" == tab_position_string) + { + is_vertical = true; + } + } + BOOL border = FALSE; + node->getAttributeBOOL("border", border); + + // Create the correct container type. + LLTabContainerCommon* tab_container = NULL; + + if (is_vertical) + { + // Vertical tabs can specify tab width + U32 tab_width = TABCNTRV_TAB_WIDTH; + if (node->hasAttribute("tab_width")) + { + node->getAttributeU32("tab_width", tab_width); + } + + tab_container = new LLTabContainerVertical(name, + LLRect::null, + NULL, + NULL, + tab_width, + border); + + } + else // horizontal tab container + { + // Horizontal tabs can have a title (?) + LLString title(LLString::null); + if (node->hasAttribute("title")) + { + node->getAttributeString("title", title); + } + + tab_container = new LLTabContainer(name, + LLRect::null, + tab_position, + NULL, + NULL, + title, + border); + + if(node->hasAttribute("tab_min_width")) + { + S32 minTabWidth=0; + node->getAttributeS32("tab_min_width",minTabWidth); + ((LLTabContainer*)tab_container)->setMinTabWidth(minTabWidth); + } + if(node->hasAttribute("tab_max_width")) + { + S32 maxTabWidth=0; + node->getAttributeS32("tab_max_width",maxTabWidth); + ((LLTabContainer*)tab_container)->setMaxTabWidth(maxTabWidth); + } + } + + tab_container->setPanelParameters(node, parent); + + if (LLFloater::getFloaterHost()) + { + LLFloater::getFloaterHost()->setTabContainer(tab_container); + } + + //parent->addChild(tab_container); + + // Add all tab panels. + LLXMLNodePtr child; + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + LLView *control = factory->createCtrlWidget(tab_container, child); + if (control && control->isPanel()) + { + LLPanel* panelp = (LLPanel*)control; + LLString label; + child->getAttributeString("label", label); + if (label.empty()) + { + label = panelp->getLabel(); + } + BOOL placeholder = FALSE; + child->getAttributeBOOL("placeholder", placeholder); + tab_container->addTabPanel(panelp, label.c_str(), false, + NULL, NULL, 0, placeholder); + } + } + + tab_container->selectFirstTab(); + + tab_container->postBuild(); + + tab_container->initButtons(); // now that we have the correct rect + + return tab_container; +} + +void LLTabContainerCommon::insertTuple(LLTabTuple * tuple, eInsertionPoint insertion_point) +{ + switch(insertion_point) + { + case START: + // insert the new tab in the front of the list + mTabList.insert(mTabList.begin(), tuple); + break; + case RIGHT_OF_CURRENT: + // insert the new tab after the current tab + { + tuple_list_t::iterator current_iter = mTabList.begin() + mCurrentTabIdx + 1; + mTabList.insert(current_iter, tuple); + } + break; + case END: + default: + mTabList.push_back( tuple ); + } +} + + +LLTabContainer::LLTabContainer( + const LLString& name, const LLRect& rect, TabPosition pos, + void(*close_callback)(void*), void* callback_userdata, + const LLString& title, BOOL bordered ) + : + LLTabContainerCommon(name, rect, pos, close_callback, callback_userdata, bordered), + mLeftArrowBtn(NULL), + mRightArrowBtn(NULL), + mRightTabBtnOffset(0), + mMinTabWidth(TABCNTR_TAB_MIN_WIDTH), + mMaxTabWidth(TABCNTR_TAB_MAX_WIDTH), + mTotalTabWidth(0) +{ + initButtons( ); +} + +LLTabContainer::LLTabContainer( + const LLString& name, const LLString& rect_control, TabPosition pos, + void(*close_callback)(void*), void* callback_userdata, + const LLString& title, BOOL bordered ) + : + LLTabContainerCommon(name, rect_control, pos, close_callback, callback_userdata, bordered), + mLeftArrowBtn(NULL), + mRightArrowBtn(NULL), + mRightTabBtnOffset(0), + mMinTabWidth(TABCNTR_TAB_MIN_WIDTH), + mMaxTabWidth(TABCNTR_TAB_MAX_WIDTH), + mTotalTabWidth(0) +{ + initButtons( ); +} + +void LLTabContainer::initButtons() +{ + // Hack: + if (mRect.getHeight() == 0 || mLeftArrowBtn) + { + return; // Don't have a rect yet or already got called + } + + LLString out_id; + LLString in_id; + + S32 arrow_fudge = 1; // match new art better + + // tabs on bottom reserve room for resize handle (just in case) + if (mTabPosition == BOTTOM) + { + mRightTabBtnOffset = RESIZE_HANDLE_WIDTH; + } + + // Left and right scroll arrows (for when there are too many tabs to show all at once). + S32 btn_top = (mTabPosition == TOP ) ? mRect.getHeight() - mTopBorderHeight : TABCNTR_ARROW_BTN_SIZE + 1; + + LLRect left_arrow_btn_rect; + left_arrow_btn_rect.setLeftTopAndSize( LLPANEL_BORDER_WIDTH+1, btn_top + arrow_fudge, TABCNTR_ARROW_BTN_SIZE, TABCNTR_ARROW_BTN_SIZE ); + + S32 right_pad = TABCNTR_ARROW_BTN_SIZE + LLPANEL_BORDER_WIDTH + 1; + LLRect right_arrow_btn_rect; + right_arrow_btn_rect.setLeftTopAndSize( mRect.getWidth() - mRightTabBtnOffset - right_pad, + btn_top + arrow_fudge, + TABCNTR_ARROW_BTN_SIZE, TABCNTR_ARROW_BTN_SIZE ); + + out_id = "UIImgBtnScrollLeftOutUUID"; + in_id = "UIImgBtnScrollLeftInUUID"; + mLeftArrowBtn = new LLButton( + "Left Arrow", left_arrow_btn_rect, + out_id, in_id, "", + &LLTabContainer::onPrevBtn, this, LLFontGL::sSansSerif ); + mLeftArrowBtn->setHeldDownCallback(onPrevBtnHeld); + mLeftArrowBtn->setFollowsLeft(); + mLeftArrowBtn->setSaveToXML(false); + mLeftArrowBtn->setTabStop(FALSE); + addChild(mLeftArrowBtn); + + out_id = "UIImgBtnScrollRightOutUUID"; + in_id = "UIImgBtnScrollRightInUUID"; + mRightArrowBtn = new LLButton( + "Right Arrow", right_arrow_btn_rect, + out_id, in_id, "", + &LLTabContainer::onNextBtn, this, + LLFontGL::sSansSerif); + mRightArrowBtn->setFollowsRight(); + mRightArrowBtn->setHeldDownCallback(onNextBtnHeld); + mRightArrowBtn->setSaveToXML(false); + mRightArrowBtn->setTabStop(FALSE); + addChild(mRightArrowBtn); + + if( mTabPosition == TOP ) + { + mRightArrowBtn->setFollowsTop(); + mLeftArrowBtn->setFollowsTop(); + } + else + { + mRightArrowBtn->setFollowsBottom(); + mLeftArrowBtn->setFollowsBottom(); + } + + // set default tab group to be panel contents + mDefaultTabGroup = 1; +} + +LLTabContainer::~LLTabContainer() +{ +} + +void LLTabContainer::addTabPanel(LLPanel* child, + const LLString& label, + BOOL select, + void (*on_tab_clicked)(void*, bool), + void* userdata, + S32 indent, + BOOL placeholder, + eInsertionPoint insertion_point) +{ + if (child->getParent() == this) + { + // already a child of mine + return; + } + const LLFontGL* font = gResMgr->getRes( LLFONT_SANSSERIF_SMALL ); + + // Store the original label for possible xml export. + child->setLabel(label); + LLString trimmed_label = label; + LLString::trim(trimmed_label); + + S32 button_width = llclamp(font->getWidth(trimmed_label) + TAB_PADDING, mMinTabWidth, mMaxTabWidth); + + // Tab panel + S32 tab_panel_top; + S32 tab_panel_bottom; + if( LLTabContainer::TOP == mTabPosition ) + { + tab_panel_top = mRect.getHeight() - mTopBorderHeight - (TABCNTR_TAB_HEIGHT - TABCNTR_BUTTON_PANEL_OVERLAP); + tab_panel_bottom = LLPANEL_BORDER_WIDTH; + } + else + { + tab_panel_top = mRect.getHeight() - mTopBorderHeight; + tab_panel_bottom = (TABCNTR_TAB_HEIGHT - TABCNTR_BUTTON_PANEL_OVERLAP); // Run to the edge, covering up the border + } + + LLRect tab_panel_rect( + LLPANEL_BORDER_WIDTH, + tab_panel_top, + mRect.getWidth()-LLPANEL_BORDER_WIDTH, + tab_panel_bottom ); + + child->setFollowsAll(); + child->translate( tab_panel_rect.mLeft - child->getRect().mLeft, tab_panel_rect.mBottom - child->getRect().mBottom); + child->reshape( tab_panel_rect.getWidth(), tab_panel_rect.getHeight(), TRUE ); + child->setBackgroundVisible( FALSE ); // No need to overdraw + // add this child later + + child->setVisible( FALSE ); // Will be made visible when selected + + mTotalTabWidth += button_width; + + // Tab button + LLRect btn_rect; // Note: btn_rect.mLeft is just a dummy. Will be updated in draw(). + LLString tab_img; + LLString tab_selected_img; + S32 tab_fudge = 1; // To make new tab art look better, nudge buttons up 1 pel + + if( LLTabContainer::TOP == mTabPosition ) + { + btn_rect.setLeftTopAndSize( 0, mRect.getHeight() - mTopBorderHeight + tab_fudge, button_width, TABCNTR_TAB_HEIGHT ); + tab_img = "UIImgBtnTabTopOutUUID"; + tab_selected_img = "UIImgBtnTabTopInUUID"; + } + else + { + btn_rect.setOriginAndSize( 0, 0 + tab_fudge, button_width, TABCNTR_TAB_HEIGHT ); + tab_img = "UIImgBtnTabBottomOutUUID"; + tab_selected_img = "UIImgBtnTabBottomInUUID"; + } + + if (placeholder) + { + //FIXME: wont work for horizontal tabs + btn_rect.translate(0, -LLBUTTON_V_PAD-2); + LLString box_label = trimmed_label; + LLTextBox* text = new LLTextBox(box_label, btn_rect, box_label, font); + addChild( text, 0 ); + + LLButton* btn = new LLButton("", LLRect(0,0,0,0)); + LLTabTuple* tuple = new LLTabTuple( this, child, btn, on_tab_clicked, userdata, text ); + addChild( btn, 0 ); + addChild( child, 1 ); + insertTuple(tuple, insertion_point); + } + else + { + LLString tooltip = trimmed_label; + tooltip += "\nCtrl-[ for previous tab"; + tooltip += "\nCtrl-] for next tab"; + + LLButton* btn = new LLButton( + LLString(child->getName()) + " tab", + btn_rect, + tab_img, tab_selected_img, "", + &LLTabContainer::onTabBtn, NULL, // set userdata below + font, + trimmed_label, trimmed_label ); + btn->setSaveToXML(false); + btn->setVisible( FALSE ); + btn->setToolTip( tooltip ); + btn->setScaleImage(TRUE); + btn->setFixedBorder(14, 14); + + // Try to squeeze in a bit more text + btn->setLeftHPad( 4 ); + btn->setRightHPad( 2 ); + btn->setHAlign(LLFontGL::LEFT); + btn->setTabStop(FALSE); + if (indent) + { + btn->setLeftHPad(indent); + } + + if( mTabPosition == TOP ) + { + btn->setFollowsTop(); + } + else + { + btn->setFollowsBottom(); + } + + LLTabTuple* tuple = new LLTabTuple( this, child, btn, on_tab_clicked, userdata ); + btn->setCallbackUserData( tuple ); + addChild( btn, 0 ); + addChild( child, 1 ); + insertTuple(tuple, insertion_point); + } + + updateMaxScrollPos(); + + if( select ) + { + selectLastTab(); + } +} + +void LLTabContainer::removeTabPanel(LLPanel* child) +{ + // Adjust the total tab width. + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + if( tuple->mTabPanel == child ) + { + mTotalTabWidth -= tuple->mButton->getRect().getWidth(); + break; + } + } + + LLTabContainerCommon::removeTabPanel(child); +} + +void LLTabContainer::setPanelTitle(S32 index, const LLString& title) +{ + if (index >= 0 && index < (S32)mTabList.size()) + { + LLButton* tab_button = mTabList[index]->mButton; + const LLFontGL* fontp = gResMgr->getRes( LLFONT_SANSSERIF_SMALL ); + mTotalTabWidth -= tab_button->getRect().getWidth(); + tab_button->reshape(llclamp(fontp->getWidth(title) + TAB_PADDING, mMinTabWidth, mMaxTabWidth), tab_button->getRect().getHeight()); + mTotalTabWidth += tab_button->getRect().getWidth(); + tab_button->setLabelSelected(title); + tab_button->setLabelUnselected(title); + } + updateMaxScrollPos(); +} + + +void LLTabContainer::updateMaxScrollPos() +{ + S32 tab_space = 0; + S32 available_space = 0; + tab_space = mTotalTabWidth; + available_space = mRect.getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + TABCNTR_TAB_H_PAD); + + if( tab_space > available_space ) + { + S32 available_width_with_arrows = mRect.getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + TABCNTR_ARROW_BTN_SIZE + 1); + // subtract off reserved portion on left + available_width_with_arrows -= TABCNTR_TAB_PARTIAL_WIDTH; + + S32 running_tab_width = 0; + mMaxScrollPos = mTabList.size(); + for(tuple_list_t::reverse_iterator tab_it = mTabList.rbegin(); tab_it != mTabList.rend(); ++tab_it) + { + running_tab_width += (*tab_it)->mButton->getRect().getWidth(); + if (running_tab_width > available_width_with_arrows) + { + break; + } + mMaxScrollPos--; + } + // in case last tab doesn't actually fit on screen, make it the last scrolling position + mMaxScrollPos = llmin(mMaxScrollPos, (S32)mTabList.size() - 1); + } + else + { + mMaxScrollPos = 0; + mScrollPos = 0; + } + if (mScrollPos > mMaxScrollPos) + { + mScrollPos = mMaxScrollPos; + } +} + +void LLTabContainer::commitHoveredButton(S32 x, S32 y) +{ + if (gFocusMgr.getMouseCapture() == this) + { + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->setVisible( TRUE ); + S32 local_x = x - tuple->mButton->getRect().mLeft; + S32 local_y = y - tuple->mButton->getRect().mBottom; + if (tuple->mButton->pointInView(local_x, local_y) && tuple->mButton->getEnabled() && !tuple->mTabPanel->getVisible()) + { + tuple->mButton->onCommit(); + } + } + } +} + +void LLTabContainer::setMinTabWidth(S32 width) +{ + mMinTabWidth = width; +} + +void LLTabContainer::setMaxTabWidth(S32 width) +{ + mMaxTabWidth = width; +} + +S32 LLTabContainer::getMinTabWidth() const +{ + return mMinTabWidth; +} + +S32 LLTabContainer::getMaxTabWidth() const +{ + return mMaxTabWidth; +} + +BOOL LLTabContainer::selectTab(S32 which) +{ + if (which >= (S32)mTabList.size()) return FALSE; + if (which < 0) return FALSE; + + //if( gFocusMgr.childHasKeyboardFocus( this ) ) + //{ + // gFocusMgr.setKeyboardFocus( NULL, NULL ); + //} + + LLTabTuple* selected_tuple = mTabList[which]; + if (!selected_tuple) + { + return FALSE; + } + + if (mTabList[which]->mButton->getEnabled()) + { + mCurrentTabIdx = which; + + S32 i = 0; + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + BOOL is_selected = ( tuple == selected_tuple ); + tuple->mTabPanel->setVisible( is_selected ); +// tuple->mTabPanel->setFocus(is_selected); // not clear that we want to do this here. + tuple->mButton->setToggleState( is_selected ); + // RN: this limits tab-stops to active button only, which would require arrow keys to switch tabs + tuple->mButton->setTabStop( is_selected && mTabList.size() > 1 ); + + if( is_selected && mMaxScrollPos > 0) + { + // Make sure selected tab is within scroll region + if( i < mScrollPos ) + { + mScrollPos = i; + } + else + { + S32 available_width_with_arrows = mRect.getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + TABCNTR_ARROW_BTN_SIZE + 1); + S32 running_tab_width = tuple->mButton->getRect().getWidth(); + S32 j = i - 1; + S32 min_scroll_pos = i; + if (running_tab_width < available_width_with_arrows) + { + while (j >= 0) + { + LLTabTuple* other_tuple = mTabList[j]; + running_tab_width += other_tuple->mButton->getRect().getWidth(); + if (running_tab_width > available_width_with_arrows) + { + break; + } + j--; + } + min_scroll_pos = j + 1; + } + mScrollPos = llclamp(mScrollPos, min_scroll_pos, i); + mScrollPos = llmin(mScrollPos, mMaxScrollPos); + } + } + i++; + } + if( selected_tuple->mOnChangeCallback ) + { + selected_tuple->mOnChangeCallback( selected_tuple->mUserData, false ); + } + return TRUE; + } + else + { + return FALSE; + } +} + +void LLTabContainer::draw() +{ + S32 target_pixel_scroll = 0; + S32 cur_scroll_pos = mScrollPos; + if (cur_scroll_pos > 0) + { + S32 available_width_with_arrows = mRect.getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + TABCNTR_ARROW_BTN_SIZE + 1); + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + if (cur_scroll_pos == 0) + { + break; + } + target_pixel_scroll += (*iter)->mButton->getRect().getWidth(); + cur_scroll_pos--; + } + + // Show part of the tab to the left of what is fully visible + target_pixel_scroll -= TABCNTR_TAB_PARTIAL_WIDTH; + // clamp so that rightmost tab never leaves right side of screen + target_pixel_scroll = llmin(mTotalTabWidth - available_width_with_arrows, target_pixel_scroll); + } + + mScrollPosPixels = (S32)lerp((F32)mScrollPosPixels, (F32)target_pixel_scroll, LLCriticalDamp::getInterpolant(0.08f)); + if( getVisible() ) + { + BOOL has_scroll_arrows = (mMaxScrollPos > 0) || (mScrollPosPixels > 0); + mLeftArrowBtn->setVisible( has_scroll_arrows ); + mRightArrowBtn->setVisible( has_scroll_arrows ); + + // Set the leftmost position of the tab buttons. + S32 left = LLPANEL_BORDER_WIDTH + (has_scroll_arrows ? TABCNTR_ARROW_BTN_SIZE : TABCNTR_TAB_H_PAD); + left -= mScrollPosPixels; + + // Hide all the buttons + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->setVisible( FALSE ); + } + + LLPanel::draw(); + + // Show all the buttons + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->setVisible( TRUE ); + } + + // Draw some of the buttons... + + LLGLEnable scissor_test(has_scroll_arrows ? GL_SCISSOR_TEST : GL_FALSE); + if( has_scroll_arrows ) + { + // ...but clip them. + S32 x1 = mLeftArrowBtn->getRect().mRight; + S32 y1 = 0; + S32 x2 = mRightArrowBtn->getRect().mLeft; + S32 y2 = 1; + if (mTabList.size() > 0) + { + y2 = mTabList[0]->mButton->getRect().mTop; + } + LLUI::setScissorRegionLocal(LLRect(x1, y2, x2, y1)); + } + + S32 max_scroll_visible = mTabList.size() - mMaxScrollPos + mScrollPos; + S32 idx = 0; + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->translate( left - tuple->mButton->getRect().mLeft, 0 ); + left += tuple->mButton->getRect().getWidth(); + + if( idx < mScrollPos ) + { + if( tuple->mButton->getFlashing() ) + { + mLeftArrowBtn->setFlashing( TRUE ); + } + } + else + if( max_scroll_visible < idx ) + { + if( tuple->mButton->getFlashing() ) + { + mRightArrowBtn->setFlashing( TRUE ); + } + } + + LLUI::pushMatrix(); + { + LLUI::translate((F32)tuple->mButton->getRect().mLeft, (F32)tuple->mButton->getRect().mBottom, 0.f); + tuple->mButton->draw(); + } + LLUI::popMatrix(); + + idx++; + } + + mLeftArrowBtn->setFlashing(FALSE); + mRightArrowBtn->setFlashing(FALSE); + } +} + + +void LLTabContainer::setRightTabBtnOffset(S32 offset) +{ + mRightArrowBtn->translate( -offset - mRightTabBtnOffset, 0 ); + mRightTabBtnOffset = offset; + updateMaxScrollPos(); +} + +BOOL LLTabContainer::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + BOOL handled = FALSE; + BOOL has_scroll_arrows = (mMaxScrollPos > 0); + + if (has_scroll_arrows) + { + if (mLeftArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mLeftArrowBtn->getRect().mLeft; + S32 local_y = y - mLeftArrowBtn->getRect().mBottom; + handled = mLeftArrowBtn->handleMouseDown(local_x, local_y, mask); + } + else if (mRightArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mRightArrowBtn->getRect().mLeft; + S32 local_y = y - mRightArrowBtn->getRect().mBottom; + handled = mRightArrowBtn->handleMouseDown(local_x, local_y, mask); + } + } + if (!handled) + { + handled = LLPanel::handleMouseDown( x, y, mask ); + } + + if (mTabList.size() > 0) + { + LLTabTuple* firsttuple = mTabList[0]; + LLRect tab_rect(has_scroll_arrows ? mLeftArrowBtn->getRect().mRight : mLeftArrowBtn->getRect().mLeft, + firsttuple->mButton->getRect().mTop, + has_scroll_arrows ? mRightArrowBtn->getRect().mLeft : mRightArrowBtn->getRect().mRight, + firsttuple->mButton->getRect().mBottom ); + if( tab_rect.pointInRect( x, y ) ) + { + LLButton* tab_button = mTabList[getCurrentPanelIndex()]->mButton; + gFocusMgr.setMouseCapture(this, NULL); + gFocusMgr.setKeyboardFocus(tab_button, NULL); + } + } + return handled; +} + +BOOL LLTabContainer::handleHover( S32 x, S32 y, MASK mask ) +{ + BOOL handled = FALSE; + BOOL has_scroll_arrows = (mMaxScrollPos > 0); + + if (has_scroll_arrows) + { + if (mLeftArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mLeftArrowBtn->getRect().mLeft; + S32 local_y = y - mLeftArrowBtn->getRect().mBottom; + handled = mLeftArrowBtn->handleHover(local_x, local_y, mask); + } + else if (mRightArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mRightArrowBtn->getRect().mLeft; + S32 local_y = y - mRightArrowBtn->getRect().mBottom; + handled = mRightArrowBtn->handleHover(local_x, local_y, mask); + } + } + if (!handled) + { + handled = LLPanel::handleHover(x, y, mask); + } + + commitHoveredButton(x, y); + return handled; +} + +BOOL LLTabContainer::handleMouseUp( S32 x, S32 y, MASK mask ) +{ + BOOL handled = FALSE; + BOOL has_scroll_arrows = (mMaxScrollPos > 0); + + if (has_scroll_arrows) + { + if (mLeftArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mLeftArrowBtn->getRect().mLeft; + S32 local_y = y - mLeftArrowBtn->getRect().mBottom; + handled = mLeftArrowBtn->handleMouseUp(local_x, local_y, mask); + } + else if (mRightArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mRightArrowBtn->getRect().mLeft; + S32 local_y = y - mRightArrowBtn->getRect().mBottom; + handled = mRightArrowBtn->handleMouseUp(local_x, local_y, mask); + } + } + if (!handled) + { + handled = LLPanel::handleMouseUp( x, y, mask ); + } + + commitHoveredButton(x, y); + LLPanel* cur_panel = getCurrentPanel(); + if (gFocusMgr.getMouseCapture() == this) + { + if (cur_panel) + { + if (!cur_panel->focusFirstItem(FALSE)) + { + // if nothing in the panel gets focus, make sure the new tab does + // otherwise the last tab might keep focus + mTabList[getCurrentPanelIndex()]->mButton->setFocus(TRUE); + } + } + gFocusMgr.setMouseCapture(NULL, NULL); + } + return handled; +} + +BOOL LLTabContainer::handleToolTip( S32 x, S32 y, LLString& msg, LLRect* sticky_rect ) +{ + BOOL handled = LLPanel::handleToolTip( x, y, msg, sticky_rect ); + if (!handled && mTabList.size() > 0 && getVisible() && pointInView( x, y ) ) + { + LLTabTuple* firsttuple = mTabList[0]; + + BOOL has_scroll_arrows = (mMaxScrollPos > 0); + LLRect clip( + has_scroll_arrows ? mLeftArrowBtn->getRect().mRight : mLeftArrowBtn->getRect().mLeft, + firsttuple->mButton->getRect().mTop, + has_scroll_arrows ? mRightArrowBtn->getRect().mLeft : mRightArrowBtn->getRect().mRight, + 0 ); + if( clip.pointInRect( x, y ) ) + { + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->setVisible( TRUE ); + S32 local_x = x - tuple->mButton->getRect().mLeft; + S32 local_y = y - tuple->mButton->getRect().mBottom; + handled = tuple->mButton->handleToolTip( local_x, local_y, msg, sticky_rect ); + if( handled ) + { + break; + } + } + } + + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->setVisible( FALSE ); + } + } + return handled; +} + +BOOL LLTabContainer::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + if (!getEnabled()) return FALSE; + + if (!gFocusMgr.childHasKeyboardFocus(this)) return FALSE; + + BOOL handled = FALSE; + if (key == '[' && mask == MASK_CONTROL) + { + selectPrevTab(); + handled = TRUE; + } + else if (key == ']' && mask == MASK_CONTROL) + { + selectNextTab(); + handled = TRUE; + } + + if (handled) + { + if (getCurrentPanel()) + { + getCurrentPanel()->setFocus(TRUE); + } + } + + if (!gFocusMgr.childHasKeyboardFocus(getCurrentPanel())) + { + // if child has focus, but not the current panel, focus + // is on a button + switch(key) + { + case KEY_UP: + if (getTabPosition() == BOTTOM && getCurrentPanel()) + { + getCurrentPanel()->setFocus(TRUE); + } + handled = TRUE; + break; + case KEY_DOWN: + if (getTabPosition() == TOP && getCurrentPanel()) + { + getCurrentPanel()->setFocus(TRUE); + } + handled = TRUE; + break; + case KEY_LEFT: + selectPrevTab(); + handled = TRUE; + break; + case KEY_RIGHT: + selectNextTab(); + handled = TRUE; + break; + default: + break; + } + } + return handled; +} + +// virtual +LLXMLNodePtr LLTabContainer::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLTabContainerCommon::getXML(); + + node->createChild("tab_position", TRUE)->setStringValue((mTabPosition == TOP ? "top" : "bottom")); + + return node; +} + +BOOL LLTabContainer::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType type, void* cargo_data, EAcceptance *accept, LLString &tooltip) +{ + BOOL has_scroll_arrows = (mMaxScrollPos > 0); + + if (has_scroll_arrows) + { + if (mLeftArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mLeftArrowBtn->getRect().mLeft; + S32 local_y = y - mLeftArrowBtn->getRect().mBottom; + mLeftArrowBtn->handleHover(local_x, local_y, mask); + } + else if (mRightArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mRightArrowBtn->getRect().mLeft; + S32 local_y = y - mRightArrowBtn->getRect().mBottom; + mRightArrowBtn->handleHover(local_x, local_y, mask); + } + } + + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->setVisible( TRUE ); + S32 local_x = x - tuple->mButton->getRect().mLeft; + S32 local_y = y - tuple->mButton->getRect().mBottom; + if (tuple->mButton->pointInView(local_x, local_y) && tuple->mButton->getEnabled() && !tuple->mTabPanel->getVisible()) + { + tuple->mButton->onCommit(); + } + } + + return LLView::handleDragAndDrop(x, y, mask, drop, type, cargo_data, accept, tooltip); +} + diff --git a/indra/llui/lltabcontainer.h b/indra/llui/lltabcontainer.h new file mode 100644 index 0000000000..41e602eaea --- /dev/null +++ b/indra/llui/lltabcontainer.h @@ -0,0 +1,242 @@ +/** + * @file lltabcontainer.h + * @brief LLTabContainerCommon base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Fear my script-fu! + +#ifndef LL_TABCONTAINER_H +#define LL_TABCONTAINER_H + +#include "llpanel.h" +#include "llframetimer.h" + +class LLButton; +class LLTextBox; + + +class LLTabContainerCommon : public LLPanel +{ +public: + enum TabPosition + { + TOP, + BOTTOM, + LEFT + }; + typedef enum e_insertion_point + { + START, + END, + RIGHT_OF_CURRENT + } eInsertionPoint; + + LLTabContainerCommon( const LLString& name, + const LLRect& rect, + TabPosition pos, + void(*close_callback)(void*), void* callback_userdata, + BOOL bordered = TRUE); + + LLTabContainerCommon( const LLString& name, + const LLString& rect_control, + TabPosition pos, + void(*close_callback)(void*), void* callback_userdata, + BOOL bordered = TRUE); + + virtual ~LLTabContainerCommon(); + + virtual void initButtons() = 0; + + virtual void setValue(const LLSD& value) { selectTab((S32) value.asInteger()); } + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_TAB_CONTAINER; } + virtual LLString getWidgetTag() const { return LL_TAB_CONTAINER_COMMON_TAG; } + + virtual LLView* getChildByName(const LLString& name, BOOL recurse = FALSE) const; + + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + virtual void addTabPanel(LLPanel* child, + const LLString& label, + BOOL select = FALSE, + void (*on_tab_clicked)(void*, bool) = NULL, + void* userdata = NULL, + S32 indent = 0, + BOOL placeholder = FALSE, + eInsertionPoint insertion_point = END) = 0; + virtual void addPlaceholder(LLPanel* child, const LLString& label); + + virtual void enableTabButton(S32 which, BOOL enable); + + virtual void removeTabPanel( LLPanel* child ); + virtual void deleteAllTabs(); + virtual LLPanel* getCurrentPanel(); + virtual S32 getCurrentPanelIndex(); + virtual S32 getTabCount(); + virtual S32 getPanelIndexByTitle(const LLString& title); + virtual LLPanel* getPanelByIndex(S32 index); + virtual LLPanel* getPanelByName(const LLString& name); + virtual S32 getIndexForPanel(LLPanel* panel); + + virtual void setCurrentTabName(const LLString& name); + + + virtual void selectFirstTab(); + virtual void selectLastTab(); + virtual BOOL selectTabPanel( LLPanel* child ); + virtual BOOL selectTab(S32 which) = 0; + virtual BOOL selectTabByName(const LLString& title); + virtual void selectNextTab(); + virtual void selectPrevTab(); + + BOOL getTabPanelFlashing(LLPanel* child); + void setTabPanelFlashing(LLPanel* child, BOOL state); + void setTitle( const LLString& title ); + const LLString getPanelTitle(S32 index); + + virtual void setTopBorderHeight(S32 height); + + virtual void setTabChangeCallback(LLPanel* tab, void (*on_tab_clicked)(void*,bool)); + virtual void setTabUserData(LLPanel* tab, void* userdata); + + virtual void reshape(S32 width, S32 height, BOOL called_from_parent); + + static void onCloseBtn(void* userdata); + static void onTabBtn(void* userdata); + static void onNextBtn(void* userdata); + static void onNextBtnHeld(void* userdata); + static void onPrevBtn(void* userdata); + static void onPrevBtnHeld(void* userdata); + + virtual void setRightTabBtnOffset( S32 offset ) { } + virtual void setPanelTitle(S32 index, const LLString& title) { } + + virtual TabPosition getTabPosition() { return mTabPosition; } + + +protected: + // Structure used to map tab buttons to and from tab panels + struct LLTabTuple + { + LLTabTuple( LLTabContainerCommon* c, LLPanel* p, LLButton* b, + void (*cb)(void*,bool), void* userdata, LLTextBox* placeholder = NULL ) + : + mTabContainer(c), + mTabPanel(p), + mButton(b), + mOnChangeCallback( cb ), + mUserData( userdata ), + mOldState(FALSE), + mPlaceholderText(placeholder) + {} + + LLTabContainerCommon* mTabContainer; + LLPanel* mTabPanel; + LLButton* mButton; + void (*mOnChangeCallback)(void*, bool); + void* mUserData; + BOOL mOldState; + LLTextBox* mPlaceholderText; + }; + + typedef std::vector tuple_list_t; + tuple_list_t mTabList; + S32 mCurrentTabIdx; + + BOOL mScrolled; + LLFrameTimer mScrollTimer; + S32 mScrollPos; + S32 mScrollPosPixels; + S32 mMaxScrollPos; + + void (*mCloseCallback)(void*); + void* mCallbackUserdata; + + LLTextBox* mTitleBox; + + S32 mTopBorderHeight; + TabPosition mTabPosition; + +protected: + void scrollPrev(); + void scrollNext(); + + virtual void updateMaxScrollPos() = 0; + virtual void commitHoveredButton(S32 x, S32 y) = 0; + LLTabTuple* getTabByPanel(LLPanel* child); + void insertTuple(LLTabTuple * tuple, eInsertionPoint insertion_point); +}; + +class LLTabContainer : public LLTabContainerCommon +{ +public: + LLTabContainer( const LLString& name, const LLRect& rect, TabPosition pos, + void(*close_callback)(void*), void* callback_userdata, + const LLString& title=LLString::null, BOOL bordered = TRUE ); + + LLTabContainer( const LLString& name, const LLString& rect_control, TabPosition pos, + void(*close_callback)(void*), void* callback_userdata, + const LLString& title=LLString::null, BOOL bordered = TRUE ); + + ~LLTabContainer(); + + /*virtual*/ void initButtons(); + + /*virtual*/ void draw(); + + /*virtual*/ void addTabPanel(LLPanel* child, + const LLString& label, + BOOL select = FALSE, + void (*on_tab_clicked)(void*, bool) = NULL, + void* userdata = NULL, + S32 indent = 0, + BOOL placeholder = FALSE, + eInsertionPoint insertion_point = END); + + /*virtual*/ BOOL selectTab(S32 which); + /*virtual*/ void removeTabPanel( LLPanel* child ); + + /*virtual*/ void setPanelTitle(S32 index, const LLString& title); + + /*virtual*/ void setRightTabBtnOffset( S32 offset ); + + /*virtual*/ void setMinTabWidth(S32 width); + /*virtual*/ void setMaxTabWidth(S32 width); + + /*virtual*/ S32 getMinTabWidth() const; + /*virtual*/ S32 getMaxTabWidth() const; + + /*virtual*/ BOOL handleMouseDown( S32 x, S32 y, MASK mask ); + /*virtual*/ BOOL handleHover( S32 x, S32 y, MASK mask ); + /*virtual*/ BOOL handleMouseUp( S32 x, S32 y, MASK mask ); + /*virtual*/ BOOL handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect ); + /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + /*virtual*/ BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType type, void* cargo_data, + EAcceptance* accept, LLString& tooltip); + + virtual LLXMLNodePtr getXML(bool save_children = true) const; + + +protected: + + LLButton* mLeftArrowBtn; + LLButton* mRightArrowBtn; + + S32 mRightTabBtnOffset; // Extra room to the right of the tab buttons. + +protected: + virtual void updateMaxScrollPos(); + virtual void commitHoveredButton(S32 x, S32 y); + + S32 mMinTabWidth; + S32 mMaxTabWidth; + S32 mTotalTabWidth; +}; + +const S32 TABCNTR_CLOSE_BTN_SIZE = 16; +const S32 TABCNTR_HEADER_HEIGHT = LLPANEL_BORDER_WIDTH + TABCNTR_CLOSE_BTN_SIZE; + +#endif // LL_TABCONTAINER_H diff --git a/indra/llui/lltextbox.cpp b/indra/llui/lltextbox.cpp new file mode 100644 index 0000000000..0cd2e98514 --- /dev/null +++ b/indra/llui/lltextbox.cpp @@ -0,0 +1,438 @@ +/** + * @file lltextbox.cpp + * @brief A text display widget + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lltextbox.h" + +#include "llerror.h" +#include "llgl.h" +#include "llui.h" +#include "lluictrlfactory.h" +#include "llcontrol.h" +#include "llfocusmgr.h" +#include "llstl.h" +#include + +LLTextBox::LLTextBox(const LLString& name, const LLRect& rect, const LLString& text, + const LLFontGL* font, BOOL mouse_opaque) +: LLUICtrl(name, rect, mouse_opaque, NULL, NULL, FOLLOWS_LEFT | FOLLOWS_TOP ), + mTextColor( LLUI::sColorsGroup->getColor( "LabelTextColor" ) ), + mDisabledColor( LLUI::sColorsGroup->getColor( "LabelDisabledColor" ) ), + mBackgroundColor( LLUI::sColorsGroup->getColor( "DefaultBackgroundColor" ) ), + mBorderColor( LLUI::sColorsGroup->getColor( "DefaultHighlightLight" ) ), + mBackgroundVisible( FALSE ), + mBorderVisible( FALSE ), + mDropshadowVisible( TRUE ), + mBorderDropShadowVisible( FALSE ), + mHPad(0), + mVPad(0), + mHAlign( LLFontGL::LEFT ), + mVAlign( LLFontGL::TOP ), + mClickedCallback(NULL), + mCallbackUserData(NULL) +{ + // TomY TODO Nuke this eventually + setText( !text.empty() ? text : name ); + mFontGL = font ? font : LLFontGL::sSansSerifSmall; + setTabStop(FALSE); +} + +LLTextBox::LLTextBox(const LLString& name, const LLString& text, F32 max_width, + const LLFontGL* font, BOOL mouse_opaque) : + LLUICtrl(name, LLRect(0, 0, 1, 1), mouse_opaque, NULL, NULL, FOLLOWS_LEFT | FOLLOWS_TOP), + mFontGL(font ? font : LLFontGL::sSansSerifSmall), + mTextColor(LLUI::sColorsGroup->getColor("LabelTextColor")), + mDisabledColor(LLUI::sColorsGroup->getColor("LabelDisabledColor")), + mBackgroundColor(LLUI::sColorsGroup->getColor("DefaultBackgroundColor")), + mBorderColor(LLUI::sColorsGroup->getColor("DefaultHighlightLight")), + mBackgroundVisible(FALSE), + mBorderVisible(FALSE), + mDropshadowVisible(TRUE), + mBorderDropShadowVisible(FALSE), + mHPad(0), + mVPad(0), + mHAlign(LLFontGL::LEFT), + mVAlign( LLFontGL::TOP ), + mClickedCallback(NULL), + mCallbackUserData(NULL) +{ + setWrappedText(!text.empty() ? text : name, max_width); + reshapeToFitText(); + setTabStop(FALSE); +} + +LLTextBox::~LLTextBox() +{ +} + +// virtual +EWidgetType LLTextBox::getWidgetType() const +{ + return WIDGET_TYPE_TEXT_BOX; +} + +// virtual +LLString LLTextBox::getWidgetTag() const +{ + return LL_TEXT_BOX_TAG; +} + +BOOL LLTextBox::handleMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + // HACK: Only do this if there actually is a click callback, so that + // overly large text boxes in the older UI won't start eating clicks. + if (mClickedCallback) + { + handled = TRUE; + + // Route future Mouse messages here preemptively. (Release on mouse up.) + gFocusMgr.setMouseCapture( this, NULL ); + + if (mSoundFlags & MOUSE_DOWN) + { + make_ui_sound("UISndClick"); + } + } + + return handled; +} + + +BOOL LLTextBox::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + // We only handle the click if the click both started and ended within us + + // HACK: Only do this if there actually is a click callback, so that + // overly large text boxes in the older UI won't start eating clicks. + if (mClickedCallback + && this == gFocusMgr.getMouseCapture()) + { + handled = TRUE; + + // Release the mouse + gFocusMgr.setMouseCapture( NULL, NULL ); + + if (mSoundFlags & MOUSE_UP) + { + make_ui_sound("UISndClickRelease"); + } + + // DO THIS AT THE VERY END to allow the button to be destroyed as a result of being clicked. + // If mouseup in the widget, it's been clicked + if (mClickedCallback) + { + (*mClickedCallback)( mCallbackUserData ); + } + } + + return handled; +} + +void LLTextBox::setText(const LLString& text) +{ + mText.assign(text); + setLineLengths(); +} + +void LLTextBox::setLineLengths() +{ + mLineLengthList.clear(); + + LLString::size_type cur = 0; + LLString::size_type len = mText.getWString().size(); + + while (cur < len) + { + LLString::size_type end = mText.getWString().find('\n', cur); + LLString::size_type runLen; + + if (end == LLString::npos) + { + runLen = len - cur; + cur = len; + } + else + { + runLen = end - cur; + cur = end + 1; // skip the new line character + } + + mLineLengthList.push_back( (S32)runLen ); + } +} + +void LLTextBox::setWrappedText(const LLString& in_text, F32 max_width) +{ + if (max_width < 0.0) + { + max_width = (F32)getRect().getWidth(); + } + + LLWString wtext = utf8str_to_wstring(in_text); + LLWString final_wtext; + + LLWString::size_type cur = 0;; + LLWString::size_type len = wtext.size(); + + while (cur < len) + { + LLWString::size_type end = wtext.find('\n', cur); + if (end == LLWString::npos) + { + end = len; + } + + LLWString::size_type runLen = end - cur; + if (runLen > 0) + { + LLWString run(wtext, cur, runLen); + LLWString::size_type useLen = + mFontGL->maxDrawableChars(run.c_str(), max_width, runLen, TRUE); + + final_wtext.append(wtext, cur, useLen); + cur += useLen; + } + + if (cur < len) + { + if (wtext[cur] == '\n') + { + cur += 1; + } + final_wtext += '\n'; + } + } + + LLString final_text = wstring_to_utf8str(final_wtext); + setText(final_text); +} + +S32 LLTextBox::getTextPixelWidth() +{ + S32 max_line_width = 0; + if( mLineLengthList.size() > 0 ) + { + S32 cur_pos = 0; + for (std::vector::iterator iter = mLineLengthList.begin(); + iter != mLineLengthList.end(); ++iter) + { + S32 line_length = *iter; + S32 line_width = mFontGL->getWidth( mText.getWString().c_str(), cur_pos, line_length ); + if( line_width > max_line_width ) + { + max_line_width = line_width; + } + cur_pos += line_length+1; + } + } + else + { + max_line_width = mFontGL->getWidth(mText.getWString().c_str()); + } + return max_line_width; +} + +S32 LLTextBox::getTextPixelHeight() +{ + S32 num_lines = mLineLengthList.size(); + if( num_lines < 1 ) + { + num_lines = 1; + } + return (S32)(num_lines * mFontGL->getLineHeight()); +} + + +void LLTextBox::setValue(const LLSD& value ) +{ + setText(value.asString()); +} + +LLSD LLTextBox::getValue() const +{ + return LLSD(getText()); +} + +BOOL LLTextBox::setTextArg( const LLString& key, const LLString& text ) +{ + mText.setArg(key, text); + setLineLengths(); + return TRUE; +} + +void LLTextBox::draw() +{ + if( getVisible() ) + { + if (mBorderVisible) + { + gl_rect_2d_offset_local(getLocalRect(), 2, FALSE); + } + + if( mBorderDropShadowVisible ) + { + static LLColor4 color_drop_shadow = LLUI::sColorsGroup->getColor("ColorDropShadow"); + static S32 drop_shadow_tooltip = LLUI::sConfigGroup->getS32("DropShadowTooltip"); + gl_drop_shadow(0, mRect.getHeight(), mRect.getWidth(), 0, + color_drop_shadow, drop_shadow_tooltip); + } + + if (mBackgroundVisible) + { + LLRect r( 0, mRect.getHeight(), mRect.getWidth(), 0 ); + gl_rect_2d( r, mBackgroundColor ); + } + + S32 text_x = 0; + switch( mHAlign ) + { + case LLFontGL::LEFT: + text_x = mHPad; + break; + case LLFontGL::HCENTER: + text_x = mRect.getWidth() / 2; + break; + case LLFontGL::RIGHT: + text_x = mRect.getWidth() - mHPad; + break; + } + + S32 text_y = mRect.getHeight() - mVPad; + + if ( getEnabled() ) + { + drawText( text_x, text_y, mTextColor ); + } + else + { + drawText( text_x, text_y, mDisabledColor ); + } + + if (sDebugRects) + { + drawDebugRect(); + } + } +} + +void LLTextBox::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + // reparse line lengths + setText(mText); + LLView::reshape(width, height, called_from_parent); +} + +void LLTextBox::drawText( S32 x, S32 y, const LLColor4& color ) +{ + if( !mLineLengthList.empty() ) + { + S32 cur_pos = 0; + for (std::vector::iterator iter = mLineLengthList.begin(); + iter != mLineLengthList.end(); ++iter) + { + S32 line_length = *iter; + mFontGL->render(mText.getWString(), cur_pos, (F32)x, (F32)y, color, + mHAlign, mVAlign, + mDropshadowVisible ? LLFontGL::DROP_SHADOW : LLFontGL::NORMAL, + line_length, mRect.getWidth(), NULL, TRUE ); + cur_pos += line_length + 1; + y -= llfloor(mFontGL->getLineHeight()); + } + } + else + { + mFontGL->render(mText.getWString(), 0, (F32)x, (F32)y, color, + mHAlign, mVAlign, + mDropshadowVisible ? LLFontGL::DROP_SHADOW : LLFontGL::NORMAL, + S32_MAX, mRect.getWidth(), NULL, TRUE); + } +} + + +void LLTextBox::reshapeToFitText() +{ + S32 width = getTextPixelWidth(); + S32 height = getTextPixelHeight(); + reshape( width + 2 * mHPad, height + 2 * mVPad ); +} + +// virtual +LLXMLNodePtr LLTextBox::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + // Attributes + + node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mFontGL)); + + node->createChild("halign", TRUE)->setStringValue(LLFontGL::nameFromHAlign(mHAlign)); + + addColorXML(node, mTextColor, "text_color", "LabelTextColor"); + addColorXML(node, mDisabledColor, "disabled_color", "LabelDisabledColor"); + addColorXML(node, mBackgroundColor, "bg_color", "DefaultBackgroundColor"); + addColorXML(node, mBorderColor, "border_color", "DefaultHighlightLight"); + + node->createChild("bg_visible", TRUE)->setBoolValue(mBackgroundVisible); + + node->createChild("border_visible", TRUE)->setBoolValue(mBorderVisible); + + node->createChild("drop_shadow_visible", TRUE)->setBoolValue(mDropshadowVisible); + + node->createChild("border_drop_shadow_visible", TRUE)->setBoolValue(mBorderDropShadowVisible); + + node->createChild("h_pad", TRUE)->setIntValue(mHPad); + + node->createChild("v_pad", TRUE)->setIntValue(mVPad); + + // Contents + + node->setStringValue(mText); + + return node; +} + +// static +LLView* LLTextBox::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("text_box"); + node->getAttributeString("name", name); + LLFontGL* font = LLView::selectFont(node); + + LLString text = node->getTextContents(); + + // TomY Yes I know this is a hack, but insert a space to make a blank text field + if (text == "") + { + text = " "; + } + + LLTextBox* text_box = new LLTextBox(name, + LLRect(), + text, + font, + FALSE); + + LLFontGL::HAlign halign = LLView::selectFontHAlign(node); + text_box->setHAlign(halign); + + text_box->initFromXML(node, parent); + + if(node->hasAttribute("text_color")) + { + LLColor4 color; + LLUICtrlFactory::getAttributeColor(node, "text_color", color); + text_box->setColor(color); + } + + return text_box; +} diff --git a/indra/llui/lltextbox.h b/indra/llui/lltextbox.h new file mode 100644 index 0000000000..0c09ae26b4 --- /dev/null +++ b/indra/llui/lltextbox.h @@ -0,0 +1,106 @@ +/** + * @file lltextbox.h + * @brief A single text item display + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTEXTBOX_H +#define LL_LLTEXTBOX_H + +#include "lluictrl.h" +#include "v4color.h" +#include "llstring.h" +#include "llfontgl.h" +#include "lluistring.h" + + +class LLTextBox +: public LLUICtrl +{ +public: + // By default, follows top and left and is mouse-opaque. + // If no text, text = name. + // If no font, uses default system font. + LLTextBox(const LLString& name, const LLRect& rect, const LLString& text = LLString::null, + const LLFontGL* font = NULL, BOOL mouse_opaque = TRUE ); + + // Construct a textbox which handles word wrapping for us. + LLTextBox(const LLString& name, const LLString& text, F32 max_width = 200, + const LLFontGL* font = NULL, BOOL mouse_opaque = TRUE ); + + virtual ~LLTextBox(); + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + + virtual void draw(); + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + + void setColor( const LLColor4& c ) { mTextColor = c; } + void setDisabledColor( const LLColor4& c) { mDisabledColor = c; } + void setBackgroundColor( const LLColor4& c) { mBackgroundColor = c; } + void setBorderColor( const LLColor4& c) { mBorderColor = c; } + void setText( const LLString& text ); + void setWrappedText(const LLString& text, F32 max_width = -1.0); + // default width means use existing control width + + void setBackgroundVisible(BOOL visible) { mBackgroundVisible = visible; } + void setBorderVisible(BOOL visible) { mBorderVisible = visible; } + void setDropshadowVisible(BOOL visible) { mDropshadowVisible = visible; } + void setBorderDropshadowVisible(BOOL visible){ mBorderDropShadowVisible = visible; } + void setHPad(S32 pixels) { mHPad = pixels; } + void setVPad(S32 pixels) { mVPad = pixels; } + void setRightAlign() { mHAlign = LLFontGL::RIGHT; } + void setHAlign( LLFontGL::HAlign align ) { mHAlign = align; } + void setClickedCallback( void (*cb)(void *data) ){ mClickedCallback = cb; } // mouse down and up within button + void setCallbackUserData( void* data ) { mCallbackUserData = data; } + + const LLFontGL* getFont() const { return mFontGL; } + + void reshapeToFitText(); + + const LLString& getText() const { return mText.getString(); } + S32 getTextPixelWidth(); + S32 getTextPixelHeight(); + + + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + virtual BOOL setTextArg( const LLString& key, const LLString& text ); + +protected: + void setLineLengths(); + void drawText(S32 x, S32 y, const LLColor4& color ); + +protected: + LLUIString mText; + const LLFontGL* mFontGL; + LLColor4 mTextColor; + LLColor4 mDisabledColor; + + LLColor4 mBackgroundColor; + LLColor4 mBorderColor; + + BOOL mBackgroundVisible; + BOOL mBorderVisible; + + BOOL mDropshadowVisible; // Draws black dropshadow below and to the right of the text. + BOOL mBorderDropShadowVisible; + + S32 mHPad; + S32 mVPad; + LLFontGL::HAlign mHAlign; + LLFontGL::VAlign mVAlign; + + std::vector mLineLengthList; + void (*mClickedCallback)(void* data ); + void* mCallbackUserData; +}; + +#endif diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp new file mode 100644 index 0000000000..a4747aef67 --- /dev/null +++ b/indra/llui/lltexteditor.cpp @@ -0,0 +1,4144 @@ +/** + * @file lltexteditor.cpp + * @brief LLTextEditor base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Text editor widget to let users enter a a multi-line ASCII document. + +#include "linden_common.h" + +#include "lltexteditor.h" + +#include "llfontgl.h" +#include "llgl.h" +#include "llui.h" +#include "lluictrlfactory.h" +#include "llrect.h" +#include "llfocusmgr.h" +#include "sound_ids.h" +#include "lltimer.h" +#include "llmath.h" + +#include "audioengine.h" +#include "llclipboard.h" +#include "llscrollbar.h" +#include "llstl.h" +#include "llkeyboard.h" +#include "llkeywords.h" +#include "llundo.h" +#include "llviewborder.h" +#include "llcontrol.h" +#include "llimagegl.h" +#include "llwindow.h" +#include "llglheaders.h" +#include + +// +// Globals +// + +BOOL gDebugTextEditorTips = FALSE; + +// +// Constants +// + +const S32 UI_TEXTEDITOR_BUFFER_BLOCK_SIZE = 512; + +const S32 UI_TEXTEDITOR_BORDER = 1; +const S32 UI_TEXTEDITOR_H_PAD = 4; +const S32 UI_TEXTEDITOR_V_PAD_TOP = 4; +const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds +const S32 CURSOR_THICKNESS = 2; +const S32 SPACES_PER_TAB = 4; + +LLColor4 LLTextEditor::mLinkColor = LLColor4::blue; +void (* LLTextEditor::mURLcallback)(const char*) = NULL; +BOOL (* LLTextEditor::mSecondlifeURLcallback)(LLString) = NULL; + +/////////////////////////////////////////////////////////////////// +//virtuals +BOOL LLTextCmd::canExtend(S32 pos) +{ + return FALSE; +} + +void LLTextCmd::blockExtensions() +{ +} + +BOOL LLTextCmd::extendAndExecute( LLTextEditor* editor, S32 pos, llwchar c, S32* delta ) +{ + llassert(0); + return 0; +} + +BOOL LLTextCmd::hasExtCharValue( llwchar value ) +{ + return FALSE; +} + +// Utility funcs +S32 LLTextCmd::insert(LLTextEditor* editor, S32 pos, const LLWString &utf8str) +{ + return editor->insertStringNoUndo( pos, utf8str ); +} +S32 LLTextCmd::remove(LLTextEditor* editor, S32 pos, S32 length) +{ + return editor->removeStringNoUndo( pos, length ); +} +S32 LLTextCmd::overwrite(LLTextEditor* editor, S32 pos, llwchar wc) +{ + return editor->overwriteCharNoUndo(pos, wc); +} + +/////////////////////////////////////////////////////////////////// + +class LLTextCmdInsert : public LLTextCmd +{ +public: + LLTextCmdInsert(S32 pos, BOOL group_with_next, const LLWString &ws) + : LLTextCmd(pos, group_with_next), mString(ws) + { + } + virtual BOOL execute( LLTextEditor* editor, S32* delta ) + { + *delta = insert(editor, mPos, mString ); + LLWString::truncate(mString, *delta); + //mString = wstring_truncate(mString, *delta); + return (*delta != 0); + } + virtual S32 undo( LLTextEditor* editor ) + { + remove(editor, mPos, mString.length() ); + return mPos; + } + virtual S32 redo( LLTextEditor* editor ) + { + insert(editor, mPos, mString ); + return mPos + mString.length(); + } + +private: + LLWString mString; +}; + +/////////////////////////////////////////////////////////////////// + +class LLTextCmdAddChar : public LLTextCmd +{ +public: + LLTextCmdAddChar( S32 pos, BOOL group_with_next, llwchar wc) + : LLTextCmd(pos, group_with_next), mString(1, wc), mBlockExtensions(FALSE) + { + } + virtual void blockExtensions() + { + mBlockExtensions = TRUE; + } + virtual BOOL canExtend(S32 pos) + { + return !mBlockExtensions && (pos == mPos + (S32)mString.length()); + } + virtual BOOL execute( LLTextEditor* editor, S32* delta ) + { + *delta = insert(editor, mPos, mString); + LLWString::truncate(mString, *delta); + //mString = wstring_truncate(mString, *delta); + return (*delta != 0); + } + virtual BOOL extendAndExecute( LLTextEditor* editor, S32 pos, llwchar wc, S32* delta ) + { + LLWString ws; + ws += wc; + + *delta = insert(editor, pos, ws); + if( *delta > 0 ) + { + mString += wc; + } + return (*delta != 0); + } + virtual S32 undo( LLTextEditor* editor ) + { + remove(editor, mPos, mString.length() ); + return mPos; + } + virtual S32 redo( LLTextEditor* editor ) + { + insert(editor, mPos, mString ); + return mPos + mString.length(); + } + +private: + LLWString mString; + BOOL mBlockExtensions; + +}; + +/////////////////////////////////////////////////////////////////// + +class LLTextCmdOverwriteChar : public LLTextCmd +{ +public: + LLTextCmdOverwriteChar( S32 pos, BOOL group_with_next, llwchar wc) + : LLTextCmd(pos, group_with_next), mChar(wc), mOldChar(0) {} + + virtual BOOL execute( LLTextEditor* editor, S32* delta ) + { + mOldChar = editor->getWChar(mPos); + overwrite(editor, mPos, mChar); + *delta = 0; + return TRUE; + } + virtual S32 undo( LLTextEditor* editor ) + { + overwrite(editor, mPos, mOldChar); + return mPos; + } + virtual S32 redo( LLTextEditor* editor ) + { + overwrite(editor, mPos, mChar); + return mPos+1; + } + +private: + llwchar mChar; + llwchar mOldChar; +}; + +/////////////////////////////////////////////////////////////////// + +class LLTextCmdRemove : public LLTextCmd +{ +public: + LLTextCmdRemove( S32 pos, BOOL group_with_next, S32 len ) : + LLTextCmd(pos, group_with_next), mLen(len) + { + } + virtual BOOL execute( LLTextEditor* editor, S32* delta ) + { + mString = editor->getWSubString(mPos, mLen); + *delta = remove(editor, mPos, mLen ); + return (*delta != 0); + } + virtual S32 undo( LLTextEditor* editor ) + { + insert(editor, mPos, mString ); + return mPos + mString.length(); + } + virtual S32 redo( LLTextEditor* editor ) + { + remove(editor, mPos, mLen ); + return mPos; + } +private: + LLWString mString; + S32 mLen; +}; + +/////////////////////////////////////////////////////////////////// + +// +// Member functions +// + +LLTextEditor::LLTextEditor( + const LLString& name, + const LLRect& rect, + S32 max_length, + const LLString &default_text, + const LLFontGL* font, + BOOL allow_embedded_items) + : + LLUICtrl( name, rect, TRUE, NULL, NULL, FOLLOWS_TOP | FOLLOWS_LEFT ), + mTextIsUpToDate(TRUE), + mMaxTextLength( max_length ), + mBaseDocIsPristine(TRUE), + mPristineCmd( NULL ), + mLastCmd( NULL ), + mCursorPos( 0 ), + mIsSelecting( FALSE ), + mSelectionStart( 0 ), + mSelectionEnd( 0 ), + mOnScrollEndCallback( NULL ), + mOnScrollEndData( NULL ), + mCursorColor( LLUI::sColorsGroup->getColor( "TextCursorColor" ) ), + mFgColor( LLUI::sColorsGroup->getColor( "TextFgColor" ) ), + mReadOnlyFgColor( LLUI::sColorsGroup->getColor( "TextFgReadOnlyColor" ) ), + mWriteableBgColor( LLUI::sColorsGroup->getColor( "TextBgWriteableColor" ) ), + mReadOnlyBgColor( LLUI::sColorsGroup->getColor( "TextBgReadOnlyColor" ) ), + mFocusBgColor( LLUI::sColorsGroup->getColor( "TextBgFocusColor" ) ), + mReadOnly(FALSE), + mWordWrap( FALSE ), + mTabToNextField( TRUE ), + mCommitOnFocusLost( FALSE ), + mTakesFocus( TRUE ), + mHideScrollbarForShortDocs( FALSE ), + mTakesNonScrollClicks( TRUE ), + mAllowEmbeddedItems( allow_embedded_items ), + mAcceptCallingCardNames(FALSE), + mHandleEditKeysDirectly( FALSE ), + mMouseDownX(0), + mMouseDownY(0), + mLastSelectionX(-1), + mLastSelectionY(-1) +{ + mSourceID.generate(); + + if (font) + { + mGLFont = font; + } + else + { + mGLFont = LLFontGL::sSansSerif; + } + + updateTextRect(); + + S32 line_height = llround( mGLFont->getLineHeight() ); + S32 page_size = mTextRect.getHeight() / line_height; + + // Init the scrollbar + LLRect scroll_rect; + scroll_rect.setOriginAndSize( + mRect.getWidth() - UI_TEXTEDITOR_BORDER - SCROLLBAR_SIZE, + UI_TEXTEDITOR_BORDER, + SCROLLBAR_SIZE, + mRect.getHeight() - 2 * UI_TEXTEDITOR_BORDER ); + S32 lines_in_doc = getLineCount(); + mScrollbar = new LLScrollbar( "Scrollbar", scroll_rect, + LLScrollbar::VERTICAL, + lines_in_doc, + 0, + page_size, + NULL, this ); + mScrollbar->setFollowsRight(); + mScrollbar->setFollowsTop(); + mScrollbar->setFollowsBottom(); + mScrollbar->setEnabled( TRUE ); + mScrollbar->setVisible( TRUE ); + mScrollbar->setOnScrollEndCallback(mOnScrollEndCallback, mOnScrollEndData); + addChild(mScrollbar); + + mBorder = new LLViewBorder( "text ed border", LLRect(0, mRect.getHeight(), mRect.getWidth(), 0), LLViewBorder::BEVEL_IN, LLViewBorder::STYLE_LINE, UI_TEXTEDITOR_BORDER ); + addChild( mBorder ); + + setText(default_text); + + mParseHTML=FALSE; + mHTML=""; +} + + +LLTextEditor::~LLTextEditor() +{ + gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() + + // Route menu back to the default + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } + + // Scrollbar is deleted by LLView + mHoverSegment = NULL; + std::for_each(mSegments.begin(), mSegments.end(), DeletePointer()); + + std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer()); +} + +//virtual +LLString LLTextEditor::getWidgetTag() const +{ + return LL_TEXT_EDITOR_TAG; +} + +void LLTextEditor::setTrackColor( const LLColor4& color ) +{ + mScrollbar->setTrackColor(color); +} + +void LLTextEditor::setThumbColor( const LLColor4& color ) +{ + mScrollbar->setThumbColor(color); +} + +void LLTextEditor::setHighlightColor( const LLColor4& color ) +{ + mScrollbar->setHighlightColor(color); +} + +void LLTextEditor::setShadowColor( const LLColor4& color ) +{ + mScrollbar->setShadowColor(color); +} + +void LLTextEditor::updateLineStartList(S32 startpos) +{ + updateSegments(); + + bindEmbeddedChars( mGLFont ); + + S32 seg_num = mSegments.size(); + S32 seg_idx = 0; + S32 seg_offset = 0; + + if (!mLineStartList.empty()) + { + getSegmentAndOffset(startpos, &seg_idx, &seg_offset); + line_info t(seg_idx, seg_offset); + line_list_t::iterator iter = std::upper_bound(mLineStartList.begin(), mLineStartList.end(), t, line_info_compare()); + if (iter != mLineStartList.begin()) --iter; + seg_idx = iter->mSegment; + seg_offset = iter->mOffset; + mLineStartList.erase(iter, mLineStartList.end()); + } + + while( seg_idx < seg_num ) + { + mLineStartList.push_back(line_info(seg_idx,seg_offset)); + BOOL line_ended = FALSE; + S32 line_width = 0; + while(!line_ended && seg_idx < seg_num) + { + LLTextSegment* segment = mSegments[seg_idx]; + S32 start_idx = segment->getStart() + seg_offset; + S32 end_idx = start_idx; + while (end_idx < segment->getEnd() && mWText[end_idx] != '\n') + { + end_idx++; + } + if (start_idx == end_idx) + { + if (end_idx >= segment->getEnd()) + { + // empty segment + seg_idx++; + seg_offset = 0; + } + else + { + // empty line + line_ended = TRUE; + seg_offset++; + } + } + else + { + const llwchar* str = mWText.c_str() + start_idx; + S32 drawn = mGLFont->maxDrawableChars(str, (F32)mTextRect.getWidth() - line_width, + end_idx - start_idx, mWordWrap, mAllowEmbeddedItems ); + if( 0 == drawn && line_width == 0) + { + // If at the beginning of a line, draw at least one character, even if it doesn't all fit. + drawn = 1; + } + seg_offset += drawn; + line_width += mGLFont->getWidth(str, 0, drawn, mAllowEmbeddedItems); + end_idx = segment->getStart() + seg_offset; + if (end_idx < segment->getEnd()) + { + line_ended = TRUE; + if (mWText[end_idx] == '\n') + { + seg_offset++; // skip newline + } + } + else + { + // finished with segment + seg_idx++; + seg_offset = 0; + } + } + } + } + + unbindEmbeddedChars(mGLFont); + + mScrollbar->setDocSize( getLineCount() ); + + if (mHideScrollbarForShortDocs) + { + BOOL short_doc = (mScrollbar->getDocSize() <= mScrollbar->getPageSize()); + mScrollbar->setVisible(!short_doc); + } + +} + +//////////////////////////////////////////////////////////// +// LLTextEditor +// Public methods + +//static +BOOL LLTextEditor::isPartOfWord(llwchar c) { return (c == '_') || isalnum(c); } + + + +void LLTextEditor::truncate() +{ + if (mWText.size() > (size_t)mMaxTextLength) + { + LLWString::truncate(mWText, mMaxTextLength); + mTextIsUpToDate = FALSE; + } +} + +void LLTextEditor::setText(const LLString &utf8str) +{ + mUTF8Text = utf8str; + mWText = utf8str_to_wstring(utf8str); + mTextIsUpToDate = TRUE; + + truncate(); + blockUndo(); + + setCursorPos(0); + deselect(); + + updateLineStartList(); + updateScrollFromCursor(); +} + +void LLTextEditor::setWText(const LLWString &wtext) +{ + mWText = wtext; + mUTF8Text.clear(); + mTextIsUpToDate = FALSE; + + truncate(); + blockUndo(); + + setCursorPos(0); + deselect(); + + updateLineStartList(); + updateScrollFromCursor(); +} + +void LLTextEditor::setValue(const LLSD& value) +{ + setText(value.asString()); +} + +const LLString& LLTextEditor::getText() const +{ + if (!mTextIsUpToDate) + { + if (mAllowEmbeddedItems) + { + llwarns << "getText() called on text with embedded items (not supported)" << llendl; + } + mUTF8Text = wstring_to_utf8str(mWText); + mTextIsUpToDate = TRUE; + } + return mUTF8Text; +} + +LLSD LLTextEditor::getValue() const +{ + return LLSD(getText()); +} + +void LLTextEditor::setWordWrap(BOOL b) +{ + mWordWrap = b; + + setCursorPos(0); + deselect(); + + updateLineStartList(); + updateScrollFromCursor(); +} + + +void LLTextEditor::setBorderVisible(BOOL b) +{ + mBorder->setVisible(b); +} + + + +void LLTextEditor::setHideScrollbarForShortDocs(BOOL b) +{ + mHideScrollbarForShortDocs = b; + + if (mHideScrollbarForShortDocs) + { + BOOL short_doc = (mScrollbar->getDocSize() <= mScrollbar->getPageSize()); + mScrollbar->setVisible(!short_doc); + } +} + +void LLTextEditor::selectNext(const LLString& search_text_in, BOOL case_insensitive, BOOL wrap) +{ + if (search_text_in.empty()) + { + return; + } + + LLWString text = getWText(); + LLWString search_text = utf8str_to_wstring(search_text_in); + if (case_insensitive) + { + LLWString::toLower(text); + LLWString::toLower(search_text); + } + + if (mIsSelecting) + { + LLWString selected_text = text.substr(mSelectionEnd, mSelectionStart - mSelectionEnd); + + if (selected_text == search_text) + { + // We already have this word selected, we are searching for the next. + mCursorPos += search_text.size(); + } + } + + S32 loc = text.find(search_text,mCursorPos); + + // If Maybe we wrapped, search again + if (wrap && (-1 == loc)) + { + loc = text.find(search_text); + } + + // If still -1, then search_text just isn't found. + if (-1 == loc) + { + mIsSelecting = FALSE; + mSelectionEnd = 0; + mSelectionStart = 0; + return; + } + + setCursorPos(loc); + + mIsSelecting = TRUE; + mSelectionEnd = mCursorPos; + mSelectionStart = llmin((S32)getLength(), (S32)(mCursorPos + search_text.size())); +} + +BOOL LLTextEditor::replaceText(const LLString& search_text_in, const LLString& replace_text, + BOOL case_insensitive, BOOL wrap) +{ + BOOL replaced = FALSE; + + if (search_text_in.empty()) + { + return replaced; + } + + LLWString search_text = utf8str_to_wstring(search_text_in); + if (mIsSelecting) + { + LLWString text = getWText(); + LLWString selected_text = text.substr(mSelectionEnd, mSelectionStart - mSelectionEnd); + + if (case_insensitive) + { + LLWString::toLower(selected_text); + LLWString::toLower(search_text); + } + + if (selected_text == search_text) + { + insertText(replace_text); + replaced = TRUE; + } + } + + selectNext(search_text_in, case_insensitive, wrap); + return replaced; +} + +void LLTextEditor::replaceTextAll(const LLString& search_text, const LLString& replace_text, BOOL case_insensitive) +{ + S32 cur_pos = mScrollbar->getDocPos(); + + setCursorPos(0); + selectNext(search_text, case_insensitive, FALSE); + + BOOL replaced = TRUE; + while ( replaced ) + { + replaced = replaceText(search_text,replace_text, case_insensitive, FALSE); + } + + mScrollbar->setDocPos(cur_pos); +} + +void LLTextEditor::setTakesNonScrollClicks(BOOL b) +{ + mTakesNonScrollClicks = b; +} + + +// Picks a new cursor position based on the screen size of text being drawn. +void LLTextEditor::setCursorAtLocalPos( S32 local_x, S32 local_y, BOOL round ) +{ + setCursorPos(getCursorPosFromLocalCoord(local_x, local_y, round)); +} + +S32 LLTextEditor::prevWordPos(S32 cursorPos) const +{ + const LLWString& wtext = mWText; + while( (cursorPos > 0) && (wtext[cursorPos-1] == ' ') ) + { + cursorPos--; + } + while( (cursorPos > 0) && isPartOfWord( wtext[cursorPos-1] ) ) + { + cursorPos--; + } + return cursorPos; +} + +S32 LLTextEditor::nextWordPos(S32 cursorPos) const +{ + const LLWString& wtext = mWText; + while( (cursorPos < getLength()) && isPartOfWord( wtext[cursorPos+1] ) ) + { + cursorPos++; + } + while( (cursorPos < getLength()) && (wtext[cursorPos+1] == ' ') ) + { + cursorPos++; + } + return cursorPos; +} + +S32 LLTextEditor::getLineCount() +{ + return mLineStartList.size(); +} + +S32 LLTextEditor::getLineStart( S32 line ) +{ + S32 num_lines = getLineCount(); + if (num_lines == 0) + { + return 0; + } + line = llclamp(line, 0, num_lines-1); + S32 segidx = mLineStartList[line].mSegment; + S32 segoffset = mLineStartList[line].mOffset; + LLTextSegment* seg = mSegments[segidx]; + S32 res = seg->getStart() + segoffset; + if (res > seg->getEnd()) llerrs << "wtf" << llendl; + return res; +} + +// Given an offset into text (pos), find the corresponding line (from the start of the doc) and an offset into the line. +void LLTextEditor::getLineAndOffset( S32 startpos, S32* linep, S32* offsetp ) +{ + if (mLineStartList.empty()) + { + *linep = 0; + *offsetp = startpos; + } + else + { + S32 seg_idx, seg_offset; + getSegmentAndOffset( startpos, &seg_idx, &seg_offset ); + + line_info tline(seg_idx, seg_offset); + line_list_t::iterator iter = std::upper_bound(mLineStartList.begin(), mLineStartList.end(), tline, line_info_compare()); + if (iter != mLineStartList.begin()) --iter; + *linep = iter - mLineStartList.begin(); + S32 line_start = mSegments[iter->mSegment]->getStart() + iter->mOffset; + *offsetp = startpos - line_start; + } +} + +void LLTextEditor::getSegmentAndOffset( S32 startpos, S32* segidxp, S32* offsetp ) +{ + if (mSegments.empty()) + { + *segidxp = -1; + *offsetp = startpos; + } + + LLTextSegment tseg(startpos); + segment_list_t::iterator seg_iter; + seg_iter = std::upper_bound(mSegments.begin(), mSegments.end(), &tseg, LLTextSegment::compare()); + if (seg_iter != mSegments.begin()) --seg_iter; + *segidxp = seg_iter - mSegments.begin(); + *offsetp = startpos - (*seg_iter)->getStart(); +} + +const LLWString& LLTextEditor::getWText() const +{ + return mWText; +} + +S32 LLTextEditor::getLength() const +{ + return mWText.length(); +} + +llwchar LLTextEditor::getWChar(S32 pos) +{ + return mWText[pos]; +} + +LLWString LLTextEditor::getWSubString(S32 pos, S32 len) +{ + return mWText.substr(pos, len); +} + +S32 LLTextEditor::getCursorPosFromLocalCoord( S32 local_x, S32 local_y, BOOL round ) +{ + // If round is true, if the position is on the right half of a character, the cursor + // will be put to its right. If round is false, the cursor will always be put to the + // character's left. + + // Figure out which line we're nearest to. + S32 total_lines = getLineCount(); + S32 line_height = llround( mGLFont->getLineHeight() ); + S32 max_visible_lines = mTextRect.getHeight() / line_height; + S32 scroll_lines = mScrollbar->getDocPos(); + S32 visible_lines = llmin( total_lines - scroll_lines, max_visible_lines ); // Lines currently visible + + //S32 line = S32( 0.5f + ((mTextRect.mTop - local_y) / mGLFont->getLineHeight()) ); + S32 line = (mTextRect.mTop - 1 - local_y) / line_height; + if (line >= total_lines) + { + return getLength(); // past the end + } + + line = llclamp( line, 0, visible_lines ) + scroll_lines; + + S32 line_start = getLineStart(line); + S32 next_start = getLineStart(line+1); + S32 line_end = (next_start != line_start) ? next_start - 1 : getLength(); + + if(line_start == -1) + { + return 0; + } + else + { + S32 line_len = line_end - line_start; + S32 pos; + + if (mAllowEmbeddedItems) + { + // Figure out which character we're nearest to. + bindEmbeddedChars(mGLFont); + pos = mGLFont->charFromPixelOffset(mWText.c_str(), line_start, + (F32)(local_x - mTextRect.mLeft), + (F32)(mTextRect.getWidth()), + line_len, + round, TRUE); + unbindEmbeddedChars(mGLFont); + } + else + { + pos = mGLFont->charFromPixelOffset(mWText.c_str(), line_start, + (F32)(local_x - mTextRect.mLeft), + (F32)mTextRect.getWidth(), + line_len, + round); + } + + return line_start + pos; + } +} + +void LLTextEditor::setCursor(S32 row, S32 column) +{ + const llwchar* doc = mWText.c_str(); + const char CR = 10; + while(row--) + { + while (CR != *doc++); + } + doc += column; + setCursorPos(doc - mWText.c_str()); + updateScrollFromCursor(); +} + +void LLTextEditor::setCursorPos(S32 offset) +{ + mCursorPos = llclamp(offset, 0, (S32)getLength()); + updateScrollFromCursor(); +} + + +BOOL LLTextEditor::canDeselect() +{ + return hasSelection(); +} + + +void LLTextEditor::deselect() +{ + mSelectionStart = 0; + mSelectionEnd = 0; + mIsSelecting = FALSE; +} + + +void LLTextEditor::startSelection() +{ + if( !mIsSelecting ) + { + mIsSelecting = TRUE; + mSelectionStart = mCursorPos; + mSelectionEnd = mCursorPos; + } +} + +void LLTextEditor::endSelection() +{ + if( mIsSelecting ) + { + mIsSelecting = FALSE; + mSelectionEnd = mCursorPos; + } + if (mParseHTML && mHTML.length() > 0) + { + //Special handling for slurls + if ( (mSecondlifeURLcallback!=NULL) && !(*mSecondlifeURLcallback)(mHTML) ) + { + if (mURLcallback!=NULL) (*mURLcallback)(mHTML.c_str()); + + //load_url(url.c_str()); + } + mHTML=""; + } +} + +BOOL LLTextEditor::selectionContainsLineBreaks() +{ + if (hasSelection()) + { + S32 left = llmin(mSelectionStart, mSelectionEnd); + S32 right = left + abs(mSelectionStart - mSelectionEnd); + + const LLWString &wtext = mWText; + for( S32 i = left; i < right; i++ ) + { + if (wtext[i] == '\n') + { + return TRUE; + } + } + } + return FALSE; +} + + +S32 LLTextEditor::indentLine( S32 pos, S32 spaces ) +{ + // Assumes that pos is at the start of the line + // spaces may be positive (indent) or negative (unindent). + // Returns the actual number of characters added or removed. + + llassert(pos >= 0); + llassert(pos <= getLength() ); + + S32 delta_spaces = 0; + + if (spaces >= 0) + { + // Indent + for(S32 i=0; i < spaces; i++) + { + delta_spaces += addChar(pos, ' '); + } + } + else + { + // Unindent + for(S32 i=0; i < -spaces; i++) + { + const LLWString &wtext = mWText; + if (wtext[pos] == ' ') + { + delta_spaces += remove( pos, 1, FALSE ); + } + } + } + + return delta_spaces; +} + +void LLTextEditor::indentSelectedLines( S32 spaces ) +{ + if( hasSelection() ) + { + const LLWString &text = mWText; + S32 left = llmin( mSelectionStart, mSelectionEnd ); + S32 right = left + abs( mSelectionStart - mSelectionEnd ); + BOOL cursor_on_right = (mSelectionEnd > mSelectionStart); + S32 cur = left; + + // Expand left to start of line + while( (cur > 0) && (text[cur] != '\n') ) + { + cur--; + } + left = cur; + if( cur > 0 ) + { + left++; + } + + // Expand right to end of line + if( text[right - 1] == '\n' ) + { + right--; + } + else + { + while( (text[right] != '\n') && (right <= getLength() ) ) + { + right++; + } + } + + // Find each start-of-line and indent it + do + { + if( text[cur] == '\n' ) + { + cur++; + } + + S32 delta_spaces = indentLine( cur, spaces ); + if( delta_spaces > 0 ) + { + cur += delta_spaces; + } + right += delta_spaces; + + //text = mWText; + + // Find the next new line + while( (cur < right) && (text[cur] != '\n') ) + { + cur++; + } + } + while( cur < right ); + + if( (right < getLength()) && (text[right] == '\n') ) + { + right++; + } + + // Set the selection and cursor + if( cursor_on_right ) + { + mSelectionStart = left; + mSelectionEnd = right; + } + else + { + mSelectionStart = right; + mSelectionEnd = left; + } + mCursorPos = mSelectionEnd; + } +} + + +BOOL LLTextEditor::canSelectAll() +{ + return TRUE; +} + +void LLTextEditor::selectAll() +{ + mSelectionStart = getLength(); + mSelectionEnd = 0; + mCursorPos = mSelectionEnd; +} + + +BOOL LLTextEditor::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen) +{ + if (pointInView(x, y) && getVisible()) + { + for ( child_list_const_iter_t child_it = getChildList()->begin(); + child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + if( viewp->handleToolTip(local_x, local_y, msg, sticky_rect_screen ) ) + { + return TRUE; + } + } + + if( mSegments.empty() ) + { + return TRUE; + } + + LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y ); + if( cur_segment ) + { + BOOL has_tool_tip = FALSE; + has_tool_tip = cur_segment->getToolTip( msg ); + + if( has_tool_tip ) + { + // Just use a slop area around the cursor + // Convert rect local to screen coordinates + S32 SLOP = 8; + localPointToScreen( + x - SLOP, y - SLOP, + &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); + sticky_rect_screen->mRight = sticky_rect_screen->mLeft + 2 * SLOP; + sticky_rect_screen->mTop = sticky_rect_screen->mBottom + 2 * SLOP; + } + } + return TRUE; + } + return FALSE; +} + +BOOL LLTextEditor::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + // Pretend the mouse is over the scrollbar + if (getVisible()) + { + return mScrollbar->handleScrollWheel( 0, 0, clicks ); + } + else + { + return FALSE; + } +} + +BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + // Let scrollbar have first dibs + handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; + + if( !handled && mTakesNonScrollClicks) + { + if (!(mask & MASK_SHIFT)) + { + deselect(); + } + + BOOL start_select = TRUE; + if( start_select ) + { + // If we're not scrolling (handled by child), then we're selecting + if (mask & MASK_SHIFT) + { + S32 old_cursor_pos = mCursorPos; + setCursorAtLocalPos( x, y, TRUE ); + + if (hasSelection()) + { + /* Mac-like behavior - extend selection towards the cursor + if (mCursorPos < mSelectionStart + && mCursorPos < mSelectionEnd) + { + // ...left of selection + mSelectionStart = llmax(mSelectionStart, mSelectionEnd); + mSelectionEnd = mCursorPos; + } + else if (mCursorPos > mSelectionStart + && mCursorPos > mSelectionEnd) + { + // ...right of selection + mSelectionStart = llmin(mSelectionStart, mSelectionEnd); + mSelectionEnd = mCursorPos; + } + else + { + mSelectionEnd = mCursorPos; + } + */ + // Windows behavior + mSelectionEnd = mCursorPos; + } + else + { + mSelectionStart = old_cursor_pos; + mSelectionEnd = mCursorPos; + } + // assume we're starting a drag select + mIsSelecting = TRUE; + } + else + { + setCursorAtLocalPos( x, y, TRUE ); + startSelection(); + } + gFocusMgr.setMouseCapture( this, &LLTextEditor::onMouseCaptureLost ); + } + + handled = TRUE; + } + + if (mTakesFocus) + { + setFocus( TRUE ); + handled = TRUE; + } + + // Delay cursor flashing + mKeystrokeTimer.reset(); + + return handled; +} + + +BOOL LLTextEditor::handleHover(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + mHoverSegment = NULL; + if( getVisible() ) + { + if(gFocusMgr.getMouseCapture() == this ) + { + if( mIsSelecting ) + { + if (x != mLastSelectionX || y != mLastSelectionY) + { + mLastSelectionX = x; + mLastSelectionY = y; + } + + if( y > mTextRect.mTop ) + { + mScrollbar->setDocPos( mScrollbar->getDocPos() - 1 ); + } + else + if( y < mTextRect.mBottom ) + { + mScrollbar->setDocPos( mScrollbar->getDocPos() + 1 ); + } + + setCursorAtLocalPos( x, y, TRUE ); + mSelectionEnd = mCursorPos; + + updateScrollFromCursor(); + } + + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; + getWindow()->setCursor(UI_CURSOR_IBEAM); + handled = TRUE; + } + + if( !handled ) + { + // Pass to children + handled = LLView::childrenHandleHover(x, y, mask) != NULL; + } + + if( handled ) + { + // Delay cursor flashing + mKeystrokeTimer.reset(); + } + + // Opaque + if( !handled && mTakesNonScrollClicks) + { + // Check to see if we're over an HTML-style link + if( !mSegments.empty() ) + { + LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y ); + if( cur_segment ) + { + if(cur_segment->getStyle().isLink()) + { + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over link, inactive)" << llendl; + getWindow()->setCursor(UI_CURSOR_HAND); + handled = TRUE; + } + else + if(cur_segment->getStyle().getIsEmbeddedItem()) + { + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over embedded item, inactive)" << llendl; + getWindow()->setCursor(UI_CURSOR_HAND); + //getWindow()->setCursor(UI_CURSOR_ARROW); + handled = TRUE; + } + mHoverSegment = cur_segment; + } + } + + if( !handled ) + { + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl; + if (!mScrollbar->getVisible() || x < mRect.getWidth() - SCROLLBAR_SIZE) + { + getWindow()->setCursor(UI_CURSOR_IBEAM); + } + else + { + getWindow()->setCursor(UI_CURSOR_ARROW); + } + handled = TRUE; + } + } + } + + if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) + { + mOnScrollEndCallback(mOnScrollEndData); + } + return handled; +} + + +BOOL LLTextEditor::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + // let scrollbar have first dibs + handled = LLView::childrenHandleMouseUp(x, y, mask) != NULL; + + if( !handled && mTakesNonScrollClicks) + { + if( mIsSelecting ) + { + // Finish selection + if( y > mTextRect.mTop ) + { + mScrollbar->setDocPos( mScrollbar->getDocPos() - 1 ); + } + else + if( y < mTextRect.mBottom ) + { + mScrollbar->setDocPos( mScrollbar->getDocPos() + 1 ); + } + + setCursorAtLocalPos( x, y, TRUE ); + endSelection(); + + updateScrollFromCursor(); + } + + if( !hasSelection() ) + { + handleMouseUpOverSegment( x, y, mask ); + } + + handled = TRUE; + } + + // Delay cursor flashing + mKeystrokeTimer.reset(); + + if( gFocusMgr.getMouseCapture() == this ) + { + gFocusMgr.setMouseCapture( NULL, NULL ); + handled = TRUE; + } + + return handled; +} + + +BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + // let scrollbar have first dibs + handled = LLView::childrenHandleDoubleClick(x, y, mask) != NULL; + + if( !handled && mTakesNonScrollClicks) + { + if (mTakesFocus) + { + setFocus( TRUE ); + } + + setCursorAtLocalPos( x, y, FALSE ); + deselect(); + + const LLWString &text = mWText; + + if( isPartOfWord( text[mCursorPos] ) ) + { + // Select word the cursor is over + while ((mCursorPos > 0) && isPartOfWord(text[mCursorPos-1])) + { + mCursorPos--; + } + startSelection(); + + while ((mCursorPos < (S32)text.length()) && isPartOfWord( text[mCursorPos] ) ) + { + mCursorPos++; + } + + mSelectionEnd = mCursorPos; + } + else if ((mCursorPos < (S32)text.length()) && !iswspace( text[mCursorPos]) ) + { + // Select the character the cursor is over + startSelection(); + mCursorPos++; + mSelectionEnd = mCursorPos; + } + + // We don't want handleMouseUp() to "finish" the selection (and thereby + // set mSelectionEnd to where the mouse is), so we finish the selection here. + mIsSelecting = FALSE; + + // delay cursor flashing + mKeystrokeTimer.reset(); + + handled = TRUE; + } + return handled; +} + + +// Allow calling cards to be dropped onto text fields. Append the name and +// a carriage return. +// virtual +BOOL LLTextEditor::handleDragAndDrop(S32 x, S32 y, MASK mask, + BOOL drop, EDragAndDropType cargo_type, void *cargo_data, + EAcceptance *accept, + LLString& tooltip_msg) +{ + *accept = ACCEPT_NO; + + return TRUE; +} + +//---------------------------------------------------------------------------- +// Returns change in number of characters in mText + +S32 LLTextEditor::execute( LLTextCmd* cmd ) +{ + S32 delta = 0; + if( cmd->execute(this, &delta) ) + { + // Delete top of undo stack + undo_stack_t::iterator enditer = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd); + if (enditer != mUndoStack.begin()) + { + --enditer; + std::for_each(mUndoStack.begin(), enditer, DeletePointer()); + mUndoStack.erase(mUndoStack.begin(), enditer); + } + // Push the new command is now on the top (front) of the undo stack. + mUndoStack.push_front(cmd); + mLastCmd = cmd; + } + else + { + // Operation failed, so don't put it on the undo stack. + delete cmd; + } + + return delta; +} + +S32 LLTextEditor::insert(const S32 pos, const LLWString &wstr, const BOOL group_with_next_op) +{ + return execute( new LLTextCmdInsert( pos, group_with_next_op, wstr ) ); +} + +S32 LLTextEditor::remove(const S32 pos, const S32 length, const BOOL group_with_next_op) +{ + return execute( new LLTextCmdRemove( pos, group_with_next_op, length ) ); +} + +S32 LLTextEditor::append(const LLWString &wstr, const BOOL group_with_next_op) +{ + return insert(mWText.length(), wstr, group_with_next_op); +} + +S32 LLTextEditor::overwriteChar(S32 pos, llwchar wc) +{ + if ((S32)mWText.length() == pos) + { + return addChar(pos, wc); + } + else + { + return execute(new LLTextCmdOverwriteChar(pos, FALSE, wc)); + } +} + +// Remove a single character from the text. Tries to remove +// a pseudo-tab (up to for spaces in a row) +void LLTextEditor::removeCharOrTab() +{ + if( getEnabled() ) + { + if( mCursorPos > 0 ) + { + S32 chars_to_remove = 1; + + const LLWString &text = mWText; + if (text[mCursorPos - 1] == ' ') + { + // Try to remove a "tab" + S32 line, offset; + getLineAndOffset(mCursorPos, &line, &offset); + if (offset > 0) + { + chars_to_remove = offset % SPACES_PER_TAB; + if( chars_to_remove == 0 ) + { + chars_to_remove = SPACES_PER_TAB; + } + + for( S32 i = 0; i < chars_to_remove; i++ ) + { + if (text[ mCursorPos - i - 1] != ' ') + { + // Fewer than a full tab's worth of spaces, so + // just delete a single character. + chars_to_remove = 1; + break; + } + } + } + } + + for (S32 i = 0; i < chars_to_remove; i++) + { + setCursorPos(mCursorPos - 1); + remove( mCursorPos, 1, FALSE ); + } + } + else + { + reportBadKeystroke(); + } + } +} + +// Remove a single character from the text +S32 LLTextEditor::removeChar(S32 pos) +{ + return remove( pos, 1, FALSE ); +} + +void LLTextEditor::removeChar() +{ + if (getEnabled()) + { + if (mCursorPos > 0) + { + setCursorPos(mCursorPos - 1); + removeChar(mCursorPos); + } + else + { + reportBadKeystroke(); + } + } +} + +// Add a single character to the text +S32 LLTextEditor::addChar(S32 pos, llwchar wc) +{ + if ((S32)mWText.length() == mMaxTextLength) + { + make_ui_sound("UISndBadKeystroke"); + return 0; + } + + if (mLastCmd && mLastCmd->canExtend(pos)) + { + S32 delta = 0; + mLastCmd->extendAndExecute(this, pos, wc, &delta); + return delta; + } + else + { + return execute(new LLTextCmdAddChar(pos, FALSE, wc)); + } +} + +void LLTextEditor::addChar(llwchar wc) +{ + if( getEnabled() ) + { + if( hasSelection() ) + { + deleteSelection(TRUE); + } + else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + removeChar(mCursorPos); + } + + setCursorPos(mCursorPos + addChar( mCursorPos, wc )); + } +} + + +BOOL LLTextEditor::handleSelectionKey(const KEY key, const MASK mask) +{ + BOOL handled = FALSE; + + if( mask & MASK_SHIFT ) + { + handled = TRUE; + + switch( key ) + { + case KEY_LEFT: + if( 0 < mCursorPos ) + { + startSelection(); + mCursorPos--; + if( mask & MASK_CONTROL ) + { + mCursorPos = prevWordPos(mCursorPos); + } + mSelectionEnd = mCursorPos; + } + break; + + case KEY_RIGHT: + if( mCursorPos < getLength() ) + { + startSelection(); + mCursorPos++; + if( mask & MASK_CONTROL ) + { + mCursorPos = nextWordPos(mCursorPos); + } + mSelectionEnd = mCursorPos; + } + break; + + case KEY_UP: + startSelection(); + changeLine( -1 ); + mSelectionEnd = mCursorPos; + break; + + case KEY_PAGE_UP: + startSelection(); + changePage( -1 ); + mSelectionEnd = mCursorPos; + break; + + case KEY_HOME: + startSelection(); + if( mask & MASK_CONTROL ) + { + mCursorPos = 0; + } + else + { + startOfLine(); + } + mSelectionEnd = mCursorPos; + break; + + case KEY_DOWN: + startSelection(); + changeLine( 1 ); + mSelectionEnd = mCursorPos; + break; + + case KEY_PAGE_DOWN: + startSelection(); + changePage( 1 ); + mSelectionEnd = mCursorPos; + break; + + case KEY_END: + startSelection(); + if( mask & MASK_CONTROL ) + { + mCursorPos = getLength(); + } + else + { + endOfLine(); + } + mSelectionEnd = mCursorPos; + break; + + default: + handled = FALSE; + break; + } + } + + + if( !handled && mHandleEditKeysDirectly ) + { + if( (MASK_CONTROL & mask) && ('A' == key) ) + { + if( canSelectAll() ) + { + selectAll(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + } + + return handled; +} + +BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) +{ + BOOL handled = FALSE; + + // Ignore capslock key + if( MASK_NONE == mask ) + { + handled = TRUE; + switch( key ) + { + case KEY_UP: + if (mReadOnly) + { + mScrollbar->setDocPos(mScrollbar->getDocPos() - 1); + } + else + { + changeLine( -1 ); + } + break; + + case KEY_PAGE_UP: + changePage( -1 ); + break; + + case KEY_HOME: + if (mReadOnly) + { + mScrollbar->setDocPos(0); + } + else + { + startOfLine(); + } + break; + + case KEY_DOWN: + if (mReadOnly) + { + mScrollbar->setDocPos(mScrollbar->getDocPos() + 1); + } + else + { + changeLine( 1 ); + } + break; + + case KEY_PAGE_DOWN: + changePage( 1 ); + break; + + case KEY_END: + if (mReadOnly) + { + mScrollbar->setDocPos(mScrollbar->getDocPosMax()); + } + else + { + endOfLine(); + } + break; + + case KEY_LEFT: + if (mReadOnly) + { + break; + } + if( hasSelection() ) + { + setCursorPos(llmin( mCursorPos - 1, mSelectionStart, mSelectionEnd )); + } + else + { + if( 0 < mCursorPos ) + { + setCursorPos(mCursorPos - 1); + } + else + { + reportBadKeystroke(); + } + } + break; + + case KEY_RIGHT: + if (mReadOnly) + { + break; + } + if( hasSelection() ) + { + setCursorPos(llmax( mCursorPos + 1, mSelectionStart, mSelectionEnd )); + } + else + { + if( mCursorPos < getLength() ) + { + setCursorPos(mCursorPos + 1); + } + else + { + reportBadKeystroke(); + } + } + break; + + default: + handled = FALSE; + break; + } + } + + if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) + { + mOnScrollEndCallback(mOnScrollEndData); + } + return handled; +} + +void LLTextEditor::deleteSelection(BOOL group_with_next_op ) +{ + if( getEnabled() && hasSelection() ) + { + S32 pos = llmin( mSelectionStart, mSelectionEnd ); + S32 length = abs( mSelectionStart - mSelectionEnd ); + + remove( pos, length, group_with_next_op ); + + deselect(); + setCursorPos(pos); + } +} + +BOOL LLTextEditor::canCut() +{ + return !mReadOnly && hasSelection(); +} + +// cut selection to clipboard +void LLTextEditor::cut() +{ + if( canCut() ) + { + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + S32 length = abs( mSelectionStart - mSelectionEnd ); + gClipboard.copyFromSubstring( mWText, left_pos, length, mSourceID ); + deleteSelection( FALSE ); + + updateLineStartList(); + updateScrollFromCursor(); + } +} + +BOOL LLTextEditor::canCopy() +{ + return hasSelection(); +} + + +// copy selection to clipboard +void LLTextEditor::copy() +{ + if( canCopy() ) + { + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + S32 length = abs( mSelectionStart - mSelectionEnd ); + gClipboard.copyFromSubstring(mWText, left_pos, length, mSourceID); + } +} + +BOOL LLTextEditor::canPaste() +{ + return !mReadOnly && gClipboard.canPasteString(); +} + + +// paste from clipboard +void LLTextEditor::paste() +{ + if (canPaste()) + { + LLUUID source_id; + LLWString paste = gClipboard.getPasteWString(&source_id); + if (!paste.empty()) + { + // Delete any selected characters (the paste replaces them) + if( hasSelection() ) + { + deleteSelection(TRUE); + } + + // Clean up string (replace tabs and remove characters that our fonts don't support). + LLWString clean_string(paste); + LLWString::replaceTabsWithSpaces(clean_string, SPACES_PER_TAB); + if( mAllowEmbeddedItems ) + { + const llwchar LF = 10; + S32 len = clean_string.length(); + for( S32 i = 0; i < len; i++ ) + { + llwchar wc = clean_string[i]; + if( (wc < LLFont::FIRST_CHAR) && (wc != LF) ) + { + clean_string[i] = LL_UNKNOWN_CHAR; + } + else if (wc >= FIRST_EMBEDDED_CHAR && wc <= LAST_EMBEDDED_CHAR) + { + clean_string[i] = pasteEmbeddedItem(wc); + } + } + } + + // Insert the new text into the existing text. + setCursorPos(mCursorPos + insert(mCursorPos, clean_string, FALSE)); + deselect(); + + updateLineStartList(); + updateScrollFromCursor(); + } + } +} + + +BOOL LLTextEditor::handleControlKey(const KEY key, const MASK mask) +{ + BOOL handled = FALSE; + + if( mask & MASK_CONTROL ) + { + handled = TRUE; + + switch( key ) + { + case KEY_HOME: + if( mask & MASK_SHIFT ) + { + startSelection(); + mCursorPos = 0; + mSelectionEnd = mCursorPos; + } + else + { + // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down + // all move the cursor as if clicking, so should deselect. + deselect(); + setCursorPos(0); + } + break; + + case KEY_END: + { + if( mask & MASK_SHIFT ) + { + startSelection(); + } + else + { + // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down + // all move the cursor as if clicking, so should deselect. + deselect(); + } + endOfDoc(); + if( mask & MASK_SHIFT ) + { + mSelectionEnd = mCursorPos; + } + break; + } + + case KEY_RIGHT: + if( mCursorPos < getLength() ) + { + // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down + // all move the cursor as if clicking, so should deselect. + deselect(); + + setCursorPos(nextWordPos(mCursorPos + 1)); + } + break; + + + case KEY_LEFT: + if( mCursorPos > 0 ) + { + // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down + // all move the cursor as if clicking, so should deselect. + deselect(); + + setCursorPos(prevWordPos(mCursorPos - 1)); + } + break; + + default: + handled = FALSE; + break; + } + } + + return handled; +} + +BOOL LLTextEditor::handleEditKey(const KEY key, const MASK mask) +{ + BOOL handled = FALSE; + + // Standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system. + if( KEY_DELETE == key ) + { + if( canDoDelete() ) + { + doDelete(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + else + if( MASK_CONTROL & mask ) + { + if( 'C' == key ) + { + if( canCopy() ) + { + copy(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + else + if( 'V' == key ) + { + if( canPaste() ) + { + paste(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + else + if( 'X' == key ) + { + if( canCut() ) + { + cut(); + } + else + { + reportBadKeystroke(); + } + handled = TRUE; + } + } + + return handled; +} + + +BOOL LLTextEditor::handleSpecialKey(const KEY key, const MASK mask, BOOL* return_key_hit) +{ + *return_key_hit = FALSE; + BOOL handled = TRUE; + + switch( key ) + { + case KEY_INSERT: + if (mask == MASK_NONE) + { + gKeyboard->toggleInsertMode(); + } + break; + + case KEY_BACKSPACE: + if( hasSelection() ) + { + deleteSelection(FALSE); + } + else + if( 0 < mCursorPos ) + { + removeCharOrTab(); + } + else + { + reportBadKeystroke(); + } + break; + + + case KEY_RETURN: + if (mask == MASK_NONE) + { + if( hasSelection() ) + { + deleteSelection(FALSE); + } + autoIndent(); // TODO: make this optional + } + else + { + handled = FALSE; + break; + } + break; + + case KEY_TAB: + if (mask & MASK_CONTROL) + { + handled = FALSE; + break; + } + if( hasSelection() && selectionContainsLineBreaks() ) + { + indentSelectedLines( (mask & MASK_SHIFT) ? -SPACES_PER_TAB : SPACES_PER_TAB ); + } + else + { + if( hasSelection() ) + { + deleteSelection(FALSE); + } + + S32 line, offset; + getLineAndOffset( mCursorPos, &line, &offset ); + + S32 spaces_needed = SPACES_PER_TAB - (offset % SPACES_PER_TAB); + for( S32 i=0; i < spaces_needed; i++ ) + { + addChar( ' ' ); + } + } + break; + + default: + handled = FALSE; + break; + } + + return handled; +} + + +void LLTextEditor::unindentLineBeforeCloseBrace() +{ + if( mCursorPos >= 1 ) + { + const LLWString &text = mWText; + if( ' ' == text[ mCursorPos - 1 ] ) + { + removeCharOrTab(); + } + } +} + + +BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent ) +{ + BOOL handled = FALSE; + BOOL selection_modified = FALSE; + BOOL return_key_hit = FALSE; + BOOL text_may_have_changed = TRUE; + + if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible()) + { + // Special case for TAB. If want to move to next field, report + // not handled and let the parent take care of field movement. + if (KEY_TAB == key && mTabToNextField) + { + return FALSE; + } + + handled = handleNavigationKey( key, mask ); + if( handled ) + { + text_may_have_changed = FALSE; + } + + if( !handled ) + { + handled = handleSelectionKey( key, mask ); + if( handled ) + { + selection_modified = TRUE; + } + } + + if( !handled ) + { + handled = handleControlKey( key, mask ); + if( handled ) + { + selection_modified = TRUE; + } + } + + if( !handled && mHandleEditKeysDirectly ) + { + handled = handleEditKey( key, mask ); + if( handled ) + { + selection_modified = TRUE; + text_may_have_changed = TRUE; + } + } + + // Handle most keys only if the text editor is writeable. + if( !mReadOnly ) + { + if( !handled ) + { + handled = handleSpecialKey( key, mask, &return_key_hit ); + if( handled ) + { + selection_modified = TRUE; + text_may_have_changed = TRUE; + } + } + + } + + if( handled ) + { + mKeystrokeTimer.reset(); + + // Most keystrokes will make the selection box go away, but not all will. + if( !selection_modified && + KEY_SHIFT != key && + KEY_CONTROL != key && + KEY_ALT != key && + KEY_CAPSLOCK ) + { + deselect(); + } + + if(text_may_have_changed) + { + updateLineStartList(); + } + updateScrollFromCursor(); + } + } + + return handled; +} + + +BOOL LLTextEditor::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent) +{ + if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL + { + return FALSE; + } + + BOOL handled = FALSE; + + if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible()) + { + // Handle most keys only if the text editor is writeable. + if( !mReadOnly ) + { + if( '}' == uni_char ) + { + unindentLineBeforeCloseBrace(); + } + + // TODO: KLW Add auto show of tool tip on ( + addChar( uni_char ); + + // Keys that add characters temporarily hide the cursor + getWindow()->hideCursorUntilMouseMove(); + + handled = TRUE; + } + + if( handled ) + { + mKeystrokeTimer.reset(); + + // Most keystrokes will make the selection box go away, but not all will. + deselect(); + + updateLineStartList(); + updateScrollFromCursor(); + } + } + + return handled; +} + + + +BOOL LLTextEditor::canDoDelete() +{ + return !mReadOnly && ( hasSelection() || (mCursorPos < getLength()) ); +} + +void LLTextEditor::doDelete() +{ + if( canDoDelete() ) + { + if( hasSelection() ) + { + deleteSelection(FALSE); + } + else + if( mCursorPos < getLength() ) + { + S32 i; + S32 chars_to_remove = 1; + const LLWString &text = mWText; + if( (text[ mCursorPos ] == ' ') && (mCursorPos + SPACES_PER_TAB < getLength()) ) + { + // Try to remove a full tab's worth of spaces + S32 line, offset; + getLineAndOffset( mCursorPos, &line, &offset ); + chars_to_remove = SPACES_PER_TAB - (offset % SPACES_PER_TAB); + if( chars_to_remove == 0 ) + { + chars_to_remove = SPACES_PER_TAB; + } + + for( i = 0; i < chars_to_remove; i++ ) + { + if( text[mCursorPos + i] != ' ' ) + { + chars_to_remove = 1; + break; + } + } + } + + + for( i = 0; i < chars_to_remove; i++ ) + { + setCursorPos(mCursorPos + 1); + removeChar(); + } + } + + updateLineStartList(); + updateScrollFromCursor(); + } +} + +//---------------------------------------------------------------------------- + + +void LLTextEditor::blockUndo() +{ + mBaseDocIsPristine = FALSE; + mLastCmd = NULL; + std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer()); + mUndoStack.clear(); +} + + +BOOL LLTextEditor::canUndo() +{ + return !mReadOnly && mLastCmd != NULL; +} + +void LLTextEditor::undo() +{ + if( canUndo() ) + { + deselect(); + + S32 pos = 0; + do + { + pos = mLastCmd->undo(this); + undo_stack_t::iterator iter = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd); + if (iter != mUndoStack.end()) + ++iter; + if (iter != mUndoStack.end()) + mLastCmd = *iter; + else + mLastCmd = NULL; + + } while( mLastCmd && mLastCmd->groupWithNext() ); + + setCursorPos(pos); + + updateLineStartList(); + updateScrollFromCursor(); + } +} + +BOOL LLTextEditor::canRedo() +{ + return !mReadOnly && (mUndoStack.size() > 0) && (mLastCmd != mUndoStack.front()); +} + +void LLTextEditor::redo() +{ + if( canRedo() ) + { + deselect(); + + S32 pos = 0; + do + { + if( !mLastCmd ) + { + mLastCmd = mUndoStack.back(); + } + else + { + undo_stack_t::iterator iter = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd); + if (iter != mUndoStack.begin()) + mLastCmd = *(--iter); + else + mLastCmd = NULL; + } + + if( mLastCmd ) + { + pos = mLastCmd->redo(this); + } + } while( + mLastCmd && + mLastCmd->groupWithNext() && + (mLastCmd != mUndoStack.front()) ); + + setCursorPos(pos); + + updateLineStartList(); + updateScrollFromCursor(); + } +} + + +// virtual, from LLView +void LLTextEditor::onFocusLost() +{ + // Route menu back to the default + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } + + if (mCommitOnFocusLost) + { + onCommit(); + } + + // Make sure cursor is shown again + getWindow()->showCursorFromMouseMove(); +} + +void LLTextEditor::setEnabled(BOOL enabled) +{ + // just treat enabled as read-only flag + BOOL read_only = !enabled; + if (read_only != mReadOnly) + { + mReadOnly = read_only; + updateSegments(); + } +} + +void LLTextEditor::drawBackground() +{ + S32 left = 0; + S32 top = mRect.getHeight(); + S32 right = mRect.getWidth(); + S32 bottom = 0; + + LLColor4 bg_color = mReadOnlyBgColor; + + if( !mReadOnly ) + { + if (gFocusMgr.getKeyboardFocus() == this) + { + bg_color = mFocusBgColor; + } + else + { + bg_color = mWriteableBgColor; + } + } + gl_rect_2d(left, top, right, bottom, bg_color); + + LLView::draw(); +} + +// Draws the black box behind the selected text +void LLTextEditor::drawSelectionBackground() +{ + // Draw selection even if we don't have keyboard focus for search/replace + if( hasSelection() ) + { + const LLWString &text = mWText; + const S32 text_len = getLength(); + std::queue line_endings; + + S32 line_height = llround( mGLFont->getLineHeight() ); + + S32 selection_left = llmin( mSelectionStart, mSelectionEnd ); + S32 selection_right = llmax( mSelectionStart, mSelectionEnd ); + S32 selection_left_x = mTextRect.mLeft; + S32 selection_left_y = mTextRect.mTop - line_height; + S32 selection_right_x = mTextRect.mRight; + S32 selection_right_y = mTextRect.mBottom; + + BOOL selection_left_visible = FALSE; + BOOL selection_right_visible = FALSE; + + // Skip through the lines we aren't drawing. + S32 cur_line = mScrollbar->getDocPos(); + + S32 left_line_num = cur_line; + S32 num_lines = getLineCount(); + S32 right_line_num = num_lines - 1; + + S32 line_start = -1; + if (cur_line >= num_lines) + { + return; + } + + line_start = getLineStart(cur_line); + + S32 left_visible_pos = line_start; + S32 right_visible_pos = line_start; + + S32 text_y = mTextRect.mTop - line_height; + + // Find the coordinates of the selected area + while((cur_line < num_lines)) + { + S32 next_line = -1; + S32 line_end = text_len; + + if ((cur_line + 1) < num_lines) + { + next_line = getLineStart(cur_line + 1); + line_end = next_line; + + line_end = ( (line_end - line_start)==0 || text[next_line-1] == '\n' || text[next_line-1] == '\0' || text[next_line-1] == ' ' || text[next_line-1] == '\t' ) ? next_line-1 : next_line; + } + + const llwchar* line = text.c_str() + line_start; + + if( line_start <= selection_left && selection_left <= line_end ) + { + left_line_num = cur_line; + selection_left_visible = TRUE; + selection_left_x = mTextRect.mLeft + mGLFont->getWidth(line, 0, selection_left - line_start, mAllowEmbeddedItems); + selection_left_y = text_y; + } + if( line_start <= selection_right && selection_right <= line_end ) + { + right_line_num = cur_line; + selection_right_visible = TRUE; + selection_right_x = mTextRect.mLeft + mGLFont->getWidth(line, 0, selection_right - line_start, mAllowEmbeddedItems); + if (selection_right == line_end) + { + // add empty space for "newline" + //selection_right_x += mGLFont->getWidth("n"); + } + selection_right_y = text_y; + } + + // if selection spans end of current line... + if (selection_left <= line_end && line_end < selection_right && selection_left != selection_right) + { + // extend selection slightly beyond end of line + // to indicate selection of newline character (use "n" character to determine width) + const LLWString nstr(utf8str_to_wstring(LLString("n"))); + line_endings.push(mTextRect.mLeft + mGLFont->getWidth(line, 0, line_end - line_start, mAllowEmbeddedItems) + mGLFont->getWidth(nstr.c_str())); + } + + // move down one line + text_y -= line_height; + + right_visible_pos = line_end; + line_start = next_line; + cur_line++; + + if (selection_right_visible) + { + break; + } + } + + // Draw the selection box (we're using a box instead of reversing the colors on the selected text). + BOOL selection_visible = (left_visible_pos <= selection_right) && (selection_left <= right_visible_pos); + if( selection_visible ) + { + LLGLSNoTexture no_texture; + const LLColor4& color = mReadOnly ? mReadOnlyBgColor : mWriteableBgColor; + glColor3f( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2] ); + + if( selection_left_y == selection_right_y ) + { + // Draw from selection start to selection end + gl_rect_2d( selection_left_x, selection_left_y + line_height + 1, + selection_right_x, selection_right_y); + } + else + { + // Draw from selection start to the end of the first line + if( mTextRect.mRight == selection_left_x ) + { + selection_left_x -= CURSOR_THICKNESS; + } + + S32 line_end = line_endings.front(); + line_endings.pop(); + gl_rect_2d( selection_left_x, selection_left_y + line_height + 1, + line_end, selection_left_y ); + + S32 line_num = left_line_num + 1; + while(line_endings.size()) + { + S32 vert_offset = -(line_num - left_line_num) * line_height; + // Draw the block between the two lines + gl_rect_2d( mTextRect.mLeft, selection_left_y + vert_offset + line_height + 1, + line_endings.front(), selection_left_y + vert_offset); + line_endings.pop(); + line_num++; + } + + // Draw from the start of the last line to selection end + if( mTextRect.mLeft == selection_right_x ) + { + selection_right_x += CURSOR_THICKNESS; + } + gl_rect_2d( mTextRect.mLeft, selection_right_y + line_height + 1, + selection_right_x, selection_right_y ); + } + } + } +} + +void LLTextEditor::drawCursor() +{ + if( gFocusMgr.getKeyboardFocus() == this + && gShowTextEditCursor && !mReadOnly) + { + const LLWString &text = mWText; + const S32 text_len = getLength(); + + // Skip through the lines we aren't drawing. + S32 cur_pos = mScrollbar->getDocPos(); + + S32 num_lines = getLineCount(); + if (cur_pos >= num_lines) + { + return; + } + S32 line_start = getLineStart(cur_pos); + + F32 line_height = mGLFont->getLineHeight(); + F32 text_y = (F32)(mTextRect.mTop) - line_height; + + F32 cursor_left = 0.f; + F32 next_char_left = 0.f; + F32 cursor_bottom = 0.f; + BOOL cursor_visible = FALSE; + + S32 line_end = 0; + // Determine if the cursor is visible and if so what its coordinates are. + while( (mTextRect.mBottom <= llround(text_y)) && (cur_pos < num_lines)) + { + line_end = text_len + 1; + S32 next_line = -1; + + if ((cur_pos + 1) < num_lines) + { + next_line = getLineStart(cur_pos + 1); + line_end = next_line - 1; + } + + const llwchar* line = text.c_str() + line_start; + + // Find the cursor and selection bounds + if( line_start <= mCursorPos && mCursorPos <= line_end ) + { + cursor_visible = TRUE; + next_char_left = (F32)mTextRect.mLeft + mGLFont->getWidthF32(line, 0, mCursorPos - line_start, mAllowEmbeddedItems ); + cursor_left = next_char_left - 1.f; + cursor_bottom = text_y; + break; + } + + // move down one line + text_y -= line_height; + line_start = next_line; + cur_pos++; + } + + // Draw the cursor + if( cursor_visible ) + { + // (Flash the cursor every half second starting a fixed time after the last keystroke) + F32 elapsed = mKeystrokeTimer.getElapsedTimeF32(); + if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) ) + { + F32 cursor_top = cursor_bottom + line_height + 1.f; + F32 cursor_right = cursor_left + (F32)CURSOR_THICKNESS; + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) + { + cursor_left += CURSOR_THICKNESS; + const LLWString space(utf8str_to_wstring(LLString(" "))); + F32 spacew = mGLFont->getWidthF32(space.c_str()); + if (mCursorPos == line_end) + { + cursor_right = cursor_left + spacew; + } + else + { + F32 width = mGLFont->getWidthF32(text.c_str(), mCursorPos, 1, mAllowEmbeddedItems); + cursor_right = cursor_left + llmax(spacew, width); + } + } + + LLGLSNoTexture no_texture; + + glColor4fv( mCursorColor.mV ); + + gl_rect_2d(llfloor(cursor_left), llfloor(cursor_top), + llfloor(cursor_right), llfloor(cursor_bottom)); + + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection() && text[mCursorPos] != '\n') + { + LLTextSegment* segmentp = getSegmentAtOffset(mCursorPos); + LLColor4 text_color; + if (segmentp) + { + text_color = segmentp->getColor(); + } + else if (mReadOnly) + { + text_color = mReadOnlyFgColor; + } + else + { + text_color = mFgColor; + } + LLGLSTexture texture; + mGLFont->render(text, mCursorPos, next_char_left, cursor_bottom + line_height, + LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], 1.f), + LLFontGL::LEFT, LLFontGL::TOP, + LLFontGL::NORMAL, + 1); + } + + + } + } + } +} + + +void LLTextEditor::drawText() +{ + const LLWString &text = mWText; + const S32 text_len = getLength(); + + if( text_len > 0 ) + { + S32 selection_left = -1; + S32 selection_right = -1; + // Draw selection even if we don't have keyboard focus for search/replace + if( hasSelection()) + { + selection_left = llmin( mSelectionStart, mSelectionEnd ); + selection_right = llmax( mSelectionStart, mSelectionEnd ); + } + + LLGLSUIDefault gls_ui; + + S32 cur_line = mScrollbar->getDocPos(); + S32 num_lines = getLineCount(); + if (cur_line >= num_lines) + { + return; + } + + S32 line_start = getLineStart(cur_line); + LLTextSegment t(line_start); + segment_list_t::iterator seg_iter; + seg_iter = std::upper_bound(mSegments.begin(), mSegments.end(), &t, LLTextSegment::compare()); + if (seg_iter == mSegments.end() || (*seg_iter)->getStart() > line_start) --seg_iter; + LLTextSegment* cur_segment = *seg_iter; + + S32 line_height = llround( mGLFont->getLineHeight() ); + F32 text_y = (F32)(mTextRect.mTop - line_height); + while((mTextRect.mBottom <= text_y) && (cur_line < num_lines)) + { + S32 next_start = -1; + S32 line_end = text_len; + + if ((cur_line + 1) < num_lines) + { + next_start = getLineStart(cur_line + 1); + line_end = next_start; + } + if ( text[line_end-1] == '\n' ) + { + --line_end; + } + + F32 text_x = (F32)mTextRect.mLeft; + + S32 seg_start = line_start; + while( seg_start < line_end ) + { + while( cur_segment->getEnd() <= seg_start ) + { + seg_iter++; + if (seg_iter == mSegments.end()) + { + llwarns << "Ran off the segmentation end!" << llendl; + return; + } + cur_segment = *seg_iter; + } + + // Draw a segment within the line + S32 clipped_end = llmin( line_end, cur_segment->getEnd() ); + S32 clipped_len = clipped_end - seg_start; + if( clipped_len > 0 ) + { + LLStyle style = cur_segment->getStyle(); + if ( style.isImage() && (cur_segment->getStart() >= seg_start) && (cur_segment->getStart() <= clipped_end)) + { + LLImageGL *image = style.getImage(); + + gl_draw_scaled_image( llround(text_x), llround(text_y)+line_height-style.mImageHeight, style.mImageWidth, style.mImageHeight, image, LLColor4::white ); + + } + + if (cur_segment == mHoverSegment && style.getIsEmbeddedItem()) + { + style.mUnderline = TRUE; + } + + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + + if ( (mParseHTML) && (left_pos > seg_start) && (left_pos < clipped_end) && mIsSelecting && (mSelectionStart == mSelectionEnd) ) + { + mHTML = style.getLinkHREF(); + } + + drawClippedSegment( text, seg_start, clipped_end, text_x, text_y, selection_left, selection_right, style, &text_x ); + + // Note: text_x is incremented by drawClippedSegment() + seg_start += clipped_len; + } + } + + // move down one line + text_y -= (F32)line_height; + + line_start = next_start; + cur_line++; + } + } +} + +// Draws a single text segment, reversing the color for selection if needed. +void LLTextEditor::drawClippedSegment(const LLWString &text, S32 seg_start, S32 seg_end, F32 x, F32 y, S32 selection_left, S32 selection_right, const LLStyle& style, F32* right_x ) +{ + const LLFontGL* font = mGLFont; + + LLColor4 color; + + if (!style.isVisible()) + { + return; + } + + color = style.getColor(); + + if ( style.getFontString()[0] ) + { + font = gResMgr->getRes(style.getFontID()); + } + + U8 font_flags = LLFontGL::NORMAL; + + if (style.mBold) + { + font_flags |= LLFontGL::BOLD; + } + if (style.mItalic) + { + font_flags |= LLFontGL::ITALIC; + } + if (style.mUnderline) + { + font_flags |= LLFontGL::UNDERLINE; + } + + if (style.getIsEmbeddedItem()) + { + if (mReadOnly) + { + color = LLUI::sColorsGroup->getColor("TextEmbeddedItemReadOnlyColor"); + } + else + { + color = LLUI::sColorsGroup->getColor("TextEmbeddedItemColor"); + } + } + + F32 y_top = y + (F32)llround(font->getLineHeight()); + + if( selection_left > seg_start ) + { + // Draw normally + S32 start = seg_start; + S32 end = llmin( selection_left, seg_end ); + S32 length = end - start; + font->render(text, start, x, y_top, color, LLFontGL::LEFT, LLFontGL::TOP, font_flags, length, S32_MAX, right_x, mAllowEmbeddedItems); + } + x = *right_x; + + if( (selection_left < seg_end) && (selection_right > seg_start) ) + { + // Draw reversed + S32 start = llmax( selection_left, seg_start ); + S32 end = llmin( selection_right, seg_end ); + S32 length = end - start; + + font->render(text, start, x, y_top, + LLColor4( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], 1.f ), + LLFontGL::LEFT, LLFontGL::TOP, font_flags, length, S32_MAX, right_x, mAllowEmbeddedItems); + } + x = *right_x; + if( selection_right < seg_end ) + { + // Draw normally + S32 start = llmax( selection_right, seg_start ); + S32 end = seg_end; + S32 length = end - start; + font->render(text, start, x, y_top, color, LLFontGL::LEFT, LLFontGL::TOP, font_flags, length, S32_MAX, right_x, mAllowEmbeddedItems); + } + } + + +void LLTextEditor::draw() +{ + if( getVisible() ) + { + { + LLGLEnable scissor_test(GL_SCISSOR_TEST); + LLUI::setScissorRegionLocal(LLRect(0, mRect.getHeight(), mRect.getWidth() - (mScrollbar->getVisible() ? SCROLLBAR_SIZE : 0), 0)); + + bindEmbeddedChars( mGLFont ); + + drawBackground(); + drawSelectionBackground(); + drawText(); + drawCursor(); + + unbindEmbeddedChars( mGLFont ); + + //RN: the decision was made to always show the orange border for keyboard focus but do not put an insertion caret + // when in readonly mode + mBorder->setKeyboardFocusHighlight( gFocusMgr.getKeyboardFocus() == this);// && !mReadOnly); + } + LLView::draw(); // Draw children (scrollbar and border) + } +} + +void LLTextEditor::reportBadKeystroke() +{ + make_ui_sound("UISndBadKeystroke"); +} + + +void LLTextEditor::onTabInto() +{ + // selecting all on tabInto causes users to hit tab twice and replace their text with a tab character + // theoretically, one could selectAll if mTabToNextField is true, but we couldn't think of a use case + // where you'd want to select all anyway + // preserve insertion point when returning to the editor + //selectAll(); +} + +void LLTextEditor::clear() +{ + setText(""); +} + +// Start or stop the editor from accepting text-editing keystrokes +// see also LLLineEditor +void LLTextEditor::setFocus( BOOL new_state ) +{ + BOOL old_state = hasFocus(); + + // Don't change anything if the focus state didn't change + if (new_state == old_state) return; + + LLUICtrl::setFocus( new_state ); + + if( new_state ) + { + // Route menu to this class + gEditMenuHandler = this; + + // Don't start the cursor flashing right away + mKeystrokeTimer.reset(); + } + else + { + // Route menu back to the default + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } + + endSelection(); + } +} + +BOOL LLTextEditor::acceptsTextInput() const +{ + return !mReadOnly; +} + +// Given a line (from the start of the doc) and an offset into the line, find the offset (pos) into text. +S32 LLTextEditor::getPos( S32 line, S32 offset ) +{ + S32 line_start = getLineStart(line); + S32 next_start = getLineStart(line+1); + if (next_start == line_start) + { + next_start = getLength() + 1; + } + S32 line_length = next_start - line_start - 1; + line_length = llmax(line_length, 0); + return line_start + llmin( offset, line_length ); +} + + +void LLTextEditor::changePage( S32 delta ) +{ + S32 line, offset; + getLineAndOffset( mCursorPos, &line, &offset ); + + // allow one line overlap + S32 page_size = mScrollbar->getPageSize() - 1; + if( delta == -1 ) + { + line = llmax( line - page_size, 0); + setCursorPos(getPos( line, offset )); + mScrollbar->setDocPos( mScrollbar->getDocPos() - page_size ); + } + else + if( delta == 1 ) + { + setCursorPos(getPos( line + page_size, offset )); + mScrollbar->setDocPos( mScrollbar->getDocPos() + page_size ); + } + if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) + { + mOnScrollEndCallback(mOnScrollEndData); + } +} + +void LLTextEditor::changeLine( S32 delta ) +{ + bindEmbeddedChars( mGLFont ); + + S32 line, offset; + getLineAndOffset( mCursorPos, &line, &offset ); + + S32 line_start = getLineStart(line); + + S32 desired_x_pixel; + + desired_x_pixel = mGLFont->getWidth(mWText.c_str(), line_start, offset, mAllowEmbeddedItems ); + + S32 new_line = 0; + if( (delta < 0) && (line > 0 ) ) + { + new_line = line - 1; + } + else + if( (delta > 0) && (line < (getLineCount() - 1)) ) + { + new_line = line + 1; + } + else + { + unbindEmbeddedChars( mGLFont ); + return; + } + + S32 num_lines = getLineCount(); + S32 new_line_start = getLineStart(new_line); + S32 new_line_end = getLength(); + if (new_line + 1 < num_lines) + { + new_line_end = getLineStart(new_line + 1) - 1; + } + + S32 new_line_len = new_line_end - new_line_start; + + S32 new_offset; + new_offset = mGLFont->charFromPixelOffset(mWText.c_str(), new_line_start, + (F32)desired_x_pixel, + (F32)mTextRect.getWidth(), + new_line_len, + mAllowEmbeddedItems); + + setCursorPos (getPos( new_line, new_offset )); + unbindEmbeddedChars( mGLFont ); +} + +void LLTextEditor::startOfLine() +{ + S32 line, offset; + getLineAndOffset( mCursorPos, &line, &offset ); + setCursorPos(mCursorPos - offset); +} + + +// public +void LLTextEditor::setCursorAndScrollToEnd() +{ + deselect(); + endOfDoc(); + updateScrollFromCursor(); +} + + +void LLTextEditor::getCurrentLineAndColumn( S32* line, S32* col, BOOL include_wordwrap ) +{ + if( include_wordwrap ) + { + getLineAndOffset( mCursorPos, line, col ); + } + else + { + const LLWString &text = mWText; + S32 line_count = 0; + S32 line_start = 0; + S32 i; + for( i = 0; text[i] && (i < mCursorPos); i++ ) + { + if( '\n' == text[i] ) + { + line_start = i + 1; + line_count++; + } + } + *line = line_count; + *col = i - line_start; + } +} + + +void LLTextEditor::endOfLine() +{ + S32 line, offset; + getLineAndOffset( mCursorPos, &line, &offset ); + S32 num_lines = getLineCount(); + if (line + 1 >= num_lines) + { + setCursorPos(getLength()); + } + else + { + setCursorPos( getLineStart(line + 1) - 1 ); + } +} + +void LLTextEditor::endOfDoc() +{ + mScrollbar->setDocPos( mScrollbar->getDocPosMax() ); + S32 len = getLength(); + if( len ) + { + setCursorPos(len); + } + if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) + { + mOnScrollEndCallback(mOnScrollEndData); + } +} + +// Sets the scrollbar from the cursor position +void LLTextEditor::updateScrollFromCursor() +{ + mScrollbar->setDocSize( getLineCount() ); + + if (mReadOnly) + { + // no cursor in read only mode + return; + } + + S32 line, offset; + getLineAndOffset( mCursorPos, &line, &offset ); + + S32 page_size = mScrollbar->getPageSize(); + + if( line < mScrollbar->getDocPos() ) + { + // scroll so that the cursor is at the top of the page + mScrollbar->setDocPos( line ); + } + else if( line >= mScrollbar->getDocPos() + page_size - 1 ) + { + S32 new_pos = 0; + if( line < mScrollbar->getDocSize() - 1 ) + { + // scroll so that the cursor is one line above the bottom of the page, + new_pos = line - page_size + 1; + } + else + { + // if there is less than a page of text remaining, scroll so that the cursor is at the bottom + new_pos = mScrollbar->getDocPosMax(); + } + mScrollbar->setDocPos( new_pos ); + } + + // Check if we've scrolled to bottom for callback if asked for callback + if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) + { + mOnScrollEndCallback(mOnScrollEndData); + } +} + +void LLTextEditor::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLView::reshape( width, height, called_from_parent ); + + updateTextRect(); + + S32 line_height = llround( mGLFont->getLineHeight() ); + S32 page_lines = mTextRect.getHeight() / line_height; + mScrollbar->setPageSize( page_lines ); + + updateLineStartList(); +} + +void LLTextEditor::autoIndent() +{ + // Count the number of spaces in the current line + S32 line, offset; + getLineAndOffset( mCursorPos, &line, &offset ); + S32 line_start = getLineStart(line); + S32 space_count = 0; + S32 i; + + const LLWString &text = mWText; + while( ' ' == text[line_start] ) + { + space_count++; + line_start++; + } + + // If we're starting a braced section, indent one level. + if( (mCursorPos > 0) && (text[mCursorPos -1] == '{') ) + { + space_count += SPACES_PER_TAB; + } + + // Insert that number of spaces on the new line + addChar( '\n' ); + for( i = 0; i < space_count; i++ ) + { + addChar( ' ' ); + } +} + +// Inserts new text at the cursor position +void LLTextEditor::insertText(const LLString &new_text) +{ + BOOL enabled = getEnabled(); + setEnabled( TRUE ); + + // Delete any selected characters (the insertion replaces them) + if( hasSelection() ) + { + deleteSelection(TRUE); + } + + setCursorPos(mCursorPos + insert( mCursorPos, utf8str_to_wstring(new_text), FALSE )); + + updateLineStartList(); + updateScrollFromCursor(); + + setEnabled( enabled ); +} + + +void LLTextEditor::appendColoredText(const LLString &new_text, + bool allow_undo, + bool prepend_newline, + const LLColor4 &color, + const LLString& font_name) +{ + LLStyle style; + style.setVisible(true); + style.setColor(color); + style.setFontName(font_name); + if(mParseHTML) + { + + S32 start=0,end=0; + LLString text = new_text; + while ( findHTML(text, &start, &end) ) + { + LLStyle html; + html.setVisible(true); + html.setColor(mLinkColor); + html.setFontName(font_name); + html.mUnderline = TRUE; + + if (start > 0) appendText(text.substr(0,start),allow_undo, prepend_newline, &style); + html.setLinkHREF(text.substr(start,end-start)); + appendText(text.substr(start, end-start),allow_undo, prepend_newline, &html); + if (end < (S32)text.length()) + { + text = text.substr(end,text.length() - end); + end=0; + } + else + { + break; + } + } + if (end < (S32)text.length()) appendText(text,allow_undo, prepend_newline, &style); + } + else + { + appendText(new_text, allow_undo, prepend_newline, &style); + } +} + +void LLTextEditor::appendStyledText(const LLString &new_text, + bool allow_undo, + bool prepend_newline, + const LLStyle &style) +{ + appendText(new_text, allow_undo, prepend_newline, &style); +} + +// Appends new text to end of document +void LLTextEditor::appendText(const LLString &new_text, bool allow_undo, bool prepend_newline, + const LLStyle* segment_style) +{ + // Save old state + BOOL was_scrolled_to_bottom = (mScrollbar->getDocPos() == mScrollbar->getDocPosMax()); + S32 selection_start = mSelectionStart; + S32 selection_end = mSelectionEnd; + S32 cursor_pos = mCursorPos; + S32 old_length = getLength(); + BOOL cursor_was_at_end = (mCursorPos == old_length); + + deselect(); + + setCursorPos(old_length); + + // Add carriage return if not first line + if (getLength() != 0 + && prepend_newline) + { + LLString final_text = "\n"; + final_text += new_text; + append(utf8str_to_wstring(final_text), TRUE); + } + else + { + append(utf8str_to_wstring(new_text), TRUE ); + } + + if (segment_style) + { + S32 segment_start = old_length; + S32 segment_end = getLength(); + LLTextSegment* segment = new LLTextSegment(*segment_style, segment_start, segment_end ); + mSegments.push_back(segment); + } + + updateLineStartList(old_length); + + // Set the cursor and scroll position + // Maintain the scroll position unless the scroll was at the end of the doc + // (in which case, move it to the new end of the doc) + if( was_scrolled_to_bottom ) + { + endOfDoc(); + } + else if( selection_start != selection_end ) + { + mSelectionStart = selection_start; + + mSelectionEnd = selection_end; + setCursorPos(cursor_pos); + } + else if( cursor_was_at_end ) + { + setCursorPos(getLength()); + } + else + { + setCursorPos(cursor_pos); + } + + if( !allow_undo ) + { + blockUndo(); + } +} + +void LLTextEditor::removeTextFromEnd(S32 num_chars) +{ + if (num_chars <= 0) return; + + remove(getLength() - num_chars, num_chars, FALSE); + + S32 len = getLength(); + mCursorPos = llclamp(mCursorPos, 0, len); + mSelectionStart = llclamp(mSelectionStart, 0, len); + mSelectionEnd = llclamp(mSelectionEnd, 0, len); + + pruneSegments(); + updateLineStartList(); +} + +/////////////////////////////////////////////////////////////////// +// Returns change in number of characters in mWText + +S32 LLTextEditor::insertStringNoUndo(const S32 pos, const LLWString &wstr) +{ + S32 len = mWText.length(); + S32 s_len = wstr.length(); + S32 new_len = len + s_len; + if( new_len > mMaxTextLength ) + { + new_len = mMaxTextLength; + + // The user's not getting everything he's hoping for + make_ui_sound("UISndBadKeystroke"); + } + + mWText.insert(pos, wstr); + mTextIsUpToDate = FALSE; + truncate(); + + return new_len - len; +} + +S32 LLTextEditor::removeStringNoUndo(S32 pos, S32 length) +{ + mWText.erase(pos, length); + mTextIsUpToDate = FALSE; + return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length +} + +S32 LLTextEditor::overwriteCharNoUndo(S32 pos, llwchar wc) +{ + if (pos > (S32)mWText.length()) + { + return 0; + } + mWText[pos] = wc; + mTextIsUpToDate = FALSE; + return 1; +} + +//---------------------------------------------------------------------------- + +void LLTextEditor::makePristine() +{ + mPristineCmd = mLastCmd; + mBaseDocIsPristine = !mLastCmd; + + // Create a clean partition in the undo stack. We don't want a single command to extend from + // the "pre-pristine" state to the "post-pristine" state. + if( mLastCmd ) + { + mLastCmd->blockExtensions(); + } +} + +BOOL LLTextEditor::isPristine() const +{ + if( mPristineCmd ) + { + return (mPristineCmd == mLastCmd); + } + else + { + // No undo stack, so check if the version before and commands were done was the original version + return !mLastCmd && mBaseDocIsPristine; + } +} + +BOOL LLTextEditor::tryToRevertToPristineState() +{ + if( !isPristine() ) + { + deselect(); + S32 i = 0; + while( !isPristine() && canUndo() ) + { + undo(); + i--; + } + + while( !isPristine() && canRedo() ) + { + redo(); + i++; + } + + if( !isPristine() ) + { + // failed, so go back to where we started + while( i > 0 ) + { + undo(); + i--; + } + } + + updateLineStartList(); + updateScrollFromCursor(); + } + + return isPristine(); // TRUE => success +} + + + +void LLTextEditor::updateTextRect() +{ + mTextRect.setOriginAndSize( + UI_TEXTEDITOR_BORDER + UI_TEXTEDITOR_H_PAD, + UI_TEXTEDITOR_BORDER, + mRect.getWidth() - SCROLLBAR_SIZE - 2 * (UI_TEXTEDITOR_BORDER + UI_TEXTEDITOR_H_PAD), + mRect.getHeight() - 2 * UI_TEXTEDITOR_BORDER - UI_TEXTEDITOR_V_PAD_TOP ); +} + +void LLTextEditor::loadKeywords(const LLString& filename, + const LLDynamicArray& funcs, + const LLDynamicArray& tooltips, + const LLColor3& color) +{ + if(mKeywords.loadFromFile(filename)) + { + S32 count = funcs.count(); + LLString name; + for(S32 i = 0; i < count; i++) + { + name = funcs.get(i); + name = utf8str_trim(name); + mKeywords.addToken(LLKeywordToken::WORD, name.c_str(), color, tooltips.get(i) ); + } + + mKeywords.findSegments( &mSegments, mWText ); + + llassert( mSegments.front()->getStart() == 0 ); + llassert( mSegments.back()->getEnd() == getLength() ); + } +} + +void LLTextEditor::updateSegments() +{ + if (mKeywords.isLoaded()) + { + // HACK: No non-ascii keywords for now + mKeywords.findSegments(&mSegments, mWText); + } + else if (mAllowEmbeddedItems) + { + findEmbeddedItemSegments(); + } + // Make sure we have at least one segment + if (mSegments.size() == 1 && mSegments[0]->getIsDefault()) + { + delete mSegments[0]; + mSegments.clear(); // create default segment + } + if (mSegments.empty()) + { + LLColor4& text_color = ( mReadOnly ? mReadOnlyFgColor : mFgColor ); + LLTextSegment* default_segment = new LLTextSegment( text_color, 0, mWText.length() ); + default_segment->setIsDefault(TRUE); + mSegments.push_back(default_segment); + } +} + +// Only effective if text was removed from the end of the editor +void LLTextEditor::pruneSegments() +{ + S32 len = mWText.length(); + // Find and update the first valid segment + segment_list_t::iterator iter = mSegments.end(); + while(iter != mSegments.begin()) + { + --iter; + LLTextSegment* seg = *iter; + if (seg->getStart() < len) + { + // valid segment + if (seg->getEnd() > len) + { + seg->setEnd(len); + } + break; // done + } + } + // erase invalid segments + ++iter; + std::for_each(iter, mSegments.end(), DeletePointer()); + mSegments.erase(iter, mSegments.end()); +} + +void LLTextEditor::findEmbeddedItemSegments() +{ + mHoverSegment = NULL; + std::for_each(mSegments.begin(), mSegments.end(), DeletePointer()); + mSegments.clear(); + + BOOL found_embedded_items = FALSE; + const LLWString &text = mWText; + S32 idx = 0; + while( text[idx] ) + { + if( text[idx] >= FIRST_EMBEDDED_CHAR && text[idx] <= LAST_EMBEDDED_CHAR ) + { + found_embedded_items = TRUE; + break; + } + ++idx; + } + + if( !found_embedded_items ) + { + return; + } + + S32 text_len = text.length(); + + BOOL in_text = FALSE; + + LLColor4& text_color = ( mReadOnly ? mReadOnlyFgColor : mFgColor ); + + if( idx > 0 ) + { + mSegments.push_back( new LLTextSegment( text_color, 0, text_len ) ); // text + in_text = TRUE; + } + + LLStyle embedded_style; + embedded_style.setIsEmbeddedItem( TRUE ); + + // Start with i just after the first embedded item + while ( text[idx] ) + { + if( text[idx] >= FIRST_EMBEDDED_CHAR && text[idx] <= LAST_EMBEDDED_CHAR ) + { + if( in_text ) + { + mSegments.back()->setEnd( idx ); + } + mSegments.push_back( new LLTextSegment( embedded_style, idx, idx + 1 ) ); // item + in_text = FALSE; + } + else + if( !in_text ) + { + mSegments.push_back( new LLTextSegment( text_color, idx, text_len ) ); // text + in_text = TRUE; + } + ++idx; + } +} + +BOOL LLTextEditor::handleMouseUpOverSegment(S32 x, S32 y, MASK mask) +{ + return FALSE; +} + +llwchar LLTextEditor::pasteEmbeddedItem(llwchar ext_char) +{ + return ext_char; +} + +void LLTextEditor::bindEmbeddedChars(const LLFontGL* font) +{ +} + +void LLTextEditor::unbindEmbeddedChars(const LLFontGL* font) +{ +} + +// Finds the text segment (if any) at the give local screen position +LLTextSegment* LLTextEditor::getSegmentAtLocalPos( S32 x, S32 y ) +{ + // Find the cursor position at the requested local screen position + S32 offset = getCursorPosFromLocalCoord( x, y, FALSE ); + S32 idx = getSegmentIdxAtOffset(offset); + return idx >= 0 ? mSegments[idx] : NULL; +} + +LLTextSegment* LLTextEditor::getSegmentAtOffset(S32 offset) +{ + S32 idx = getSegmentIdxAtOffset(offset); + return idx >= 0 ? mSegments[idx] : NULL; +} + +S32 LLTextEditor::getSegmentIdxAtOffset(S32 offset) +{ + if (mSegments.empty() || offset < 0 || offset >= getLength()) + { + return -1; + } + else + { + S32 segidx, segoff; + getSegmentAndOffset(offset, &segidx, &segoff); + return segidx; + } +} + +//static +void LLTextEditor::onMouseCaptureLost( LLMouseHandler* old_captor ) +{ + LLTextEditor* self = (LLTextEditor*) old_captor; + self->endSelection(); +} + +void LLTextEditor::setOnScrollEndCallback(void (*callback)(void*), void* userdata) +{ + mOnScrollEndCallback = callback; + mOnScrollEndData = userdata; + mScrollbar->setOnScrollEndCallback(callback, userdata); +} + +/////////////////////////////////////////////////////////////////// +// Hack for Notecards + +BOOL LLTextEditor::importBuffer(const LLString& buffer ) +{ + std::istringstream instream(buffer); + + // Version 1 format: + // Linden text version 1\n + // {\n + // + // Text length \n + // (text may contain ext_char_values) + // }\n + + char tbuf[MAX_STRING]; + + S32 version = 0; + instream.getline(tbuf, MAX_STRING); + if( 1 != sscanf(tbuf, "Linden text version %d", &version) ) + { + llwarns << "Invalid Linden text file header " << llendl; + return FALSE; + } + + if( 1 != version ) + { + llwarns << "Invalid Linden text file version: " << version << llendl; + return FALSE; + } + + instream.getline(tbuf, MAX_STRING); + if( 0 != sscanf(tbuf, "{") ) + { + llwarns << "Invalid Linden text file format" << llendl; + return FALSE; + } + + S32 text_len = 0; + instream.getline(tbuf, MAX_STRING); + if( 1 != sscanf(tbuf, "Text length %d", &text_len) ) + { + llwarns << "Invalid Linden text length field" << llendl; + return FALSE; + } + + if( text_len > mMaxTextLength ) + { + llwarns << "Invalid Linden text length: " << text_len << llendl; + return FALSE; + } + + BOOL success = TRUE; + + char* text = new char[ text_len + 1]; + instream.get(text, text_len + 1, '\0'); + text[text_len] = '\0'; + if( text_len != (S32)strlen(text) ) + { + llwarns << llformat("Invalid text length: %d != %d ",strlen(text),text_len) << llendl; + success = FALSE; + } + + instream.getline(tbuf, MAX_STRING); + if( success && (0 != sscanf(tbuf, "}")) ) + { + llwarns << "Invalid Linden text file format: missing terminal }" << llendl; + success = FALSE; + } + + if( success ) + { + // Actually set the text + setText( text ); + } + + delete[] text; + + setCursorPos(0); + deselect(); + + updateLineStartList(); + updateScrollFromCursor(); + + return success; +} + +BOOL LLTextEditor::exportBuffer(LLString &buffer ) +{ + std::ostringstream outstream(buffer); + + outstream << "Linden text version 1\n"; + outstream << "{\n"; + + outstream << llformat("Text length %d\n", mWText.length() ); + outstream << getText(); + outstream << "}\n"; + + return TRUE; +} + +////////////////////////////////////////////////////////////////////////// +// LLTextSegment + +LLTextSegment::LLTextSegment(S32 start) : mStart(start) +{ +} +LLTextSegment::LLTextSegment( const LLStyle& style, S32 start, S32 end ) : + mStyle( style ), + mStart( start), + mEnd( end ), + mToken(NULL), + mIsDefault(FALSE) +{ +} +LLTextSegment::LLTextSegment( + const LLColor4& color, S32 start, S32 end, BOOL is_visible) : + mStyle( is_visible, color,"" ), + mStart( start), + mEnd( end ), + mToken(NULL), + mIsDefault(FALSE) +{ +} +LLTextSegment::LLTextSegment( const LLColor4& color, S32 start, S32 end ) : + mStyle( TRUE, color,"" ), + mStart( start), + mEnd( end ), + mToken(NULL), + mIsDefault(FALSE) +{ +} +LLTextSegment::LLTextSegment( const LLColor3& color, S32 start, S32 end ) : + mStyle( TRUE, color,"" ), + mStart( start), + mEnd( end ), + mToken(NULL), + mIsDefault(FALSE) +{ +} + +BOOL LLTextSegment::getToolTip(LLString& msg) +{ + if (mToken && !mToken->getToolTip().empty()) + { + const LLWString& wmsg = mToken->getToolTip(); + msg = wstring_to_utf8str(wmsg); + return TRUE; + } + return FALSE; +} + + + +void LLTextSegment::dump() +{ + llinfos << "Segment [" << +// mColor.mV[VX] << ", " << +// mColor.mV[VY] << ", " << +// mColor.mV[VZ] << "]\t[" << + mStart << ", " << + getEnd() << "]" << + llendl; + +} + +// virtual +LLXMLNodePtr LLTextEditor::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLUICtrl::getXML(); + + // Attributes + + node->createChild("max_length", TRUE)->setIntValue(getMaxLength()); + + node->createChild("embedded_items", TRUE)->setBoolValue(mAllowEmbeddedItems); + + node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mGLFont)); + + node->createChild("word_wrap", TRUE)->setBoolValue(mWordWrap); + + addColorXML(node, mCursorColor, "cursor_color", "TextCursorColor"); + addColorXML(node, mFgColor, "text_color", "TextFgColor"); + addColorXML(node, mReadOnlyFgColor, "text_readonly_color", "TextFgReadOnlyColor"); + addColorXML(node, mReadOnlyBgColor, "bg_readonly_color", "TextBgReadOnlyColor"); + addColorXML(node, mWriteableBgColor, "bg_writeable_color", "TextBgWriteableColor"); + addColorXML(node, mFocusBgColor, "bg_focus_color", "TextBgFocusColor"); + + // Contents + node->setStringValue(getText()); + + return node; +} + +// static +LLView* LLTextEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("text_editor"); + node->getAttributeString("name", name); + + LLRect rect; + createRect(node, rect, parent, LLRect()); + + U32 max_text_length = 255; + node->getAttributeU32("max_length", max_text_length); + + BOOL allow_embedded_items; + node->getAttributeBOOL("embedded_items", allow_embedded_items); + + LLFontGL* font = LLView::selectFont(node); + + LLString text = node->getTextContents().substr(0, max_text_length - 1); + + LLTextEditor* text_editor = new LLTextEditor(name, + rect, + max_text_length, + text, + font, + allow_embedded_items); + + text_editor->setTextEditorParameters(node); + + BOOL hide_scrollbar = FALSE; + node->getAttributeBOOL("hide_scrollbar",hide_scrollbar); + text_editor->setHideScrollbarForShortDocs(hide_scrollbar); + + text_editor->initFromXML(node, parent); + + return text_editor; +} + +void LLTextEditor::setTextEditorParameters(LLXMLNodePtr node) +{ + BOOL word_wrap = FALSE; + node->getAttributeBOOL("word_wrap", word_wrap); + setWordWrap(word_wrap); + + LLColor4 color; + if (LLUICtrlFactory::getAttributeColor(node,"cursor_color", color)) + { + setCursorColor(color); + } + if(LLUICtrlFactory::getAttributeColor(node,"text_color", color)) + { + setFgColor(color); + } + if(LLUICtrlFactory::getAttributeColor(node,"text_readonly_color", color)) + { + setReadOnlyFgColor(color); + } + if(LLUICtrlFactory::getAttributeColor(node,"bg_readonly_color", color)) + { + setReadOnlyBgColor(color); + } + if(LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color)) + { + setWriteableBgColor(color); + } +} + +/////////////////////////////////////////////////////////////////// +S32 LLTextEditor::findHTMLToken(const LLString &line, S32 pos, BOOL reverse) +{ + LLString openers=" \t('\"[{<>"; + LLString closers=" \t)'\"]}><;"; + + S32 m2; + S32 retval; + + if (reverse) + { + + for (retval=pos; retval>0; retval--) + { + m2 = openers.find(line.substr(retval,1)); + if (m2 >= 0) + { + retval++; + break; + } + } + } + else + { + + for (retval=pos; retval<(S32)line.length(); retval++) + { + m2 = closers.find(line.substr(retval,1)); + if (m2 >= 0) + { + break; + } + } + } + + return retval; +} + +BOOL LLTextEditor::findHTML(const LLString &line, S32 *begin, S32 *end) +{ + + S32 m1,m2,m3; + BOOL matched = FALSE; + + m1=line.find("://",*end); + + if (m1 >= 0) //Easy match. + { + *begin = findHTMLToken(line, m1, TRUE); + *end = findHTMLToken(line, m1, FALSE); + + //Load_url only handles http and https so don't hilite ftp, smb, etc. + m2 = line.substr(*begin,(m1 - *begin)).find("http"); + m3 = line.substr(*begin,(m1 - *begin)).find("secondlife"); + + LLString badneighbors=".,<>/?';\"][}{=-+_)(*&^%$#@!~`\t\r\n\\"; + + if (m2 >= 0 || m3>=0) + { + S32 bn = badneighbors.find(line.substr(m1+3,1)); + + if (bn < 0) + { + matched = TRUE; + } + } + } +/* matches things like secondlife.com (no http://) needs a whitelist to really be effective. + else //Harder match. + { + m1 = line.find(".",*end); + + if (m1 >= 0) + { + *end = findHTMLToken(line, m1, FALSE); + *begin = findHTMLToken(line, m1, TRUE); + + m1 = line.rfind(".",*end); + + if ( ( *end - m1 ) > 2 && m1 > *begin) + { + LLString badneighbors=".,<>/?';\"][}{=-+_)(*&^%$#@!~`"; + m2 = badneighbors.find(line.substr(m1+1,1)); + m3 = badneighbors.find(line.substr(m1-1,1)); + if (m3<0 && m2<0) + { + matched = TRUE; + } + } + } + } + */ + + if (matched) + { + S32 strpos, strpos2; + + LLString url = line.substr(*begin,*end - *begin); + LLString slurlID = "slurl.com/secondlife/"; + strpos = url.find(slurlID); + + if (strpos < 0) + { + slurlID="secondlife://"; + strpos = url.find(slurlID); + } + + if (strpos >= 0) + { + strpos+=slurlID.length(); + + while ( ( strpos2=url.find("/",strpos) ) == -1 ) + { + if ((*end+2) >= (S32)line.length() || line.substr(*end,1) != " " ) + { + matched=FALSE; + break; + } + + strpos = (*end + 1) - *begin; + + *end = findHTMLToken(line,(*begin + strpos),FALSE); + url = line.substr(*begin,*end - *begin); + } + } + + } + + if (!matched) + { + *begin=*end=0; + } + return matched; +} diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h new file mode 100644 index 0000000000..bebf2b31f2 --- /dev/null +++ b/indra/llui/lltexteditor.h @@ -0,0 +1,483 @@ +/** + * @file lltexteditor.h + * @brief LLTextEditor base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Text editor widget to let users enter a a multi-line ASCII document// + +#ifndef LL_LLTEXTEDITOR_H +#define LL_LLTEXTEDITOR_H + +#include "llrect.h" +#include "llkeywords.h" +#include "lluictrl.h" +#include "llframetimer.h" +#include "lldarray.h" +#include "llstyle.h" +#include "lleditmenuhandler.h" +#include "lldarray.h" + +class LLFontGL; +class LLScrollbar; +class LLViewBorder; +class LLKeywordToken; +class LLTextCmd; + +// +// Constants +// + +const llwchar FIRST_EMBEDDED_CHAR = 0x100000; +const llwchar LAST_EMBEDDED_CHAR = 0x10ffff; +const S32 MAX_EMBEDDED_ITEMS = LAST_EMBEDDED_CHAR - FIRST_EMBEDDED_CHAR + 1; + +// +// Classes +// +class LLTextSegment; +class LLTextCmd; + +class LLTextEditor : public LLUICtrl, LLEditMenuHandler +{ + friend class LLTextCmd; +public: + LLTextEditor(const LLString& name, + const LLRect& rect, + S32 max_length, + const LLString &default_text, + const LLFontGL* glfont = NULL, + BOOL allow_embedded_items = FALSE); + + virtual ~LLTextEditor(); + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_TEXT_EDITOR; } + virtual LLString getWidgetTag() const; + + virtual LLXMLNodePtr getXML(bool save_children = true) const; + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + void setTextEditorParameters(LLXMLNodePtr node); + void setParseHTML(BOOL parsing) {mParseHTML=parsing;} + + // mousehandler overrides + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask ); + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent ); + virtual BOOL handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent); + + virtual BOOL handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect); + virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, void *cargo_data, + EAcceptance *accept, LLString& tooltip_msg); + + // view overrides + virtual void reshape(S32 width, S32 height, BOOL called_from_parent); + virtual void draw(); + virtual void onFocusLost(); + virtual void setEnabled(BOOL enabled); + + // uictrl overrides + virtual void onTabInto(); + virtual void clear(); + virtual void setFocus( BOOL b ); + virtual BOOL acceptsTextInput() const; + + // LLEditMenuHandler interface + virtual void undo(); + virtual BOOL canUndo(); + + virtual void redo(); + virtual BOOL canRedo(); + + virtual void cut(); + virtual BOOL canCut(); + + virtual void copy(); + virtual BOOL canCopy(); + + virtual void paste(); + virtual BOOL canPaste(); + + virtual void doDelete(); + virtual BOOL canDoDelete(); + + virtual void selectAll(); + virtual BOOL canSelectAll(); + + virtual void deselect(); + virtual BOOL canDeselect(); + + void selectNext(const LLString& search_text_in, BOOL case_insensitive, BOOL wrap = TRUE); + BOOL replaceText(const LLString& search_text, const LLString& replace_text, BOOL case_insensitive, BOOL wrap = TRUE); + void replaceTextAll(const LLString& search_text, const LLString& replace_text, BOOL case_insensitive); + + // Undo/redo stack + void blockUndo(); + + // Text editing + virtual void makePristine(); + BOOL isPristine() const; + + // inserts text at cursor + void insertText(const LLString &text); + // appends text at end + void appendText(const LLString &wtext, bool allow_undo, bool prepend_newline, + const LLStyle* segment_style = NULL); + + void appendColoredText(const LLString &wtext, bool allow_undo, + bool prepend_newline, + const LLColor4 &color, + const LLString& font_name = LLString::null); + // if styled text starts a line, you need to prepend a newline. + void appendStyledText(const LLString &new_text, bool allow_undo, + bool prepend_newline, + const LLStyle &style); + + // Removes text from the end of document + // Does not change highlight or cursor position. + void removeTextFromEnd(S32 num_chars); + + BOOL tryToRevertToPristineState(); + + void setCursor(S32 row, S32 column); + void setCursorPos(S32 offset); + void setCursorAndScrollToEnd(); + + void getCurrentLineAndColumn( S32* line, S32* col, BOOL include_wordwrap ); + + void loadKeywords(const LLString& filename, + const LLDynamicArray& funcs, + const LLDynamicArray& tooltips, + const LLColor3& func_color); + + void setCursorColor(const LLColor4& c) { mCursorColor = c; } + void setFgColor( const LLColor4& c ) { mFgColor = c; } + void setReadOnlyFgColor( const LLColor4& c ) { mReadOnlyFgColor = c; } + void setWriteableBgColor( const LLColor4& c ) { mWriteableBgColor = c; } + void setReadOnlyBgColor( const LLColor4& c ) { mReadOnlyBgColor = c; } + + void setTrackColor( const LLColor4& color ); + void setThumbColor( const LLColor4& color ); + void setHighlightColor( const LLColor4& color ); + void setShadowColor( const LLColor4& color ); + + // Hacky methods to make it into a word-wrapping, potentially scrolling, + // read-only text box. + void setBorderVisible(BOOL b); + void setTakesNonScrollClicks(BOOL b); + void setHideScrollbarForShortDocs(BOOL b); + + void setWordWrap( BOOL b ); + void setTabToNextField(BOOL b) { mTabToNextField = b; } + void setCommitOnFocusLost(BOOL b) { mCommitOnFocusLost = b; } + + // If takes focus, will take keyboard focus on click. + void setTakesFocus(BOOL b) { mTakesFocus = b; } + + // Hack to handle Notecards + virtual BOOL importBuffer(const LLString& buffer ); + virtual BOOL exportBuffer(LLString& buffer ); + + void setSourceID(const LLUUID& id) { mSourceID = id; } + void setAcceptCallingCardNames(BOOL enable) { mAcceptCallingCardNames = enable; } + + void setHandleEditKeysDirectly( BOOL b ) { mHandleEditKeysDirectly = b; } + + // Callbacks + static void onMouseCaptureLost( LLMouseHandler* old_captor ); + static void setLinkColor(LLColor4 color) { mLinkColor = color; } + static void setURLCallbacks( void (*callback1) (const char* url), + BOOL (*callback2) (LLString url) ) + { mURLcallback = callback1; mSecondlifeURLcallback = callback2;} + + void setOnScrollEndCallback(void (*callback)(void*), void* userdata); + + // new methods + void setValue(const LLSD& value); + LLSD getValue() const; + + const LLString& getText() const; + + // Non-undoable + void setText(const LLString &utf8str); + void setWText(const LLWString &wtext); + + S32 getMaxLength() const { return mMaxTextLength; } + + // Change cursor + void startOfLine(); + void endOfLine(); + void endOfDoc(); + + // Getters + const LLWString& getWText() const; + llwchar getWChar(S32 pos); + LLWString getWSubString(S32 pos, S32 len); + +protected: + S32 getLength() const; + void getSegmentAndOffset( S32 startpos, S32* segidxp, S32* offsetp ); + + void drawBackground(); + void drawSelectionBackground(); + void drawCursor(); + void drawText(); + void drawClippedSegment(const LLWString &wtext, S32 seg_start, S32 seg_end, F32 x, F32 y, S32 selection_left, S32 selection_right, const LLStyle& color, F32* right_x); + + void updateLineStartList(S32 startpos = 0); + void updateScrollFromCursor(); + void updateTextRect(); + void updateSegments(); + void pruneSegments(); + + void assignEmbedded(const LLString &s); + void truncate(); + + static BOOL isPartOfWord(llwchar c); + + void removeCharOrTab(); + void setCursorAtLocalPos(S32 x, S32 y, BOOL round); + S32 getCursorPosFromLocalCoord( S32 local_x, S32 local_y, BOOL round ); + + void indentSelectedLines( S32 spaces ); + S32 indentLine( S32 pos, S32 spaces ); + void unindentLineBeforeCloseBrace(); + + S32 getSegmentIdxAtOffset(S32 offset); + LLTextSegment* getSegmentAtLocalPos(S32 x, S32 y); + LLTextSegment* getSegmentAtOffset(S32 offset); + + void reportBadKeystroke(); + + BOOL handleNavigationKey(const KEY key, const MASK mask); + BOOL handleSpecialKey(const KEY key, const MASK mask, BOOL* return_key_hit); + BOOL handleSelectionKey(const KEY key, const MASK mask); + BOOL handleControlKey(const KEY key, const MASK mask); + BOOL handleEditKey(const KEY key, const MASK mask); + + BOOL hasSelection() { return (mSelectionStart !=mSelectionEnd); } + BOOL selectionContainsLineBreaks(); + void startSelection(); + void endSelection(); + void deleteSelection(BOOL transient_operation); + + S32 prevWordPos(S32 cursorPos) const; + S32 nextWordPos(S32 cursorPos) const; + + S32 getLineCount(); + S32 getLineStart( S32 line ); + void getLineAndOffset(S32 pos, S32* linep, S32* offsetp); + S32 getPos(S32 line, S32 offset); + + void changePage(S32 delta); + void changeLine(S32 delta); + + void autoIndent(); + + S32 execute(LLTextCmd* cmd); + + void findEmbeddedItemSegments(); + + virtual BOOL handleMouseUpOverSegment(S32 x, S32 y, MASK mask); + virtual llwchar pasteEmbeddedItem(llwchar ext_char); + virtual void bindEmbeddedChars(const LLFontGL* font); + virtual void unbindEmbeddedChars(const LLFontGL* font); + + S32 findHTMLToken(const LLString &line, S32 pos, BOOL reverse); + BOOL findHTML(const LLString &line, S32 *begin, S32 *end); + +protected: + // Undoable operations + void addChar(llwchar c); // at mCursorPos + S32 addChar(S32 pos, llwchar wc); + S32 overwriteChar(S32 pos, llwchar wc); + void removeChar(); + S32 removeChar(S32 pos); + S32 insert(const S32 pos, const LLWString &wstr, const BOOL group_with_next_op); + S32 remove(const S32 pos, const S32 length, const BOOL group_with_next_op); + S32 append(const LLWString &wstr, const BOOL group_with_next_op); + + // direct operations + S32 insertStringNoUndo(S32 pos, const LLWString &utf8str); // returns num of chars actually inserted + S32 removeStringNoUndo(S32 pos, S32 length); + S32 overwriteCharNoUndo(S32 pos, llwchar wc); + +public: + LLKeywords mKeywords; + static LLColor4 mLinkColor; + static void (*mURLcallback) (const char* url); + static BOOL (*mSecondlifeURLcallback) (LLString url); +protected: + LLWString mWText; + mutable LLString mUTF8Text; + mutable BOOL mTextIsUpToDate; + + S32 mMaxTextLength; // Maximum length mText is allowed to be + + const LLFontGL* mGLFont; + + LLScrollbar* mScrollbar; + LLViewBorder* mBorder; + + BOOL mBaseDocIsPristine; + LLTextCmd* mPristineCmd; + + LLTextCmd* mLastCmd; + + typedef std::deque undo_stack_t; + undo_stack_t mUndoStack; + + S32 mCursorPos; // I-beam is just after the mCursorPos-th character. + LLRect mTextRect; // The rect in which text is drawn. Excludes borders. + // List of offsets and segment index of the start of each line. Always has at least one node (0). + struct line_info + { + line_info(S32 segment, S32 offset) : mSegment(segment), mOffset(offset) {} + S32 mSegment; + S32 mOffset; + }; + struct line_info_compare + { + bool operator()(const line_info& a, const line_info& b) const + { + if (a.mSegment < b.mSegment) + return true; + else if (a.mSegment > b.mSegment) + return false; + else + return a.mOffset < b.mOffset; + } + }; + typedef std::vector line_list_t; + line_list_t mLineStartList; + + // Are we in the middle of a drag-select? To figure out if there is a current + // selection, call hasSelection(). + BOOL mIsSelecting; + + S32 mSelectionStart; + S32 mSelectionEnd; + + void (*mOnScrollEndCallback)(void*); + void *mOnScrollEndData; + + typedef std::vector segment_list_t; + segment_list_t mSegments; + LLTextSegment* mHoverSegment; + LLFrameTimer mKeystrokeTimer; + + LLColor4 mCursorColor; + + LLColor4 mFgColor; + LLColor4 mReadOnlyFgColor; + LLColor4 mWriteableBgColor; + LLColor4 mReadOnlyBgColor; + LLColor4 mFocusBgColor; + + BOOL mReadOnly; + BOOL mWordWrap; + + BOOL mTabToNextField; // if true, tab moves focus to next field, else inserts spaces + BOOL mCommitOnFocusLost; + BOOL mTakesFocus; + BOOL mHideScrollbarForShortDocs; + BOOL mTakesNonScrollClicks; + + BOOL mAllowEmbeddedItems; + + BOOL mAcceptCallingCardNames; + + LLUUID mSourceID; + + BOOL mHandleEditKeysDirectly; // If true, the standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system + + // Use these to determine if a click on an embedded item is a drag + // or not. + S32 mMouseDownX; + S32 mMouseDownY; + + S32 mLastSelectionX; + S32 mLastSelectionY; + + BOOL mParseHTML; + LLString mHTML; +}; + +class LLTextSegment +{ +public: + // for creating a compare value + LLTextSegment(S32 start); + LLTextSegment( const LLStyle& style, S32 start, S32 end ); + LLTextSegment( const LLColor4& color, S32 start, S32 end, BOOL is_visible); + LLTextSegment( const LLColor4& color, S32 start, S32 end ); + LLTextSegment( const LLColor3& color, S32 start, S32 end ); + + S32 getStart() { return mStart; } + S32 getEnd() { return mEnd; } + void setEnd( S32 end ) { mEnd = end; } + const LLColor4& getColor() { return mStyle.getColor(); } + void setColor(const LLColor4 &color) { mStyle.setColor(color); } + const LLStyle& getStyle() { return mStyle; } + void setStyle(const LLStyle &style) { mStyle = style; } + void setIsDefault(BOOL b) { mIsDefault = b; } + BOOL getIsDefault() { return mIsDefault; } + + void setToken( LLKeywordToken* token ) { mToken = token; } + LLKeywordToken* getToken() { return mToken; } + BOOL getToolTip( LLString& msg ); + + void dump(); + + struct compare + { + bool operator()(const LLTextSegment* a, const LLTextSegment* b) const + { + return a->mStart < b->mStart; + } + }; + +private: + LLStyle mStyle; + S32 mStart; + S32 mEnd; + LLKeywordToken* mToken; + BOOL mIsDefault; +}; + +class LLTextCmd +{ +public: + LLTextCmd( S32 pos, BOOL group_with_next ) + : mPos(pos), + mGroupWithNext(group_with_next) + { + } + virtual ~LLTextCmd() {} + virtual BOOL execute(LLTextEditor* editor, S32* delta) = 0; + virtual S32 undo(LLTextEditor* editor) = 0; + virtual S32 redo(LLTextEditor* editor) = 0; + virtual BOOL canExtend(S32 pos); + virtual void blockExtensions(); + virtual BOOL extendAndExecute( LLTextEditor* editor, S32 pos, llwchar c, S32* delta ); + virtual BOOL hasExtCharValue( llwchar value ); + + // Define these here so they can access LLTextEditor through the friend relationship + S32 insert(LLTextEditor* editor, S32 pos, const LLWString &utf8str); + S32 remove(LLTextEditor* editor, S32 pos, S32 length); + S32 overwrite(LLTextEditor* editor, S32 pos, llwchar wc); + + BOOL groupWithNext() { return mGroupWithNext; } + +protected: + S32 mPos; + BOOL mGroupWithNext; +}; + + +#endif // LL_TEXTEDITOR_ diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp new file mode 100644 index 0000000000..d951cb70f6 --- /dev/null +++ b/indra/llui/llui.cpp @@ -0,0 +1,1764 @@ +/** + * @file llui.cpp + * @brief UI implementation + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Utilities functions the user interface needs + +//#include "llviewerprecompiledheaders.h" +#include "linden_common.h" + +#include +#include + +// Linden library includes +#include "audioengine.h" +#include "v2math.h" +#include "v4color.h" +#include "llgl.h" +#include "llrect.h" +#include "llimagegl.h" +//#include "llviewerimage.h" +#include "lldir.h" +#include "llfontgl.h" + +// Project includes +//#include "audioengine.h" +#include "llcontrol.h" +//#include "llstartup.h" +#include "llui.h" +#include "llview.h" +#include "llwindow.h" + +#include "llglheaders.h" + +// +// Globals +// +const LLColor4 UI_VERTEX_COLOR(1.f, 1.f, 1.f, 1.f); + +// Used to hide the flashing text cursor when window doesn't have focus. +BOOL gShowTextEditCursor = TRUE; + +// Language for UI construction +LLString gLanguage = "english-usa"; +std::map gTranslation; +std::list gUntranslated; + +LLControlGroup* LLUI::sConfigGroup = NULL; +LLControlGroup* LLUI::sColorsGroup = NULL; +LLControlGroup* LLUI::sAssetsGroup = NULL; +LLImageProviderInterface* LLUI::sImageProvider = NULL; +LLUIAudioCallback LLUI::sAudioCallback = NULL; +LLVector2 LLUI::sGLScaleFactor(1.f, 1.f); +LLWindow* LLUI::sWindow = NULL; +BOOL LLUI::sShowXUINames = FALSE; +// +// Functions +// +void make_ui_sound(const LLString& name) +{ + if (!LLUI::sConfigGroup->controlExists(name)) + { + llwarns << "tried to make ui sound for unknown sound name: " << name << llendl; + } + else + { + LLUUID uuid(LLUI::sConfigGroup->getString(name)); + if (uuid.isNull()) + { + if ("00000000-0000-0000-0000-000000000000" == LLUI::sConfigGroup->getString(name)) + { + if (LLUI::sConfigGroup->getBOOL("UISndDebugSpamToggle")) + { + llinfos << "ui sound name: " << name << " triggered but silent (null uuid)" << llendl; + } + } + else + { + llwarns << "ui sound named: " << name << " does not translate to a valid uuid" << llendl; + } + + } + else if (LLUI::sAudioCallback != NULL) + { + if (LLUI::sConfigGroup->getBOOL("UISndDebugSpamToggle")) + { + llinfos << "ui sound name: " << name << llendl; + } + LLUI::sAudioCallback(uuid, LLUI::sConfigGroup->getF32("AudioLevelUI")); + } + } +} + +BOOL ui_point_in_rect(S32 x, S32 y, S32 left, S32 top, S32 right, S32 bottom) +{ + if (x < left || right < x) return FALSE; + if (y < bottom || top < y) return FALSE; + return TRUE; +} + + +// Puts GL into 2D drawing mode by turning off lighting, setting to an +// orthographic projection, etc. +void gl_state_for_2d(S32 width, S32 height) +{ + stop_glerror(); + F32 window_width = (F32) width;//gViewerWindow->getWindowWidth(); + F32 window_height = (F32) height;//gViewerWindow->getWindowHeight(); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0f, window_width, 0.0f, window_height, -1.0f, 1.0f); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + stop_glerror(); +} + + +void gl_draw_x(const LLRect& rect, const LLColor4& color) +{ + LLGLSNoTexture no_texture; + + glColor4fv( color.mV ); + + glBegin( GL_LINES ); + glVertex2i( rect.mLeft, rect.mTop ); + glVertex2i( rect.mRight, rect.mBottom ); + glVertex2i( rect.mLeft, rect.mBottom ); + glVertex2i( rect.mRight, rect.mTop ); + glEnd(); +} + + +void gl_rect_2d_offset_local( S32 left, S32 top, S32 right, S32 bottom, const LLColor4 &color, S32 pixel_offset, BOOL filled) +{ + glColor4fv(color.mV); + gl_rect_2d_offset_local(left, top, right, bottom, pixel_offset, filled); +} + +void gl_rect_2d_offset_local( S32 left, S32 top, S32 right, S32 bottom, S32 pixel_offset, BOOL filled) +{ + glPushMatrix(); + left += LLFontGL::sCurOrigin.mX; + right += LLFontGL::sCurOrigin.mX; + bottom += LLFontGL::sCurOrigin.mY; + top += LLFontGL::sCurOrigin.mY; + + glLoadIdentity(); + gl_rect_2d(llfloor((F32)left * LLUI::sGLScaleFactor.mV[VX]) - pixel_offset, + llfloor((F32)top * LLUI::sGLScaleFactor.mV[VY]) + pixel_offset, + llfloor((F32)right * LLUI::sGLScaleFactor.mV[VX]) + pixel_offset, + llfloor((F32)bottom * LLUI::sGLScaleFactor.mV[VY]) - pixel_offset, + filled); + glPopMatrix(); +} + + +void gl_rect_2d(S32 left, S32 top, S32 right, S32 bottom, BOOL filled ) +{ + stop_glerror(); + LLGLSNoTexture no_texture; + + // Counterclockwise quad will face the viewer + if( filled ) + { + glBegin( GL_QUADS ); + glVertex2i(left, top); + glVertex2i(left, bottom); + glVertex2i(right, bottom); + glVertex2i(right, top); + glEnd(); + } + else + { + if( gGLManager.mATIOffsetVerticalLines ) + { + // Work around bug in ATI driver: vertical lines are offset by (-1,-1) + glBegin( GL_LINES ); + + // Verticals + glVertex2i(left + 1, top); + glVertex2i(left + 1, bottom); + + glVertex2i(right, bottom); + glVertex2i(right, top); + + // Horizontals + top--; + right--; + glVertex2i(left, bottom); + glVertex2i(right, bottom); + + glVertex2i(left, top); + glVertex2i(right, top); + glEnd(); + } + else + { + top--; + right--; + glBegin( GL_LINE_STRIP ); + glVertex2i(left, top); + glVertex2i(left, bottom); + glVertex2i(right, bottom); + glVertex2i(right, top); + glVertex2i(left, top); + glEnd(); + } + } + stop_glerror(); +} + +void gl_rect_2d(S32 left, S32 top, S32 right, S32 bottom, const LLColor4 &color, BOOL filled ) +{ + glColor4fv( color.mV ); + gl_rect_2d( left, top, right, bottom, filled ); +} + + +void gl_rect_2d( const LLRect& rect, const LLColor4& color, BOOL filled ) +{ + glColor4fv( color.mV ); + gl_rect_2d( rect.mLeft, rect.mTop, rect.mRight, rect.mBottom, filled ); +} + +// Given a rectangle on the screen, draws a drop shadow _outside_ +// the right and bottom edges of it. Along the right it has width "lines" +// and along the bottom it has height "lines". +void gl_drop_shadow(S32 left, S32 top, S32 right, S32 bottom, const LLColor4 &start_color, S32 lines) +{ + stop_glerror(); + LLGLSNoTexture no_texture; + + // HACK: Overlap with the rectangle by a single pixel. + right--; + bottom++; + lines++; + + LLColor4 end_color = start_color; + end_color.mV[VALPHA] = 0.f; + + glBegin(GL_QUADS); + + // Right edge, CCW faces screen + glColor4fv(start_color.mV); + glVertex2i(right, top-lines); + glVertex2i(right, bottom); + glColor4fv(end_color.mV); + glVertex2i(right+lines, bottom); + glVertex2i(right+lines, top-lines); + + // Bottom edge, CCW faces screen + glColor4fv(start_color.mV); + glVertex2i(right, bottom); + glVertex2i(left+lines, bottom); + glColor4fv(end_color.mV); + glVertex2i(left+lines, bottom-lines); + glVertex2i(right, bottom-lines); + + // bottom left Corner + glColor4fv(start_color.mV); + glVertex2i(left+lines, bottom); + glColor4fv(end_color.mV); + glVertex2i(left, bottom); + // make the bottom left corner not sharp + glVertex2i(left+1, bottom-lines+1); + glVertex2i(left+lines, bottom-lines); + + // bottom right corner + glColor4fv(start_color.mV); + glVertex2i(right, bottom); + glColor4fv(end_color.mV); + glVertex2i(right, bottom-lines); + // make the rightmost corner not sharp + glVertex2i(right+lines-1, bottom-lines+1); + glVertex2i(right+lines, bottom); + + // top right corner + glColor4fv(start_color.mV); + glVertex2i( right, top-lines ); + glColor4fv(end_color.mV); + glVertex2i( right+lines, top-lines ); + // make the corner not sharp + glVertex2i( right+lines-1, top-1 ); + glVertex2i( right, top ); + + glEnd(); + stop_glerror(); +} + +void gl_line_2d(S32 x1, S32 y1, S32 x2, S32 y2 ) +{ + // Work around bug in ATI driver: vertical lines are offset by (-1,-1) + if( gGLManager.mATIOffsetVerticalLines && (x1 == x2) ) + { + x1++; + x2++; + y1++; + y2++; + } + + LLGLSNoTexture no_texture; + + glBegin(GL_LINES); + glVertex2i(x1, y1); + glVertex2i(x2, y2); + glEnd(); +} + +void gl_line_2d(S32 x1, S32 y1, S32 x2, S32 y2, const LLColor4 &color ) +{ + // Work around bug in ATI driver: vertical lines are offset by (-1,-1) + if( gGLManager.mATIOffsetVerticalLines && (x1 == x2) ) + { + x1++; + x2++; + y1++; + y2++; + } + + LLGLSNoTexture no_texture; + + glColor4fv( color.mV ); + + glBegin(GL_LINES); + glVertex2i(x1, y1); + glVertex2i(x2, y2); + glEnd(); +} + +void gl_triangle_2d(S32 x1, S32 y1, S32 x2, S32 y2, S32 x3, S32 y3, const LLColor4& color, BOOL filled) +{ + LLGLSNoTexture no_texture; + + glColor4fv(color.mV); + + if (filled) + { + glBegin(GL_TRIANGLES); + } + else + { + glBegin(GL_LINE_LOOP); + } + glVertex2i(x1, y1); + glVertex2i(x2, y2); + glVertex2i(x3, y3); + glEnd(); +} + +void gl_corners_2d(S32 left, S32 top, S32 right, S32 bottom, S32 length, F32 max_frac) +{ + LLGLSNoTexture no_texture; + + length = llmin((S32)(max_frac*(right - left)), length); + length = llmin((S32)(max_frac*(top - bottom)), length); + glBegin(GL_LINES); + glVertex2i(left, top); + glVertex2i(left + length, top); + + glVertex2i(left, top); + glVertex2i(left, top - length); + + glVertex2i(left, bottom); + glVertex2i(left + length, bottom); + + glVertex2i(left, bottom); + glVertex2i(left, bottom + length); + + glVertex2i(right, top); + glVertex2i(right - length, top); + + glVertex2i(right, top); + glVertex2i(right, top - length); + + glVertex2i(right, bottom); + glVertex2i(right - length, bottom); + + glVertex2i(right, bottom); + glVertex2i(right, bottom + length); + glEnd(); +} + + +void gl_draw_image( S32 x, S32 y, LLImageGL* image, const LLColor4& color ) +{ + gl_draw_scaled_rotated_image( x, y, image->getWidth(), image->getHeight(), 0.f, image, color ); +} + +void gl_draw_scaled_image(S32 x, S32 y, S32 width, S32 height, LLImageGL* image, const LLColor4& color) +{ + gl_draw_scaled_rotated_image( x, y, width, height, 0.f, image, color ); +} + +void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 border_width, S32 border_height, S32 width, S32 height, LLImageGL* image, const LLColor4& color, BOOL solid_color) +{ + stop_glerror(); + F32 border_scale = 1.f; + + if (border_height * 2 > height) + { + border_scale = (F32)height / ((F32)border_height * 2.f); + } + if (border_width * 2 > width) + { + border_scale = llmin(border_scale, (F32)width / ((F32)border_width * 2.f)); + } + + // scale screen size of borders down + S32 scaled_border_width = llfloor(border_scale * (F32)border_width); + S32 scaled_border_height = llfloor(border_scale * (F32)border_height); + + LLGLSUIDefault gls_ui; + + if (solid_color) + { + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE); + + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); + + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); + + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_PRIMARY_COLOR_ARB); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA); + } + + glPushMatrix(); + { + glTranslatef((F32)x, (F32)y, 0.f); + + image->bind(); + + glColor4fv(color.mV); + + F32 border_width_fraction = (F32)border_width / (F32)image->getWidth(); + F32 border_height_fraction = (F32)border_height / (F32)image->getHeight(); + + glBegin(GL_QUADS); + { + // draw bottom left + glTexCoord2f(0.f, 0.f); + glVertex2i(0, 0); + + glTexCoord2f(border_width_fraction, 0.f); + glVertex2i(scaled_border_width, 0); + + glTexCoord2f(border_width_fraction, border_height_fraction); + glVertex2i(scaled_border_width, scaled_border_height); + + glTexCoord2f(0.f, border_height_fraction); + glVertex2i(0, scaled_border_height); + + // draw bottom middle + glTexCoord2f(border_width_fraction, 0.f); + glVertex2i(scaled_border_width, 0); + + glTexCoord2f(1.f - border_width_fraction, 0.f); + glVertex2i(width - scaled_border_width, 0); + + glTexCoord2f(1.f - border_width_fraction, border_height_fraction); + glVertex2i(width - scaled_border_width, scaled_border_height); + + glTexCoord2f(border_width_fraction, border_height_fraction); + glVertex2i(scaled_border_width, scaled_border_height); + + // draw bottom right + glTexCoord2f(1.f - border_width_fraction, 0.f); + glVertex2i(width - scaled_border_width, 0); + + glTexCoord2f(1.f, 0.f); + glVertex2i(width, 0); + + glTexCoord2f(1.f, border_height_fraction); + glVertex2i(width, scaled_border_height); + + glTexCoord2f(1.f - border_width_fraction, border_height_fraction); + glVertex2i(width - scaled_border_width, scaled_border_height); + + // draw left + glTexCoord2f(0.f, border_height_fraction); + glVertex2i(0, scaled_border_height); + + glTexCoord2f(border_width_fraction, border_height_fraction); + glVertex2i(scaled_border_width, scaled_border_height); + + glTexCoord2f(border_width_fraction, 1.f - border_height_fraction); + glVertex2i(scaled_border_width, height - scaled_border_height); + + glTexCoord2f(0.f, 1.f - border_height_fraction); + glVertex2i(0, height - scaled_border_height); + + // draw middle + glTexCoord2f(border_width_fraction, border_height_fraction); + glVertex2i(scaled_border_width, scaled_border_height); + + glTexCoord2f(1.f - border_width_fraction, border_height_fraction); + glVertex2i(width - scaled_border_width, scaled_border_height); + + glTexCoord2f(1.f - border_width_fraction, 1.f - border_height_fraction); + glVertex2i(width - scaled_border_width, height - scaled_border_height); + + glTexCoord2f(border_width_fraction, 1.f - border_height_fraction); + glVertex2i(scaled_border_width, height - scaled_border_height); + + // draw right + glTexCoord2f(1.f - border_width_fraction, border_height_fraction); + glVertex2i(width - scaled_border_width, scaled_border_height); + + glTexCoord2f(1.f, border_height_fraction); + glVertex2i(width, scaled_border_height); + + glTexCoord2f(1.f, 1.f - border_height_fraction); + glVertex2i(width, height - scaled_border_height); + + glTexCoord2f(1.f - border_width_fraction, 1.f - border_height_fraction); + glVertex2i(width - scaled_border_width, height - scaled_border_height); + + // draw top left + glTexCoord2f(0.f, 1.f - border_height_fraction); + glVertex2i(0, height - scaled_border_height); + + glTexCoord2f(border_width_fraction, 1.f - border_height_fraction); + glVertex2i(scaled_border_width, height - scaled_border_height); + + glTexCoord2f(border_width_fraction, 1.f); + glVertex2i(scaled_border_width, height); + + glTexCoord2f(0.f, 1.f); + glVertex2i(0, height); + + // draw top middle + glTexCoord2f(border_width_fraction, 1.f - border_height_fraction); + glVertex2i(scaled_border_width, height - scaled_border_height); + + glTexCoord2f(1.f - border_width_fraction, 1.f - border_height_fraction); + glVertex2i(width - scaled_border_width, height - scaled_border_height); + + glTexCoord2f(1.f - border_width_fraction, 1.f); + glVertex2i(width - scaled_border_width, height); + + glTexCoord2f(border_width_fraction, 1.f); + glVertex2i(scaled_border_width, height); + + // draw top right + glTexCoord2f(1.f - border_width_fraction, 1.f - border_height_fraction); + glVertex2i(width - scaled_border_width, height - scaled_border_height); + + glTexCoord2f(1.f, 1.f - border_height_fraction); + glVertex2i(width, height - scaled_border_height); + + glTexCoord2f(1.f, 1.f); + glVertex2i(width, height); + + glTexCoord2f(1.f - border_width_fraction, 1.f); + glVertex2i(width - scaled_border_width, height); + } + glEnd(); + } + glPopMatrix(); + + if (solid_color) + { + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + } +} + +void gl_draw_rotated_image(S32 x, S32 y, F32 degrees, LLImageGL* image, const LLColor4& color) +{ + gl_draw_scaled_rotated_image( x, y, image->getWidth(), image->getHeight(), degrees, image, color ); +} + +void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degrees, LLImageGL* image, const LLColor4& color) +{ + LLGLSUIDefault gls_ui; + + glPushMatrix(); + { + glTranslatef((F32)x, (F32)y, 0.f); + if( degrees ) + { + F32 offset_x = F32(width/2); + F32 offset_y = F32(height/2); + glTranslatef( offset_x, offset_y, 0.f); + glRotatef( degrees, 0.f, 0.f, 1.f ); + glTranslatef( -offset_x, -offset_y, 0.f ); + } + + image->bind(); + + glColor4fv(color.mV); + + glBegin(GL_QUADS); + { + glTexCoord2f(1.f, 1.f); + glVertex2i(width, height ); + + glTexCoord2f(0.f, 1.f); + glVertex2i(0, height ); + + glTexCoord2f(0.f, 0.f); + glVertex2i(0, 0); + + glTexCoord2f(1.f, 0.f); + glVertex2i(width, 0); + } + glEnd(); + } + glPopMatrix(); +} + + +void gl_draw_scaled_image_inverted(S32 x, S32 y, S32 width, S32 height, LLImageGL* image, const LLColor4& color) +{ + LLGLSUIDefault gls_ui; + + glPushMatrix(); + { + glTranslatef((F32)x, (F32)y, 0.f); + + image->bind(); + + glColor4fv(color.mV); + + glBegin(GL_QUADS); + { + glTexCoord2f(1.f, 0.f); + glVertex2i(width, height ); + + glTexCoord2f(0.f, 0.f); + glVertex2i(0, height ); + + glTexCoord2f(0.f, 1.f); + glVertex2i(0, 0); + + glTexCoord2f(1.f, 1.f); + glVertex2i(width, 0); + } + glEnd(); + } + glPopMatrix(); +} + + +void gl_stippled_line_3d( const LLVector3& start, const LLVector3& end, const LLColor4& color, F32 phase ) +{ + phase = fmod(phase, 1.f); + + S32 shift = S32(phase * 4.f) % 4; + + // Stippled line + LLGLEnable stipple(GL_LINE_STIPPLE); + + glColor4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], color.mV[VALPHA]); + glLineWidth(2.5f); + glLineStipple(2, 0x3333 << shift); + + glBegin(GL_LINES); + { + glVertex3fv( start.mV ); + glVertex3fv( end.mV ); + } + glEnd(); + + LLUI::setLineWidth(1.f); +} + + +void gl_rect_2d_xor(S32 left, S32 top, S32 right, S32 bottom) +{ + glColor4fv( LLColor4::white.mV ); + glLogicOp( GL_XOR ); + stop_glerror(); + + glBegin(GL_QUADS); + glVertex2i(left, top); + glVertex2i(left, bottom); + glVertex2i(right, bottom); + glVertex2i(right, top); + glEnd(); + + glLogicOp( GL_COPY ); + stop_glerror(); +} + + +void gl_arc_2d(F32 center_x, F32 center_y, F32 radius, S32 steps, BOOL filled, F32 start_angle, F32 end_angle) +{ + if (end_angle < start_angle) + { + end_angle += F_TWO_PI; + } + + glPushMatrix(); + { + glTranslatef(center_x, center_y, 0.f); + + // Inexact, but reasonably fast. + F32 delta = (end_angle - start_angle) / steps; + F32 sin_delta = sin( delta ); + F32 cos_delta = cos( delta ); + F32 x = cosf(start_angle) * radius; + F32 y = sinf(start_angle) * radius; + + if (filled) + { + glBegin(GL_TRIANGLE_FAN); + glVertex2f(0.f, 0.f); + // make sure circle is complete + steps += 1; + } + else + { + glBegin(GL_LINE_STRIP); + } + + while( steps-- ) + { + // Successive rotations + glVertex2f( x, y ); + F32 x_new = x * cos_delta - y * sin_delta; + y = x * sin_delta + y * cos_delta; + x = x_new; + } + glEnd(); + } + glPopMatrix(); +} + +void gl_circle_2d(F32 center_x, F32 center_y, F32 radius, S32 steps, BOOL filled) +{ + glPushMatrix(); + { + LLGLSNoTexture gls_no_texture; + glTranslatef(center_x, center_y, 0.f); + + // Inexact, but reasonably fast. + F32 delta = F_TWO_PI / steps; + F32 sin_delta = sin( delta ); + F32 cos_delta = cos( delta ); + F32 x = radius; + F32 y = 0.f; + + if (filled) + { + glBegin(GL_TRIANGLE_FAN); + glVertex2f(0.f, 0.f); + // make sure circle is complete + steps += 1; + } + else + { + glBegin(GL_LINE_LOOP); + } + + while( steps-- ) + { + // Successive rotations + glVertex2f( x, y ); + F32 x_new = x * cos_delta - y * sin_delta; + y = x * sin_delta + y * cos_delta; + x = x_new; + } + glEnd(); + } + glPopMatrix(); +} + +// Renders a ring with sides (tube shape) +void gl_deep_circle( F32 radius, F32 depth, S32 steps ) +{ + F32 x = radius; + F32 y = 0.f; + F32 angle_delta = F_TWO_PI / (F32)steps; + glBegin( GL_TRIANGLE_STRIP ); + { + S32 step = steps + 1; // An extra step to close the circle. + while( step-- ) + { + glVertex3f( x, y, depth ); + glVertex3f( x, y, 0.f ); + + F32 x_new = x * cosf(angle_delta) - y * sinf(angle_delta); + y = x * sinf(angle_delta) + y * cosf(angle_delta); + x = x_new; + } + } + glEnd(); +} + +void gl_ring( F32 radius, F32 width, const LLColor4& center_color, const LLColor4& side_color, S32 steps, BOOL render_center ) +{ + glPushMatrix(); + { + glTranslatef(0.f, 0.f, -width / 2); + if( render_center ) + { + glColor4fv(center_color.mV); + gl_deep_circle( radius, width, steps ); + } + else + { + gl_washer_2d(radius, radius - width, steps, side_color, side_color); + glTranslatef(0.f, 0.f, width); + gl_washer_2d(radius - width, radius, steps, side_color, side_color); + } + } + glPopMatrix(); +} + +// Draw gray and white checkerboard with black border +void gl_rect_2d_checkerboard(const LLRect& rect) +{ + // Initialize the first time this is called. + const S32 PIXELS = 32; + static GLubyte checkerboard[PIXELS * PIXELS]; + static BOOL first = TRUE; + if( first ) + { + for( S32 i = 0; i < PIXELS; i++ ) + { + for( S32 j = 0; j < PIXELS; j++ ) + { + checkerboard[i * PIXELS + j] = ((i & 1) ^ (j & 1)) * 0xFF; + } + } + first = FALSE; + } + + LLGLSNoTexture gls_no_texture; + + // ...white squares + glColor3f( 1.f, 1.f, 1.f ); + gl_rect_2d(rect); + + // ...gray squares + glColor3f( .7f, .7f, .7f ); + glPolygonStipple( checkerboard ); + + LLGLEnable polygon_stipple(GL_POLYGON_STIPPLE); + gl_rect_2d(rect); +} + + +// Draws the area between two concentric circles, like +// a doughnut or washer. +void gl_washer_2d(F32 outer_radius, F32 inner_radius, S32 steps, const LLColor4& inner_color, const LLColor4& outer_color) +{ + const F32 DELTA = F_TWO_PI / steps; + const F32 SIN_DELTA = sin( DELTA ); + const F32 COS_DELTA = cos( DELTA ); + + F32 x1 = outer_radius; + F32 y1 = 0.f; + F32 x2 = inner_radius; + F32 y2 = 0.f; + + LLGLSNoTexture gls_no_texture; + + glBegin( GL_TRIANGLE_STRIP ); + { + steps += 1; // An extra step to close the circle. + while( steps-- ) + { + glColor4fv(outer_color.mV); + glVertex2f( x1, y1 ); + glColor4fv(inner_color.mV); + glVertex2f( x2, y2 ); + + F32 x1_new = x1 * COS_DELTA - y1 * SIN_DELTA; + y1 = x1 * SIN_DELTA + y1 * COS_DELTA; + x1 = x1_new; + + F32 x2_new = x2 * COS_DELTA - y2 * SIN_DELTA; + y2 = x2 * SIN_DELTA + y2 * COS_DELTA; + x2 = x2_new; + } + } + glEnd(); +} + +// Draws the area between two concentric circles, like +// a doughnut or washer. +void gl_washer_segment_2d(F32 outer_radius, F32 inner_radius, F32 start_radians, F32 end_radians, S32 steps, const LLColor4& inner_color, const LLColor4& outer_color) +{ + const F32 DELTA = (end_radians - start_radians) / steps; + const F32 SIN_DELTA = sin( DELTA ); + const F32 COS_DELTA = cos( DELTA ); + + F32 x1 = outer_radius * cos( start_radians ); + F32 y1 = outer_radius * sin( start_radians ); + F32 x2 = inner_radius * cos( start_radians ); + F32 y2 = inner_radius * sin( start_radians ); + + LLGLSNoTexture gls_no_texture; + glBegin( GL_TRIANGLE_STRIP ); + { + steps += 1; // An extra step to close the circle. + while( steps-- ) + { + glColor4fv(outer_color.mV); + glVertex2f( x1, y1 ); + glColor4fv(inner_color.mV); + glVertex2f( x2, y2 ); + + F32 x1_new = x1 * COS_DELTA - y1 * SIN_DELTA; + y1 = x1 * SIN_DELTA + y1 * COS_DELTA; + x1 = x1_new; + + F32 x2_new = x2 * COS_DELTA - y2 * SIN_DELTA; + y2 = x2 * SIN_DELTA + y2 * COS_DELTA; + x2 = x2_new; + } + } + glEnd(); +} + +// Draws spokes around a circle. +void gl_washer_spokes_2d(F32 outer_radius, F32 inner_radius, S32 count, const LLColor4& inner_color, const LLColor4& outer_color) +{ + const F32 DELTA = F_TWO_PI / count; + const F32 HALF_DELTA = DELTA * 0.5f; + const F32 SIN_DELTA = sin( DELTA ); + const F32 COS_DELTA = cos( DELTA ); + + F32 x1 = outer_radius * cos( HALF_DELTA ); + F32 y1 = outer_radius * sin( HALF_DELTA ); + F32 x2 = inner_radius * cos( HALF_DELTA ); + F32 y2 = inner_radius * sin( HALF_DELTA ); + + LLGLSNoTexture gls_no_texture; + + glBegin( GL_LINES ); + { + while( count-- ) + { + glColor4fv(outer_color.mV); + glVertex2f( x1, y1 ); + glColor4fv(inner_color.mV); + glVertex2f( x2, y2 ); + + F32 x1_new = x1 * COS_DELTA - y1 * SIN_DELTA; + y1 = x1 * SIN_DELTA + y1 * COS_DELTA; + x1 = x1_new; + + F32 x2_new = x2 * COS_DELTA - y2 * SIN_DELTA; + y2 = x2 * SIN_DELTA + y2 * COS_DELTA; + x2 = x2_new; + } + } + glEnd(); +} + +void gl_rect_2d_simple_tex( S32 width, S32 height ) +{ + glBegin( GL_QUADS ); + + glTexCoord2f(1.f, 1.f); + glVertex2i(width, height); + + glTexCoord2f(0.f, 1.f); + glVertex2i(0, height); + + glTexCoord2f(0.f, 0.f); + glVertex2i(0, 0); + + glTexCoord2f(1.f, 0.f); + glVertex2i(width, 0); + + glEnd(); +} + +void gl_rect_2d_simple( S32 width, S32 height ) +{ + glBegin( GL_QUADS ); + glVertex2i(width, height); + glVertex2i(0, height); + glVertex2i(0, 0); + glVertex2i(width, 0); + glEnd(); +} + +void gl_segmented_rect_2d_tex(const S32 left, + const S32 top, + const S32 right, + const S32 bottom, + const S32 texture_width, + const S32 texture_height, + const S32 border_size, + const U32 edges) +{ + S32 width = llabs(right - left); + S32 height = llabs(top - bottom); + + glPushMatrix(); + + glTranslatef((F32)left, (F32)bottom, 0.f); + LLVector2 border_uv_scale((F32)border_size / (F32)texture_width, (F32)border_size / (F32)texture_height); + + if (border_uv_scale.mV[VX] > 0.5f) + { + border_uv_scale *= 0.5f / border_uv_scale.mV[VX]; + } + if (border_uv_scale.mV[VY] > 0.5f) + { + border_uv_scale *= 0.5f / border_uv_scale.mV[VY]; + } + + F32 border_scale = llmin((F32)border_size, (F32)width * 0.5f, (F32)height * 0.5f); + LLVector2 border_width_left = ((edges & (~(U32)ROUNDED_RECT_RIGHT)) != 0) ? LLVector2(border_scale, 0.f) : LLVector2::zero; + LLVector2 border_width_right = ((edges & (~(U32)ROUNDED_RECT_LEFT)) != 0) ? LLVector2(border_scale, 0.f) : LLVector2::zero; + LLVector2 border_height_bottom = ((edges & (~(U32)ROUNDED_RECT_TOP)) != 0) ? LLVector2(0.f, border_scale) : LLVector2::zero; + LLVector2 border_height_top = ((edges & (~(U32)ROUNDED_RECT_BOTTOM)) != 0) ? LLVector2(0.f, border_scale) : LLVector2::zero; + LLVector2 width_vec((F32)width, 0.f); + LLVector2 height_vec(0.f, (F32)height); + + glBegin(GL_QUADS); + { + // draw bottom left + glTexCoord2f(0.f, 0.f); + glVertex2f(0.f, 0.f); + + glTexCoord2f(border_uv_scale.mV[VX], 0.f); + glVertex2fv(border_width_left.mV); + + glTexCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((border_width_left + border_height_bottom).mV); + + glTexCoord2f(0.f, border_uv_scale.mV[VY]); + glVertex2fv(border_height_bottom.mV); + + // draw bottom middle + glTexCoord2f(border_uv_scale.mV[VX], 0.f); + glVertex2fv(border_width_left.mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 0.f); + glVertex2fv((width_vec - border_width_right).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((width_vec - border_width_right + border_height_bottom).mV); + + glTexCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((border_width_left + border_height_bottom).mV); + + // draw bottom right + glTexCoord2f(1.f - border_uv_scale.mV[VX], 0.f); + glVertex2fv((width_vec - border_width_right).mV); + + glTexCoord2f(1.f, 0.f); + glVertex2fv(width_vec.mV); + + glTexCoord2f(1.f, border_uv_scale.mV[VY]); + glVertex2fv((width_vec + border_height_bottom).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((width_vec - border_width_right + border_height_bottom).mV); + + // draw left + glTexCoord2f(0.f, border_uv_scale.mV[VY]); + glVertex2fv(border_height_bottom.mV); + + glTexCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((border_width_left + border_height_bottom).mV); + + glTexCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((border_width_left + height_vec - border_height_top).mV); + + glTexCoord2f(0.f, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((height_vec - border_height_top).mV); + + // draw middle + glTexCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((border_width_left + border_height_bottom).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((width_vec - border_width_right + border_height_bottom).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((width_vec - border_width_right + height_vec - border_height_top).mV); + + glTexCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((border_width_left + height_vec - border_height_top).mV); + + // draw right + glTexCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((width_vec - border_width_right + border_height_bottom).mV); + + glTexCoord2f(1.f, border_uv_scale.mV[VY]); + glVertex2fv((width_vec + border_height_bottom).mV); + + glTexCoord2f(1.f, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((width_vec + height_vec - border_height_top).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((width_vec - border_width_right + height_vec - border_height_top).mV); + + // draw top left + glTexCoord2f(0.f, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((height_vec - border_height_top).mV); + + glTexCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((border_width_left + height_vec - border_height_top).mV); + + glTexCoord2f(border_uv_scale.mV[VX], 1.f); + glVertex2fv((border_width_left + height_vec).mV); + + glTexCoord2f(0.f, 1.f); + glVertex2fv((height_vec).mV); + + // draw top middle + glTexCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((border_width_left + height_vec - border_height_top).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((width_vec - border_width_right + height_vec - border_height_top).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f); + glVertex2fv((width_vec - border_width_right + height_vec).mV); + + glTexCoord2f(border_uv_scale.mV[VX], 1.f); + glVertex2fv((border_width_left + height_vec).mV); + + // draw top right + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((width_vec - border_width_right + height_vec - border_height_top).mV); + + glTexCoord2f(1.f, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((width_vec + height_vec - border_height_top).mV); + + glTexCoord2f(1.f, 1.f); + glVertex2fv((width_vec + height_vec).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f); + glVertex2fv((width_vec - border_width_right + height_vec).mV); + } + glEnd(); + + glPopMatrix(); +} + +void gl_segmented_rect_2d_fragment_tex(const S32 left, + const S32 top, + const S32 right, + const S32 bottom, + const S32 texture_width, + const S32 texture_height, + const S32 border_size, + const F32 start_fragment, + const F32 end_fragment, + const U32 edges) +{ + S32 width = llabs(right - left); + S32 height = llabs(top - bottom); + + glPushMatrix(); + + glTranslatef((F32)left, (F32)bottom, 0.f); + LLVector2 border_uv_scale((F32)border_size / (F32)texture_width, (F32)border_size / (F32)texture_height); + + if (border_uv_scale.mV[VX] > 0.5f) + { + border_uv_scale *= 0.5f / border_uv_scale.mV[VX]; + } + if (border_uv_scale.mV[VY] > 0.5f) + { + border_uv_scale *= 0.5f / border_uv_scale.mV[VY]; + } + + F32 border_scale = llmin((F32)border_size, (F32)width * 0.5f, (F32)height * 0.5f); + LLVector2 border_width_left = ((edges & (~(U32)ROUNDED_RECT_RIGHT)) != 0) ? LLVector2(border_scale, 0.f) : LLVector2::zero; + LLVector2 border_width_right = ((edges & (~(U32)ROUNDED_RECT_LEFT)) != 0) ? LLVector2(border_scale, 0.f) : LLVector2::zero; + LLVector2 border_height_bottom = ((edges & (~(U32)ROUNDED_RECT_TOP)) != 0) ? LLVector2(0.f, border_scale) : LLVector2::zero; + LLVector2 border_height_top = ((edges & (~(U32)ROUNDED_RECT_BOTTOM)) != 0) ? LLVector2(0.f, border_scale) : LLVector2::zero; + LLVector2 width_vec((F32)width, 0.f); + LLVector2 height_vec(0.f, (F32)height); + + F32 middle_start = border_scale / (F32)width; + F32 middle_end = 1.f - middle_start; + + F32 u_min; + F32 u_max; + LLVector2 x_min; + LLVector2 x_max; + + glBegin(GL_QUADS); + { + if (start_fragment < middle_start) + { + u_min = (start_fragment / middle_start) * border_uv_scale.mV[VX]; + u_max = llmin(end_fragment / middle_start, 1.f) * border_uv_scale.mV[VX]; + x_min = (start_fragment / middle_start) * border_width_left; + x_max = llmin(end_fragment / middle_start, 1.f) * border_width_left; + + // draw bottom left + glTexCoord2f(u_min, 0.f); + glVertex2fv(x_min.mV); + + glTexCoord2f(border_uv_scale.mV[VX], 0.f); + glVertex2fv(x_max.mV); + + glTexCoord2f(u_max, border_uv_scale.mV[VY]); + glVertex2fv((x_max + border_height_bottom).mV); + + glTexCoord2f(u_min, border_uv_scale.mV[VY]); + glVertex2fv((x_min + border_height_bottom).mV); + + // draw left + glTexCoord2f(u_min, border_uv_scale.mV[VY]); + glVertex2fv((x_min + border_height_bottom).mV); + + glTexCoord2f(u_max, border_uv_scale.mV[VY]); + glVertex2fv((x_max + border_height_bottom).mV); + + glTexCoord2f(u_max, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_max + height_vec - border_height_top).mV); + + glTexCoord2f(u_min, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_min + height_vec - border_height_top).mV); + + // draw top left + glTexCoord2f(u_min, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_min + height_vec - border_height_top).mV); + + glTexCoord2f(u_max, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_max + height_vec - border_height_top).mV); + + glTexCoord2f(u_max, 1.f); + glVertex2fv((x_max + height_vec).mV); + + glTexCoord2f(u_min, 1.f); + glVertex2fv((x_min + height_vec).mV); + } + + if (end_fragment > middle_start || start_fragment < middle_end) + { + x_min = border_width_left + ((llclamp(start_fragment, middle_start, middle_end) - middle_start)) * width_vec; + x_max = border_width_left + ((llclamp(end_fragment, middle_start, middle_end) - middle_start)) * width_vec; + + // draw bottom middle + glTexCoord2f(border_uv_scale.mV[VX], 0.f); + glVertex2fv(x_min.mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 0.f); + glVertex2fv((x_max).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((x_max + border_height_bottom).mV); + + glTexCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((x_min + border_height_bottom).mV); + + // draw middle + glTexCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((x_min + border_height_bottom).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + glVertex2fv((x_max + border_height_bottom).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_max + height_vec - border_height_top).mV); + + glTexCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_min + height_vec - border_height_top).mV); + + // draw top middle + glTexCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_min + height_vec - border_height_top).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_max + height_vec - border_height_top).mV); + + glTexCoord2f(1.f - border_uv_scale.mV[VX], 1.f); + glVertex2fv((x_max + height_vec).mV); + + glTexCoord2f(border_uv_scale.mV[VX], 1.f); + glVertex2fv((x_min + height_vec).mV); + } + + if (end_fragment > middle_end) + { + u_min = (1.f - llmax(0.f, ((start_fragment - middle_end) / middle_start))) * border_uv_scale.mV[VX]; + u_max = (1.f - ((end_fragment - middle_end) / middle_start)) * border_uv_scale.mV[VX]; + x_min = width_vec - ((1.f - llmax(0.f, ((start_fragment - middle_end) / middle_start))) * border_width_right); + x_max = width_vec - ((1.f - ((end_fragment - middle_end) / middle_start)) * border_width_right); + + // draw bottom right + glTexCoord2f(u_min, 0.f); + glVertex2fv((x_min).mV); + + glTexCoord2f(u_max, 0.f); + glVertex2fv(x_max.mV); + + glTexCoord2f(u_max, border_uv_scale.mV[VY]); + glVertex2fv((x_max + border_height_bottom).mV); + + glTexCoord2f(u_min, border_uv_scale.mV[VY]); + glVertex2fv((x_min + border_height_bottom).mV); + + // draw right + glTexCoord2f(u_min, border_uv_scale.mV[VY]); + glVertex2fv((x_min + border_height_bottom).mV); + + glTexCoord2f(u_max, border_uv_scale.mV[VY]); + glVertex2fv((x_max + border_height_bottom).mV); + + glTexCoord2f(u_max, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_max + height_vec - border_height_top).mV); + + glTexCoord2f(u_min, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_min + height_vec - border_height_top).mV); + + // draw top right + glTexCoord2f(u_min, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_min + height_vec - border_height_top).mV); + + glTexCoord2f(u_max, 1.f - border_uv_scale.mV[VY]); + glVertex2fv((x_max + height_vec - border_height_top).mV); + + glTexCoord2f(u_max, 1.f); + glVertex2fv((x_max + height_vec).mV); + + glTexCoord2f(u_min, 1.f); + glVertex2fv((x_min + height_vec).mV); + } + } + glEnd(); + + glPopMatrix(); +} + +void gl_segmented_rect_3d_tex(const LLVector2& border_scale, const LLVector3& border_width, + const LLVector3& border_height, const LLVector3& width_vec, const LLVector3& height_vec, + const U32 edges) +{ + LLVector3 left_border_width = ((edges & (~(U32)ROUNDED_RECT_RIGHT)) != 0) ? border_width : LLVector3::zero; + LLVector3 right_border_width = ((edges & (~(U32)ROUNDED_RECT_LEFT)) != 0) ? border_width : LLVector3::zero; + + LLVector3 top_border_height = ((edges & (~(U32)ROUNDED_RECT_BOTTOM)) != 0) ? border_height : LLVector3::zero; + LLVector3 bottom_border_height = ((edges & (~(U32)ROUNDED_RECT_TOP)) != 0) ? border_height : LLVector3::zero; + + glBegin(GL_QUADS); + { + // draw bottom left + glTexCoord2f(0.f, 0.f); + glVertex3f(0.f, 0.f, 0.f); + + glTexCoord2f(border_scale.mV[VX], 0.f); + glVertex3fv(left_border_width.mV); + + glTexCoord2f(border_scale.mV[VX], border_scale.mV[VY]); + glVertex3fv((left_border_width + bottom_border_height).mV); + + glTexCoord2f(0.f, border_scale.mV[VY]); + glVertex3fv(bottom_border_height.mV); + + // draw bottom middle + glTexCoord2f(border_scale.mV[VX], 0.f); + glVertex3fv(left_border_width.mV); + + glTexCoord2f(1.f - border_scale.mV[VX], 0.f); + glVertex3fv((width_vec - right_border_width).mV); + + glTexCoord2f(1.f - border_scale.mV[VX], border_scale.mV[VY]); + glVertex3fv((width_vec - right_border_width + bottom_border_height).mV); + + glTexCoord2f(border_scale.mV[VX], border_scale.mV[VY]); + glVertex3fv((left_border_width + bottom_border_height).mV); + + // draw bottom right + glTexCoord2f(1.f - border_scale.mV[VX], 0.f); + glVertex3fv((width_vec - right_border_width).mV); + + glTexCoord2f(1.f, 0.f); + glVertex3fv(width_vec.mV); + + glTexCoord2f(1.f, border_scale.mV[VY]); + glVertex3fv((width_vec + bottom_border_height).mV); + + glTexCoord2f(1.f - border_scale.mV[VX], border_scale.mV[VY]); + glVertex3fv((width_vec - right_border_width + bottom_border_height).mV); + + // draw left + glTexCoord2f(0.f, border_scale.mV[VY]); + glVertex3fv(bottom_border_height.mV); + + glTexCoord2f(border_scale.mV[VX], border_scale.mV[VY]); + glVertex3fv((left_border_width + bottom_border_height).mV); + + glTexCoord2f(border_scale.mV[VX], 1.f - border_scale.mV[VY]); + glVertex3fv((left_border_width + height_vec - top_border_height).mV); + + glTexCoord2f(0.f, 1.f - border_scale.mV[VY]); + glVertex3fv((height_vec - top_border_height).mV); + + // draw middle + glTexCoord2f(border_scale.mV[VX], border_scale.mV[VY]); + glVertex3fv((left_border_width + bottom_border_height).mV); + + glTexCoord2f(1.f - border_scale.mV[VX], border_scale.mV[VY]); + glVertex3fv((width_vec - right_border_width + bottom_border_height).mV); + + glTexCoord2f(1.f - border_scale.mV[VX], 1.f - border_scale.mV[VY]); + glVertex3fv((width_vec - right_border_width + height_vec - top_border_height).mV); + + glTexCoord2f(border_scale.mV[VX], 1.f - border_scale.mV[VY]); + glVertex3fv((left_border_width + height_vec - top_border_height).mV); + + // draw right + glTexCoord2f(1.f - border_scale.mV[VX], border_scale.mV[VY]); + glVertex3fv((width_vec - right_border_width + bottom_border_height).mV); + + glTexCoord2f(1.f, border_scale.mV[VY]); + glVertex3fv((width_vec + bottom_border_height).mV); + + glTexCoord2f(1.f, 1.f - border_scale.mV[VY]); + glVertex3fv((width_vec + height_vec - top_border_height).mV); + + glTexCoord2f(1.f - border_scale.mV[VX], 1.f - border_scale.mV[VY]); + glVertex3fv((width_vec - right_border_width + height_vec - top_border_height).mV); + + // draw top left + glTexCoord2f(0.f, 1.f - border_scale.mV[VY]); + glVertex3fv((height_vec - top_border_height).mV); + + glTexCoord2f(border_scale.mV[VX], 1.f - border_scale.mV[VY]); + glVertex3fv((left_border_width + height_vec - top_border_height).mV); + + glTexCoord2f(border_scale.mV[VX], 1.f); + glVertex3fv((left_border_width + height_vec).mV); + + glTexCoord2f(0.f, 1.f); + glVertex3fv((height_vec).mV); + + // draw top middle + glTexCoord2f(border_scale.mV[VX], 1.f - border_scale.mV[VY]); + glVertex3fv((left_border_width + height_vec - top_border_height).mV); + + glTexCoord2f(1.f - border_scale.mV[VX], 1.f - border_scale.mV[VY]); + glVertex3fv((width_vec - right_border_width + height_vec - top_border_height).mV); + + glTexCoord2f(1.f - border_scale.mV[VX], 1.f); + glVertex3fv((width_vec - right_border_width + height_vec).mV); + + glTexCoord2f(border_scale.mV[VX], 1.f); + glVertex3fv((left_border_width + height_vec).mV); + + // draw top right + glTexCoord2f(1.f - border_scale.mV[VX], 1.f - border_scale.mV[VY]); + glVertex3fv((width_vec - right_border_width + height_vec - top_border_height).mV); + + glTexCoord2f(1.f, 1.f - border_scale.mV[VY]); + glVertex3fv((width_vec + height_vec - top_border_height).mV); + + glTexCoord2f(1.f, 1.f); + glVertex3fv((width_vec + height_vec).mV); + + glTexCoord2f(1.f - border_scale.mV[VX], 1.f); + glVertex3fv((width_vec - right_border_width + height_vec).mV); + } + glEnd(); +} + +void gl_segmented_rect_3d_tex_top(const LLVector2& border_scale, const LLVector3& border_width, const LLVector3& border_height, const LLVector3& width_vec, const LLVector3& height_vec) +{ + gl_segmented_rect_3d_tex(border_scale, border_width, border_height, width_vec, height_vec, ROUNDED_RECT_TOP); +} + +#if 0 // No longer used +void load_tr(const LLString& lang) +{ + LLString inname = "words." + lang + ".txt"; + LLString filename = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, inname.c_str()); + + llifstream file; + file.open(filename.c_str(), std::ios_base::binary); + if (!file) + { + llinfos << "No translation dictionary for: " << filename << llendl; + return; + } + + llinfos << "Reading language translation dictionary: " << filename << llendl; + + gTranslation.clear(); + gUntranslated.clear(); + + const S32 MAX_LINE_LEN = 1024; + char buffer[MAX_LINE_LEN]; + while (!file.eof()) + { + file.getline(buffer, MAX_LINE_LEN); + LLString line(buffer); + S32 commentpos = line.find("//"); + if (commentpos != LLString::npos) + { + line = line.substr(0, commentpos); + } + S32 offset = line.find('\t'); + if (offset != LLString::npos) + { + LLString english = line.substr(0,offset); + LLString translation = line.substr(offset+1); + //llinfos << "TR: " << english << " = " << translation << llendl; + gTranslation[english] = translation; + } + } + + file.close(); +} + +void init_tr(const LLString& language) +{ + if (!language.empty()) + { + gLanguage = language; + } + load_tr(gLanguage); +} + +void cleanup_tr() +{ + // Dump untranslated phrases to help with translation + if (gUntranslated.size() > 0) + { + LLString outname = "untranslated_" + gLanguage + ".txt"; + LLString outfilename = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, outname.c_str()); + llofstream outfile; + outfile.open(outfilename.c_str()); + if (!outfile) + { + return; + } + llinfos << "Writing untranslated words to: " << outfilename << llendl; + LLString outtext; + for (std::list::iterator iter = gUntranslated.begin(); + iter != gUntranslated.end(); ++iter) + { + // output: english_phrase english_phrase + outtext += *iter; + outtext += "\t"; + outtext += *iter; + outtext += "\n"; + } + outfile << outtext.c_str(); + outfile.close(); + } +} + +LLString tr(const LLString& english_string) +{ + std::map::iterator it = gTranslation.find(english_string); + if (it != gTranslation.end()) + { + return it->second; + } + else + { + gUntranslated.push_back(english_string); + return english_string; + } +} + +#endif + + +class LLShowXUINamesListener: public LLSimpleListener +{ + bool handleEvent(LLPointer event, const LLSD& userdata) + { + LLUI::sShowXUINames = (BOOL) event->getValue().asBoolean(); + return true; + } +}; +static LLShowXUINamesListener show_xui_names_listener; + + +void LLUI::initClass(LLControlGroup* config, + LLControlGroup* colors, + LLControlGroup* assets, + LLImageProviderInterface* image_provider, + LLUIAudioCallback audio_callback, + const LLVector2* scale_factor, + const LLString& language) +{ + sConfigGroup = config; + sColorsGroup = colors; + sAssetsGroup = assets; + sImageProvider = image_provider; + sAudioCallback = audio_callback; + sGLScaleFactor = (scale_factor == NULL) ? LLVector2(1.f, 1.f) : *scale_factor; + sWindow = NULL; // set later in startup + LLFontGL::sShadowColor = colors->getColor("ColorDropShadow"); + + LLUI::sShowXUINames = LLUI::sConfigGroup->getBOOL("ShowXUINames"); + LLUI::sConfigGroup->getControl("ShowXUINames")->addListener(&show_xui_names_listener); +// init_tr(language); +} + +void LLUI::cleanupClass() +{ +// cleanup_tr(); +} + + +//static +void LLUI::translate(F32 x, F32 y, F32 z) +{ + glTranslatef(x,y,z); + LLFontGL::sCurOrigin.mX += (S32) x; + LLFontGL::sCurOrigin.mY += (S32) y; + LLFontGL::sCurOrigin.mZ += z; +} + +//static +void LLUI::pushMatrix() +{ + glPushMatrix(); + LLFontGL::sOriginStack.push_back(LLFontGL::sCurOrigin); +} + +//static +void LLUI::popMatrix() +{ + glPopMatrix(); + LLFontGL::sCurOrigin = *LLFontGL::sOriginStack.rbegin(); + LLFontGL::sOriginStack.pop_back(); +} + +//static +void LLUI::loadIdentity() +{ + glLoadIdentity(); + LLFontGL::sCurOrigin.mX = 0; + LLFontGL::sCurOrigin.mY = 0; + LLFontGL::sCurOrigin.mZ = 0; +} + +//static +void LLUI::setScissorRegionScreen(const LLRect& rect) +{ + stop_glerror(); + S32 x,y,w,h; + x = llround(rect.mLeft * LLUI::sGLScaleFactor.mV[VX]); + y = llround(rect.mBottom * LLUI::sGLScaleFactor.mV[VY]); + w = llround(rect.getWidth() * LLUI::sGLScaleFactor.mV[VX]); + h = llround(rect.getHeight() * LLUI::sGLScaleFactor.mV[VY]); + glScissor( x,y,w,h ); + stop_glerror(); +} + +//static +void LLUI::setScissorRegionLocal(const LLRect& rect) +{ + stop_glerror(); + S32 screen_left = LLFontGL::sCurOrigin.mX + rect.mLeft; + S32 screen_bottom = LLFontGL::sCurOrigin.mY + rect.mBottom; + + S32 x,y,w,h; + + x = llround((F32)screen_left * LLUI::sGLScaleFactor.mV[VX]); + y = llround((F32)screen_bottom * LLUI::sGLScaleFactor.mV[VY]); + w = llround((F32)rect.getWidth() * LLUI::sGLScaleFactor.mV[VX]); + h = llround((F32)rect.getHeight() * LLUI::sGLScaleFactor.mV[VY]); + + w = llmax(0,w); + h = llmax(0,h); + + glScissor(x,y,w,h); + stop_glerror(); +} + +//static +void LLUI::setScaleFactor(const LLVector2 &scale_factor) +{ + sGLScaleFactor = scale_factor; +} + +//static +void LLUI::setLineWidth(F32 width) +{ + glLineWidth(width * lerp(sGLScaleFactor.mV[VX], sGLScaleFactor.mV[VY], 0.5f)); +} + +//static +void LLUI::setCursorPositionScreen(S32 x, S32 y) +{ + S32 screen_x, screen_y; + screen_x = llround((F32)x * sGLScaleFactor.mV[VX]); + screen_y = llround((F32)y * sGLScaleFactor.mV[VY]); + + LLCoordWindow window_point; + LLView::getWindow()->convertCoords(LLCoordGL(screen_x, screen_y), &window_point); + + LLView::getWindow()->setCursorPosition(window_point); +} + +//static +void LLUI::setCursorPositionLocal(LLView* viewp, S32 x, S32 y) +{ + S32 screen_x, screen_y; + viewp->localPointToScreen(x, y, &screen_x, &screen_y); + + setCursorPositionScreen(screen_x, screen_y); +} + +//static +LLString LLUI::locateSkin(const LLString& filename) +{ + LLString slash = gDirUtilp->getDirDelimiter(); + LLString found_file = filename; + if (!gDirUtilp->fileExists(found_file)) + { + found_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename); // Should be CUSTOM_SKINS? + } + if (sConfigGroup && sConfigGroup->controlExists("Language")) + { + if (!gDirUtilp->fileExists(found_file)) + { + LLString localization(sConfigGroup->getString("Language")); + LLString local_skin = "xui" + slash + localization + slash + filename; + found_file = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, local_skin); + } + } + if (!gDirUtilp->fileExists(found_file)) + { + LLString local_skin = "xui" + slash + "en-us" + slash + filename; + found_file = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, local_skin); + } + if (!gDirUtilp->fileExists(found_file)) + { + found_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, filename); + } + return found_file; +} + +//static +LLVector2 LLUI::getWindowSize() +{ + LLCoordWindow window_rect; + sWindow->getSize(&window_rect); + + return LLVector2(window_rect.mX / sGLScaleFactor.mV[VX], window_rect.mY / sGLScaleFactor.mV[VY]); +} + +//static +LLUUID LLUI::findAssetUUIDByName(const LLString &asset_name) +{ + if(asset_name == LLString::null) return LLUUID::null; + LLString foundValue = LLUI::sConfigGroup->findString(asset_name); + if(foundValue==LLString::null) + { + foundValue = LLUI::sAssetsGroup->findString(asset_name); + } + if(foundValue == LLString::null){ + return LLUUID::null; + } + return LLUUID( foundValue ); +} diff --git a/indra/llui/llui.h b/indra/llui/llui.h new file mode 100644 index 0000000000..282e41a113 --- /dev/null +++ b/indra/llui/llui.h @@ -0,0 +1,255 @@ +/** + * @file llui.h + * @brief UI implementation + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// All immediate-mode gl drawing should happen here. + +#ifndef LL_LLUI_H +#define LL_LLUI_H + +#include "llrect.h" +#include "llcontrol.h" +#include "llrect.h" +#include "llcoord.h" + +class LLColor4; +class LLVector3; +class LLVector2; +class LLImageGL; +class LLUUID; +class LLWindow; +class LLView; + +// UI colors +extern const LLColor4 UI_VERTEX_COLOR; +void make_ui_sound(const LLString& name); + +BOOL ui_point_in_rect(S32 x, S32 y, S32 left, S32 top, S32 right, S32 bottom); +void gl_state_for_2d(S32 width, S32 height); + +void gl_line_2d(S32 x1, S32 y1, S32 x2, S32 y2); +void gl_line_2d(S32 x1, S32 y1, S32 x2, S32 y2, const LLColor4 &color ); +void gl_triangle_2d(S32 x1, S32 y1, S32 x2, S32 y2, S32 x3, S32 y3, const LLColor4& color, BOOL filled); +void gl_rect_2d_simple( S32 width, S32 height ); + +void gl_draw_x(const LLRect& rect, const LLColor4& color); + +void gl_rect_2d(S32 left, S32 top, S32 right, S32 bottom, BOOL filled = TRUE ); +void gl_rect_2d(S32 left, S32 top, S32 right, S32 bottom, const LLColor4 &color, BOOL filled = TRUE ); +void gl_rect_2d_offset_local( S32 left, S32 top, S32 right, S32 bottom, const LLColor4 &color, S32 pixel_offset = 0, BOOL filled = TRUE ); +void gl_rect_2d_offset_local( S32 left, S32 top, S32 right, S32 bottom, S32 pixel_offset = 0, BOOL filled = TRUE ); +void gl_rect_2d(const LLRect& rect, BOOL filled = TRUE ); +void gl_rect_2d(const LLRect& rect, const LLColor4& color, BOOL filled = TRUE ); +void gl_rect_2d_checkerboard(const LLRect& rect); + +void gl_drop_shadow(S32 left, S32 top, S32 right, S32 bottom, const LLColor4 &start_color, S32 lines); + +void gl_circle_2d(F32 x, F32 y, F32 radius, S32 steps, BOOL filled); +void gl_arc_2d(F32 center_x, F32 center_y, F32 radius, S32 steps, BOOL filled, F32 start_angle, F32 end_angle); +void gl_deep_circle( F32 radius, F32 depth ); +void gl_ring( F32 radius, F32 width, const LLColor4& center_color, const LLColor4& side_color, S32 steps, BOOL render_center ); +void gl_corners_2d(S32 left, S32 top, S32 right, S32 bottom, S32 length, F32 max_frac); +void gl_washer_2d(F32 outer_radius, F32 inner_radius, S32 steps, const LLColor4& inner_color, const LLColor4& outer_color); +void gl_washer_segment_2d(F32 outer_radius, F32 inner_radius, F32 start_radians, F32 end_radians, S32 steps, const LLColor4& inner_color, const LLColor4& outer_color); +void gl_washer_spokes_2d(F32 outer_radius, F32 inner_radius, S32 count, const LLColor4& inner_color, const LLColor4& outer_color); + +void gl_draw_image(S32 x, S32 y, LLImageGL* image, const LLColor4& color = UI_VERTEX_COLOR); +void gl_draw_scaled_image(S32 x, S32 y, S32 width, S32 height, LLImageGL* image, const LLColor4& color = UI_VERTEX_COLOR); +void gl_draw_rotated_image(S32 x, S32 y, F32 degrees, LLImageGL* image, const LLColor4& color = UI_VERTEX_COLOR); +void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degrees,LLImageGL* image, const LLColor4& color = UI_VERTEX_COLOR); +void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 border_width, S32 border_height, S32 width, S32 height, LLImageGL* image, const LLColor4 &color, BOOL solid_color = FALSE); +// Flip vertical, used for LLFloaterHTML +void gl_draw_scaled_image_inverted(S32 x, S32 y, S32 width, S32 height, LLImageGL* image, const LLColor4& color = UI_VERTEX_COLOR); + +void gl_rect_2d_xor(S32 left, S32 top, S32 right, S32 bottom); +void gl_stippled_line_3d( const LLVector3& start, const LLVector3& end, const LLColor4& color, F32 phase = 0.f ); + +void gl_rect_2d_simple_tex( S32 width, S32 height ); + +// segmented rectangles + +/* + TL |______TOP_________| TR + /| |\ + _/_|__________________|_\_ + L| | MIDDLE | |R + _|_|__________________|_|_ + \ | BOTTOM | / + BL\|__________________|/ BR + | | +*/ + +typedef enum e_rounded_edge +{ + ROUNDED_RECT_LEFT = 0x1, + ROUNDED_RECT_TOP = 0x2, + ROUNDED_RECT_RIGHT = 0x4, + ROUNDED_RECT_BOTTOM = 0x8, + ROUNDED_RECT_ALL = 0xf +}ERoundedEdge; + + +void gl_segmented_rect_2d_tex(const S32 left, const S32 top, const S32 right, const S32 bottom, const S32 texture_width, const S32 texture_height, const S32 border_size, const U32 edges = ROUNDED_RECT_ALL); +void gl_segmented_rect_2d_fragment_tex(const S32 left, const S32 top, const S32 right, const S32 bottom, const S32 texture_width, const S32 texture_height, const S32 border_size, const F32 start_fragment, const F32 end_fragment, const U32 edges = ROUNDED_RECT_ALL); +void gl_segmented_rect_3d_tex(const LLVector2& border_scale, const LLVector3& border_width, const LLVector3& border_height, const LLVector3& width_vec, const LLVector3& height_vec, U32 edges = ROUNDED_RECT_ALL); +void gl_segmented_rect_3d_tex_top(const LLVector2& border_scale, const LLVector3& border_width, const LLVector3& border_height, const LLVector3& width_vec, const LLVector3& height_vec); + +inline void gl_rect_2d( const LLRect& rect, BOOL filled ) +{ + gl_rect_2d( rect.mLeft, rect.mTop, rect.mRight, rect.mBottom, filled ); +} + +inline void gl_rect_2d_offset_local( const LLRect& rect, S32 pixel_offset, BOOL filled) +{ + gl_rect_2d_offset_local( rect.mLeft, rect.mTop, rect.mRight, rect.mBottom, pixel_offset, filled ); +} + +// No longer used +// Initializes translation table +// void init_tr(); + +// Returns a string from the string table in the correct language +// LLString tr(const LLString& english_chars); + +// Used to hide the flashing text cursor when window doesn't have focus. +extern BOOL gShowTextEditCursor; + +// Language +extern LLString gLanguage; + +class LLImageProviderInterface; +typedef void (*LLUIAudioCallback)(const LLUUID& uuid, F32 volume); + +class LLUI +{ +public: + static void initClass(LLControlGroup* config, + LLControlGroup* colors, + LLControlGroup* assets, + LLImageProviderInterface* image_provider, + LLUIAudioCallback audio_callback = NULL, + const LLVector2 *scale_factor = NULL, + const LLString& language = LLString::null); + static void cleanupClass(); + + static void pushMatrix(); + static void popMatrix(); + static void loadIdentity(); + static void translate(F32 x, F32 y, F32 z = 0.0f); + + //helper functions (should probably move free standing rendering helper functions here) + static LLString locateSkin(const LLString& filename); + static void setScissorRegionScreen(const LLRect& rect); + static void setScissorRegionLocal(const LLRect& rect); // works assuming LLUI::translate has been called + static void setCursorPositionScreen(S32 x, S32 y); + static void setCursorPositionLocal(LLView* viewp, S32 x, S32 y); + static void setScaleFactor(const LLVector2& scale_factor); + static void setLineWidth(F32 width); + static LLUUID findAssetUUIDByName(const LLString& name); + static LLVector2 getWindowSize(); +public: + static LLControlGroup* sConfigGroup; + static LLControlGroup* sColorsGroup; + static LLControlGroup* sAssetsGroup; + static LLImageProviderInterface* sImageProvider; + static LLUIAudioCallback sAudioCallback; + static LLVector2 sGLScaleFactor; + static LLWindow* sWindow; + static BOOL sShowXUINames; +}; + +// UI widgets +// This MUST match UICtrlNames in lluictrlfactory.cpp +typedef enum e_widget_type +{ + WIDGET_TYPE_VIEW = 0, + WIDGET_TYPE_ROOT_VIEW, + WIDGET_TYPE_FLOATER_VIEW, + WIDGET_TYPE_BUTTON, + WIDGET_TYPE_JOYSTICK_TURN, + WIDGET_TYPE_JOYSTICK_SLIDE, + WIDGET_TYPE_CHECKBOX, + WIDGET_TYPE_COLOR_SWATCH, + WIDGET_TYPE_COMBO_BOX, + WIDGET_TYPE_LINE_EDITOR, + WIDGET_TYPE_SEARCH_EDITOR, + WIDGET_TYPE_SCROLL_LIST, + WIDGET_TYPE_NAME_LIST, + WIDGET_TYPE_WEBBROWSER, + WIDGET_TYPE_SLIDER, // actually LLSliderCtrl + WIDGET_TYPE_SLIDER_BAR, // actually LLSlider + WIDGET_TYPE_VOLUME_SLIDER,//actually LLVolumeSliderCtrl + WIDGET_TYPE_SPINNER, + WIDGET_TYPE_TEXT_EDITOR, + WIDGET_TYPE_TEXTURE_PICKER, + WIDGET_TYPE_TEXT_BOX, + WIDGET_TYPE_PAD, // used in XML for positioning, not a real widget + WIDGET_TYPE_RADIO_GROUP, + WIDGET_TYPE_ICON, + WIDGET_TYPE_LOCATE, // used in XML for positioning, not a real widget + WIDGET_TYPE_VIEW_BORDER, // decorative border + WIDGET_TYPE_PANEL, + WIDGET_TYPE_MENU, + WIDGET_TYPE_PIE_MENU, + WIDGET_TYPE_PIE_MENU_BRANCH, + WIDGET_TYPE_MENU_ITEM, + WIDGET_TYPE_MENU_ITEM_SEPARATOR, + WIDGET_TYPE_MENU_SEPARATOR_VERTICAL, + WIDGET_TYPE_MENU_ITEM_CALL, + WIDGET_TYPE_MENU_ITEM_CHECK, + WIDGET_TYPE_MENU_ITEM_BRANCH, + WIDGET_TYPE_MENU_ITEM_BRANCH_DOWN, + WIDGET_TYPE_MENU_ITEM_BLANK, + WIDGET_TYPE_TEAROFF_MENU, + WIDGET_TYPE_MENU_BAR, + WIDGET_TYPE_TAB_CONTAINER, + WIDGET_TYPE_SCROLL_CONTAINER, // LLScrollableContainerView + WIDGET_TYPE_SCROLLBAR, + WIDGET_TYPE_INVENTORY_PANEL, // LLInventoryPanel + WIDGET_TYPE_FLOATER, + WIDGET_TYPE_DRAG_HANDLE_TOP, + WIDGET_TYPE_DRAG_HANDLE_LEFT, + WIDGET_TYPE_RESIZE_HANDLE, + WIDGET_TYPE_RESIZE_BAR, + WIDGET_TYPE_NAME_EDITOR, + WIDGET_TYPE_MULTI_FLOATER, + WIDGET_TYPE_MEDIA_REMOTE, + WIDGET_TYPE_FOLDER_VIEW, + WIDGET_TYPE_FOLDER_ITEM, + WIDGET_TYPE_FOLDER, + WIDGET_TYPE_STAT_GRAPH, + WIDGET_TYPE_STAT_VIEW, + WIDGET_TYPE_STAT_BAR, + WIDGET_TYPE_DROP_TARGET, + WIDGET_TYPE_TEXTURE_BAR, + WIDGET_TYPE_TEX_MEM_BAR, + WIDGET_TYPE_SNAPSHOT_LIVE_PREVIEW, + WIDGET_TYPE_STATUS_BAR, + WIDGET_TYPE_PROGRESS_VIEW, + WIDGET_TYPE_TALK_VIEW, + WIDGET_TYPE_OVERLAY_BAR, + WIDGET_TYPE_HUD_VIEW, + WIDGET_TYPE_HOVER_VIEW, + WIDGET_TYPE_MORPH_VIEW, + WIDGET_TYPE_NET_MAP, + WIDGET_TYPE_PERMISSIONS_VIEW, + WIDGET_TYPE_MENU_HOLDER, + WIDGET_TYPE_DEBUG_VIEW, + WIDGET_TYPE_SCROLLING_PANEL_LIST, + WIDGET_TYPE_AUDIO_STATUS, + WIDGET_TYPE_CONTAINER_VIEW, + WIDGET_TYPE_CONSOLE, + WIDGET_TYPE_FAST_TIMER_VIEW, + WIDGET_TYPE_VELOCITY_BAR, + WIDGET_TYPE_TEXTURE_VIEW, + WIDGET_TYPE_MEMORY_VIEW, + WIDGET_TYPE_FRAME_STAT_VIEW, + WIDGET_TYPE_DONTCARE, + WIDGET_TYPE_COUNT +} EWidgetType; + +#endif diff --git a/indra/llui/lluiconstants.h b/indra/llui/lluiconstants.h new file mode 100644 index 0000000000..01663ed233 --- /dev/null +++ b/indra/llui/lluiconstants.h @@ -0,0 +1,32 @@ +/** + * @file lluiconstants.h + * @brief Compile-time configuration for UI + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLUICONSTANTS_H +#define LL_LLUICONSTANTS_H + +// spacing for small font lines of text, like LLTextBoxes +const S32 LINE = 16; + +// spacing for larger lines of text +const S32 LINE_BIG = 24; + +// default vertical padding +const S32 VPAD = 4; + +// default horizontal padding +const S32 HPAD = 4; + +// Account History, how far to look into past +const S32 SUMMARY_INTERVAL = 7; // one week +const S32 SUMMARY_MAX = 8; // +const S32 DETAILS_INTERVAL = 1; // one day +const S32 DETAILS_MAX = 30; // one month +const S32 TRANSACTIONS_INTERVAL = 1;// one day +const S32 TRANSACTIONS_MAX = 30; // one month + +#endif diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp new file mode 100644 index 0000000000..0d9791c660 --- /dev/null +++ b/indra/llui/lluictrl.cpp @@ -0,0 +1,341 @@ +/** + * @file lluictrl.cpp + * @author James Cook, Richard Nelson, Tom Yedwab + * @brief Abstract base class for UI controls + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//#include "llviewerprecompiledheaders.h" +#include "linden_common.h" + +#include "lluictrl.h" + +#include "llgl.h" +#include "llui.h" +#include "lluiconstants.h" +#include "llfocusmgr.h" +#include "v3color.h" + +#include "llstring.h" +#include "llfontgl.h" +#include "llkeyboard.h" + +const U32 MAX_STRING_LENGTH = 10; + +LLUICtrl::LLUICtrl() : + mCommitCallback(NULL), + mFocusReceivedCallback(NULL), + mFocusChangedCallback(NULL), + mValidateCallback(NULL), + mCallbackUserData(NULL), + mTentative(FALSE), + mTabStop(TRUE), + mIsChrome(FALSE) +{ +} + +LLUICtrl::LLUICtrl(const LLString& name, const LLRect& rect, BOOL mouse_opaque, + void (*on_commit_callback)(LLUICtrl*, void*), + void* callback_userdata, + U32 reshape) +: // can't make this automatically follow top and left, breaks lots + // of buttons in the UI. JC 7/20/2002 + LLView( name, rect, mouse_opaque, reshape ), + mCommitCallback( on_commit_callback) , + mFocusReceivedCallback( NULL ), + mFocusChangedCallback( NULL ), + mValidateCallback( NULL ), + mCallbackUserData( callback_userdata ), + mTentative( FALSE ), + mTabStop( TRUE ), + mIsChrome(FALSE) +{ +} + +LLUICtrl::~LLUICtrl() +{ + gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() +} + +void LLUICtrl::onCommit() +{ + if( mCommitCallback ) + { + mCommitCallback( this, mCallbackUserData ); + } +} + +// virtual +BOOL LLUICtrl::setTextArg( const LLString& key, const LLString& text ) +{ + return FALSE; +} + +// virtual +BOOL LLUICtrl::setLabelArg( const LLString& key, const LLString& text ) +{ + return FALSE; +} + +// virtual +LLCtrlSelectionInterface* LLUICtrl::getSelectionInterface() +{ + return NULL; +} + +// virtual +LLCtrlListInterface* LLUICtrl::getListInterface() +{ + return NULL; +} + +// virtual +LLCtrlScrollInterface* LLUICtrl::getScrollInterface() +{ + return NULL; +} + +// virtual +void LLUICtrl::setTabStop( BOOL b ) +{ + mTabStop = b; +} + +// virtual +BOOL LLUICtrl::hasTabStop() const +{ + return mTabStop; +} + +// virtual +BOOL LLUICtrl::acceptsTextInput() const +{ + return FALSE; +} + +// virtual +void LLUICtrl::onTabInto() +{ +} + +// virtual +void LLUICtrl::clear() +{ +} + +// virtual +void LLUICtrl::setIsChrome(BOOL is_chrome) +{ + mIsChrome = is_chrome; +} + +// virtual +BOOL LLUICtrl::getIsChrome() const +{ + return mIsChrome; +} + +void LLUICtrl::onFocusReceived() +{ + if( mFocusReceivedCallback ) + { + mFocusReceivedCallback( this, mCallbackUserData ); + } + if( mFocusChangedCallback ) + { + mFocusChangedCallback( this, mCallbackUserData ); + } +} + +void LLUICtrl::onFocusLost() +{ + if( mFocusChangedCallback ) + { + mFocusChangedCallback( this, mCallbackUserData ); + } +} + +BOOL LLUICtrl::hasFocus() const +{ + return (gFocusMgr.childHasKeyboardFocus(this)); +} + +void LLUICtrl::setFocus(BOOL b) +{ + // focus NEVER goes to ui ctrls that are disabled! + if (!mEnabled) + { + return; + } + if( b ) + { + if (!hasFocus()) + { + gFocusMgr.setKeyboardFocus( this, &LLUICtrl::onFocusLostCallback ); + onFocusReceived(); + } + } + else + { + if( gFocusMgr.childHasKeyboardFocus(this)) + { + gFocusMgr.setKeyboardFocus( NULL, NULL ); + onFocusLost(); + } + } +} + +// static +void LLUICtrl::onFocusLostCallback( LLUICtrl* old_focus ) +{ + old_focus->onFocusLost(); +} + +// this comparator uses the crazy disambiguating logic of LLCompareByTabOrder, +// but to switch up the order so that children that have the default tab group come first +// and those that are prior to the default tab group come last +class CompareByDefaultTabGroup: public LLCompareByTabOrder +{ +public: + CompareByDefaultTabGroup(LLView::child_tab_order_t order, S32 default_tab_group): + LLCompareByTabOrder(order), + mDefaultTabGroup(default_tab_group) {} +protected: + /*virtual*/ bool compareTabOrders(const LLView::tab_order_t & a, const LLView::tab_order_t & b) const + { + S32 ag = a.first; // tab group for a + S32 bg = b.first; // tab group for b + // these two ifs have the effect of moving elements prior to the default tab group to the end of the list + // (still sorted relative to each other, though) + if(ag < mDefaultTabGroup && bg >= mDefaultTabGroup) return false; + if(bg < mDefaultTabGroup && ag >= mDefaultTabGroup) return true; + return a < b; // sort correctly if they're both on the same side of the default tab group + } + S32 mDefaultTabGroup; +}; + +// sorter for plugging into the query +class DefaultTabGroupFirstSorter : public LLQuerySorter, public LLSingleton +{ +public: + /*virtual*/ void operator() (LLView * parent, viewList_t &children) const + { + children.sort(CompareByDefaultTabGroup(parent->getCtrlOrder(), parent->getDefaultTabGroup())); + } +}; + +BOOL LLUICtrl::focusFirstItem(BOOL prefer_text_fields) +{ + // try to select default tab group child + LLCtrlQuery query = LLView::getTabOrderQuery(); + // sort things such that the default tab group is at the front + query.setSorter(DefaultTabGroupFirstSorter::getInstance()); + LLView::child_list_t result = query(this); + if(result.size() > 0) + { + LLUICtrl * ctrl = static_cast(result.front()); + if(!ctrl->hasFocus()) + { + ctrl->setFocus(TRUE); + ctrl->onTabInto(); + gFocusMgr.triggerFocusFlash(); + } + return TRUE; + } + // fall back on default behavior if we didn't find anything + return LLView::focusFirstItem(prefer_text_fields); +} + +/* +// Don't let the children handle the tool tip. Handle it here instead. +BOOL LLUICtrl::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen) +{ + BOOL handled = FALSE; + if (getVisible() && pointInView( x, y ) ) + { + if( !mToolTipMsg.empty() ) + { + msg = mToolTipMsg; + + // Convert rect local to screen coordinates + localPointToScreen( + 0, 0, + &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); + localPointToScreen( + mRect.getWidth(), mRect.getHeight(), + &(sticky_rect_screen->mRight), &(sticky_rect_screen->mTop) ); + + handled = TRUE; + } + } + + if (!handled) + { + return LLView::handleToolTip(x, y, msg, sticky_rect_screen); + } + + return handled; +}*/ + +void LLUICtrl::initFromXML(LLXMLNodePtr node, LLView* parent) +{ + BOOL has_tab_stop = hasTabStop(); + node->getAttributeBOOL("tab_stop", has_tab_stop); + + setTabStop(has_tab_stop); + + LLView::initFromXML(node, parent); +} + +LLXMLNodePtr LLUICtrl::getXML(bool save_children) const +{ + LLXMLNodePtr node = LLView::getXML(save_children); + node->createChild("tab_stop", TRUE)->setBoolValue(hasTabStop()); + + return node; +} + +// *NOTE: If other classes derive from LLPanel, they will need to be +// added to this function. +LLPanel* LLUICtrl::getParentPanel() const +{ + LLView* parent = getParent(); + while (parent + && parent->getWidgetType() != WIDGET_TYPE_PANEL + && parent->getWidgetType() != WIDGET_TYPE_FLOATER) + { + parent = parent->getParent(); + } + return reinterpret_cast(parent); +} + +// virtual +void LLUICtrl::setTentative(BOOL b) +{ + mTentative = b; +} + +// virtual +BOOL LLUICtrl::getTentative() const +{ + return mTentative; +} + +// virtual +void LLUICtrl::setDoubleClickCallback( void (*cb)(void*) ) +{ +} + +// virtual +void LLUICtrl::setColor(const LLColor4& color) +{ } + +// virtual +void LLUICtrl::setMinValue(LLSD min_value) +{ } + +// virtual +void LLUICtrl::setMaxValue(LLSD max_value) +{ } diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h new file mode 100644 index 0000000000..f58b7d6e16 --- /dev/null +++ b/indra/llui/lluictrl.h @@ -0,0 +1,153 @@ +/** + * @file lluictrl.h + * @author James Cook, Richard Nelson, Tom Yedwab + * @brief Abstract base class for UI controls + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLUICTRL_H +#define LL_LLUICTRL_H + +#include "llview.h" +#include "llrect.h" +#include "llsd.h" + +// +// Classes +// +class LLViewerImage; +class LLFontGL; +class LLButton; +class LLTextBox; +class LLLineEditor; +class LLUICtrl; +class LLPanel; +class LLCtrlSelectionInterface; +class LLCtrlListInterface; +class LLCtrlScrollInterface; + +typedef void (*LLUICtrlCallback)(LLUICtrl* ctrl, void* userdata); +typedef BOOL (*LLUICtrlValidate)(LLUICtrl* ctrl, void* userdata); + +class LLUICtrl +: public LLView +{ +public: + LLUICtrl(); + LLUICtrl( const LLString& name, const LLRect& rect, BOOL mouse_opaque, + LLUICtrlCallback callback, + void* callback_userdata, + U32 reshape=FOLLOWS_NONE); + virtual ~LLUICtrl(); + + // LLView interface + //virtual BOOL handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect); + virtual void initFromXML(LLXMLNodePtr node, LLView* parent); + virtual LLXMLNodePtr getXML(bool save_children = true) const; + + virtual LLSD getValue() const { return LLSD(); } + + // Defaults to no-op + virtual BOOL setTextArg( const LLString& key, const LLString& text ); + + // Defaults to no-op + virtual BOOL setLabelArg( const LLString& key, const LLString& text ); + + // Defaults to return NULL + virtual LLCtrlSelectionInterface* getSelectionInterface(); + virtual LLCtrlListInterface* getListInterface(); + virtual LLCtrlScrollInterface* getScrollInterface(); + + virtual void setFocus( BOOL b ); + virtual BOOL hasFocus() const; + + virtual void setTabStop( BOOL b ); + virtual BOOL hasTabStop() const; + + // Defaults to false + virtual BOOL acceptsTextInput() const; + + // Default to no-op + virtual void onTabInto(); + virtual void clear(); + + virtual void setIsChrome(BOOL is_chrome); + virtual BOOL getIsChrome() const; + + virtual void onCommit(); + + virtual BOOL isCtrl() const { return TRUE; } + // "Tentative" controls have a proposed value, but haven't committed + // it yet. This is used when multiple objects are selected and we + // want to display a parameter that differs between the objects. + virtual void setTentative(BOOL b); + virtual BOOL getTentative() const; + + // Returns containing panel/floater or NULL if none found. + LLPanel* getParentPanel() const; + + void* getCallbackUserData() const { return mCallbackUserData; } + void setCallbackUserData( void* data ) { mCallbackUserData = data; } + + void setCommitCallback( void (*cb)(LLUICtrl*, void*) ) { mCommitCallback = cb; } + void setValidateBeforeCommit( BOOL(*cb)(LLUICtrl*, void*) ) { mValidateCallback = cb; } + + // Defaults to no-op! + virtual void setDoubleClickCallback( void (*cb)(void*) ); + + // Defaults to no-op + virtual void setColor(const LLColor4& color); + + // Defaults to no-op + virtual void setMinValue(LLSD min_value); + virtual void setMaxValue(LLSD max_value); + + // In general, only LLPanel uses these. + void setFocusReceivedCallback( void (*cb)(LLUICtrl*, void*) ) { mFocusReceivedCallback = cb; } + void setFocusChangedCallback( void (*cb)(LLUICtrl*, void*) ) { mFocusChangedCallback = cb; } + + static void onFocusLostCallback(LLUICtrl* old_focus); + + /*virtual*/ BOOL focusFirstItem(BOOL prefer_text_fields = FALSE ); + + class LLTabStopPostFilter : public LLQueryFilter, public LLSingleton + { + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const + { + return filterResult_t(view->isCtrl() && static_cast(view)->hasTabStop() && children.size() == 0, TRUE); + } + }; + + class LLTextInputFilter : public LLQueryFilter, public LLSingleton + { + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const + { + return filterResult_t(view->isCtrl() && static_cast(view)->acceptsTextInput(), TRUE); + } + }; + +protected: + virtual void onFocusReceived(); + virtual void onFocusLost(); + void onChangeFocus( S32 direction ); + +protected: + + void (*mCommitCallback)( LLUICtrl* ctrl, void* userdata ); + void (*mFocusReceivedCallback)( LLUICtrl* ctrl, void* userdata ); + void (*mFocusChangedCallback)( LLUICtrl* ctrl, void* userdata ); + BOOL (*mValidateCallback)( LLUICtrl* ctrl, void* userdata ); + + void* mCallbackUserData; + BOOL mTentative; + BOOL mTabStop; + +private: + BOOL mIsChrome; + + +}; + +#endif // LL_LLUICTRL_H diff --git a/indra/llui/lluictrlfactory.cpp b/indra/llui/lluictrlfactory.cpp new file mode 100644 index 0000000000..7e286f0bee --- /dev/null +++ b/indra/llui/lluictrlfactory.cpp @@ -0,0 +1,722 @@ +/** + * @file lluictrlfactory.cpp + * @brief Factory class for creating UI controls + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lluictrlfactory.h" + +#include +#include + +// other library includes +#include "llcontrol.h" +#include "lldir.h" +#include "v4color.h" + +// this library includes +#include "llbutton.h" +#include "llcheckboxctrl.h" +//#include "llcolorswatch.h" +#include "llcombobox.h" +#include "llcontrol.h" +#include "lldir.h" +#include "llevent.h" +#include "llfloater.h" +#include "lliconctrl.h" +#include "lllineeditor.h" +#include "llmenugl.h" +#include "llradiogroup.h" +#include "llscrollcontainer.h" +#include "llscrollingpanellist.h" +#include "llscrolllistctrl.h" +#include "llslider.h" +#include "llsliderctrl.h" +#include "llspinctrl.h" +#include "lltabcontainer.h" +#include "lltabcontainervertical.h" +#include "lltextbox.h" +#include "lltexteditor.h" +#include "llui.h" +#include "llviewborder.h" + + +const char XML_HEADER[] = "\n"; + +// *NOTE: If you add a new class derived from LLPanel, add a check for its +// widget type to LLUICtrl::getParentPanel(). +// *NOTE: This MUST match EWidgetType in llui.h +//static +const LLString LLUICtrlFactory::sUICtrlNames[WIDGET_TYPE_COUNT] = +{ + LLString("view"), //WIDGET_TYPE_VIEW + LLString("root_view"), //WIDGET_TYPE_ROOT_VIEW + LLString("floater_view"), //WIDGET_TYPE_FLOATER_VIEW + LLString("button"), //WIDGET_TYPE_BUTTON + LLString("joystick_turn"), //WIDGET_TYPE_JOYSTICK_TURN + LLString("joystick_slide"), //WIDGET_TYPE_JOYSTICK_SLIDE + LLString("check_box"), //WIDGET_TYPE_CHECKBOX + LLString("color_swatch"), //WIDGET_TYPE_COLOR_SWATCH + LLString("combo_box"), //WIDGET_TYPE_COMBO_BOX + LLString("line_editor"), //WIDGET_TYPE_LINE_EDITOR + LLString("search_editor"), //WIDGET_TYPE_SEARCH_EDITOR + LLString("scroll_list"), //WIDGET_TYPE_SCROLL_LIST + LLString("name_list"), //WIDGET_TYPE_NAME_LIST + LLString("web_browser"), //WIDGET_TYPE_WEBBROWSER + LLString("slider"), //WIDGET_TYPE_SLIDER, actually LLSliderCtrl + LLString("slider_bar"), //WIDGET_TYPE_SLIDER_BAR, actually LLSlider + LLString("volume_slider"), //WIDGET_TYPE_VOLUME_SLIDER, actually LLVolumeSliderCtrl + LLString("spinner"), //WIDGET_TYPE_SPINNER, actually LLSpinCtrl + LLString("text_editor"), //WIDGET_TYPE_TEXT_EDITOR + LLString("texture_picker"),//WIDGET_TYPE_TEXTURE_PICKER + LLString("text"), //WIDGET_TYPE_TEXT_BOX + LLString("pad"), //WIDGET_TYPE_PAD + LLString("radio_group"), //WIDGET_TYPE_RADIO_GROUP + LLString("icon"), //WIDGET_TYPE_ICON + LLString("locate"), //WIDGET_TYPE_LOCATE + LLString("view_border"), //WIDGET_TYPE_VIEW_BORDER + LLString("panel"), //WIDGET_TYPE_PANEL + LLString("menu"), //WIDGET_TYPE_MENU + LLString("pie_menu"), //WIDGET_TYPE_PIE_MENU + LLString("pie_menu_branch"), //WIDGET_TYPE_PIE_MENU_BRANCH + LLString("menu_item"), //WIDGET_TYPE_MENU_ITEM + LLString("menu_item_separator"), //WIDGET_TYPE_MENU_ITEM_SEPARATOR + LLString("menu_separator_vertical"), // WIDGET_TYPE_MENU_SEPARATOR_VERTICAL + LLString("menu_item_call"), // WIDGET_TYPE_MENU_ITEM_CALL + LLString("menu_item_check"),// WIDGET_TYPE_MENU_ITEM_CHECK + LLString("menu_item_branch"), // WIDGET_TYPE_MENU_ITEM_BRANCH + LLString("menu_item_branch_down"), //WIDGET_TYPE_MENU_ITEM_BRANCH_DOWN, + LLString("menu_item_blank"), //WIDGET_TYPE_MENU_ITEM_BLANK, + LLString("tearoff_menu"), //WIDGET_TYPE_TEAROFF_MENU + LLString("menu_bar"), //WIDGET_TYPE_MENU_BAR + LLString("tab_container"),//WIDGET_TYPE_TAB_CONTAINER + LLString("scroll_container"),//WIDGET_TYPE_SCROLL_CONTAINER + LLString("scrollbar"), //WIDGET_TYPE_SCROLLBAR + LLString("inventory_panel"), //WIDGET_TYPE_INVENTORY_PANEL + LLString("floater"), //WIDGET_TYPE_FLOATER + LLString("drag_handle_top"), //WIDGET_TYPE_DRAG_HANDLE_TOP + LLString("drag_handle_left"), //WIDGET_TYPE_DRAG_HANDLE_LEFT + LLString("resize_handle"), //WIDGET_TYPE_RESIZE_HANDLE + LLString("resize_bar"), //WIDGET_TYPE_RESIZE_BAR + LLString("name_editor"), //WIDGET_TYPE_NAME_EDITOR + LLString("multi_floater"), //WIDGET_TYPE_MULTI_FLOATER + LLString("media_remote"), //WIDGET_TYPE_MEDIA_REMOTE + LLString("folder_view"), //WIDGET_TYPE_FOLDER_VIEW + LLString("folder_item"), //WIDGET_TYPE_FOLDER_ITEM + LLString("folder"), //WIDGET_TYPE_FOLDER + LLString("stat_graph"), //WIDGET_TYPE_STAT_GRAPH + LLString("stat_view"), //WIDGET_TYPE_STAT_VIEW + LLString("stat_bar"), //WIDGET_TYPE_STAT_BAR + LLString("drop_target"), //WIDGET_TYPE_DROP_TARGET + LLString("texture_bar"), //WIDGET_TYPE_TEXTURE_BAR + LLString("tex_mem_bar"), //WIDGET_TYPE_TEX_MEM_BAR + LLString("snapshot_live_preview"), //WIDGET_TYPE_SNAPSHOT_LIVE_PREVIEW + LLString("status_bar"), //WIDGET_TYPE_STATUS_BAR + LLString("progress_view"), //WIDGET_TYPE_PROGRESS_VIEW + LLString("talk_view"), //WIDGET_TYPE_TALK_VIEW + LLString("overlay_bar"), //WIDGET_TYPE_OVERLAY_BAR + LLString("hud_view"), //WIDGET_TYPE_HUD_VIEW + LLString("hover_view"), //WIDGET_TYPE_HOVER_VIEW + LLString("morph_view"), //WIDGET_TYPE_MORPH_VIEW + LLString("net_map"), //WIDGET_TYPE_NET_MAP + LLString("permissions_view"), //WIDGET_TYPE_PERMISSIONS_VIEW + LLString("menu_holder"), //WIDGET_TYPE_MENU_HOLDER + LLString("debug_view"), //WIDGET_TYPE_DEBUG_VIEW + LLString("scrolling_panel_list"), //WIDGET_TYPE_SCROLLING_PANEL_LIST + LLString("audio_status"), //WIDGET_TYPE_AUDIO_STATUS + LLString("container_view"), //WIDGET_TYPE_CONTAINER_VIEW + LLString("console"), //WIDGET_TYPE_CONSOLE + LLString("fast_timer_view"), //WIDGET_TYPE_FAST_TIMER_VIEW + LLString("velocity_bar"), //WIDGET_TYPE_VELOCITY_BAR + LLString("texture_view"), //WIDGET_TYPE_TEXTURE_VIEW + LLString("memory_view"), //WIDGET_TYPE_MEMORY_VIEW + LLString("frame_stat_view"), //WIDGET_TYPE_FRAME_STAT_VIEW + LLString("DONT_CARE"), //WIDGET_TYPE_DONTCARE +}; + +const S32 HPAD = 4; +const S32 VPAD = 4; +const S32 FLOATER_H_MARGIN = 15; +const S32 MIN_WIDGET_HEIGHT = 10; + +std::vector LLUICtrlFactory::mXUIPaths; + +// UI Ctrl class for padding +class LLUICtrlLocate : public LLUICtrl +{ +public: + LLUICtrlLocate() : LLUICtrl("locate", LLRect(0,0,0,0), FALSE, NULL, NULL) {} + virtual void draw() { } + + virtual EWidgetType getWidgetType() const { return WIDGET_TYPE_LOCATE; } + virtual LLString getWidgetTag() const { return LL_UI_CTRL_LOCATE_TAG; } + + static LLView *fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) + { + LLUICtrlLocate *new_ctrl = new LLUICtrlLocate(); + new_ctrl->initFromXML(node, parent); + return new_ctrl; + } +}; + +//----------------------------------------------------------------------------- +// LLUICtrlFactory() +//----------------------------------------------------------------------------- +LLUICtrlFactory::LLUICtrlFactory() +{ + // Register controls + LLUICtrlCreator::registerCreator(LL_BUTTON_TAG, this); + LLUICtrlCreator::registerCreator(LL_CHECK_BOX_CTRL_TAG, this); + LLUICtrlCreator::registerCreator(LL_COMBO_BOX_TAG, this); + LLUICtrlCreator::registerCreator(LL_LINE_EDITOR_TAG, this); + LLUICtrlCreator::registerCreator(LL_SEARCH_EDITOR_TAG, this); + LLUICtrlCreator::registerCreator(LL_SCROLL_LIST_CTRL_TAG, this); + LLUICtrlCreator::registerCreator(LL_SLIDER_CTRL_TAG, this); + LLUICtrlCreator::registerCreator(LL_SLIDER_TAG, this); + LLUICtrlCreator::registerCreator(LL_SPIN_CTRL_TAG, this); + LLUICtrlCreator::registerCreator(LL_TEXT_BOX_TAG, this); + LLUICtrlCreator::registerCreator(LL_RADIO_GROUP_TAG, this); + LLUICtrlCreator::registerCreator(LL_ICON_CTRL_TAG, this); + LLUICtrlCreator::registerCreator(LL_UI_CTRL_LOCATE_TAG, this); + LLUICtrlCreator::registerCreator(LL_PAD_TAG, this); + LLUICtrlCreator::registerCreator(LL_VIEW_BORDER_TAG, this); + LLUICtrlCreator::registerCreator(LL_TAB_CONTAINER_COMMON_TAG, this); + LLUICtrlCreator::registerCreator(LL_SCROLLABLE_CONTAINER_VIEW_TAG, this); + LLUICtrlCreator::registerCreator(LL_PANEL_TAG, this); + LLUICtrlCreator::registerCreator(LL_MENU_GL_TAG, this); + LLUICtrlCreator::registerCreator(LL_MENU_BAR_GL_TAG, this); + LLUICtrlCreator::registerCreator(LL_SCROLLING_PANEL_LIST_TAG, this); + + + LLString filename = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, "paths.xml"); + + LLXMLNodePtr root; + BOOL success = LLXMLNode::parseFile(filename, root, NULL); + + if (!success) + { + LLString slash = gDirUtilp->getDirDelimiter(); + LLString dir = gDirUtilp->getAppRODataDir() + slash + "skins" + slash + "xui" + slash + "en-us" + slash; + llwarns << "XUI::config file unable to open." << llendl; + mXUIPaths.push_back(dir); + } + else + { + LLXMLNodePtr path; + LLString app_dir = gDirUtilp->getAppRODataDir(); + + for (path = root->getFirstChild(); path.notNull(); path = path->getNextSibling()) + { + LLUIString path_val_ui(path->getValue()); + LLString language = "en-us"; + if (LLUI::sConfigGroup) + { + language = LLUI::sConfigGroup->getString("Language"); + } + path_val_ui.setArg("[Language]", language); + LLString fullpath = app_dir + path_val_ui.getString(); + + if (mXUIPaths.empty() || (find(mXUIPaths.begin(), mXUIPaths.end(), fullpath) == mXUIPaths.end()) ) + { + mXUIPaths.push_back(app_dir + path_val_ui.getString()); + } + } + } + + +} + +//----------------------------------------------------------------------------- +// ~LLUICtrlFactory() +//----------------------------------------------------------------------------- +LLUICtrlFactory::~LLUICtrlFactory() +{ +} + + +//----------------------------------------------------------------------------- +// getLayeredXMLNode() +//----------------------------------------------------------------------------- +bool LLUICtrlFactory::getLayeredXMLNode(const LLString &filename, LLXMLNodePtr& root) +{ + + if (!LLXMLNode::parseFile(mXUIPaths.front() + filename, root, NULL)) + { + llwarns << "Problem reading UI description file: " << mXUIPaths.front() + filename << llendl; + return FALSE; + } + + LLXMLNodePtr updateRoot; + + std::vector::const_iterator itor; + + for (itor = mXUIPaths.begin(), ++itor; itor != mXUIPaths.end(); ++itor) + { + LLString nodeName; + LLString updateName; + + LLXMLNode::parseFile((*itor) + filename, updateRoot, NULL); + + updateRoot->getAttributeString("name", updateName); + root->getAttributeString("name", nodeName); + + if (updateName == nodeName) + { + LLXMLNode::updateNode(root, updateRoot); + } + } + + return TRUE; +} + + +//----------------------------------------------------------------------------- +// buildFloater() +//----------------------------------------------------------------------------- +void LLUICtrlFactory::buildFloater(LLFloater* floaterp, const LLString &filename, + const LLCallbackMap::map_t* factory_map, BOOL open) +{ + LLXMLNodePtr root; + + if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) + { + return; + } + + // root must be called floater + if( !(root->hasName("floater") || root->hasName("multi_floater") ) ) + { + llwarns << "Root node should be named floater in: " << filename << llendl; + return; + } + + if (factory_map) + { + mFactoryStack.push_front(factory_map); + } + + floaterp->initFloaterXML(root, NULL, this, open); + + if (LLUI::sShowXUINames) + { + floaterp->mToolTipMsg = filename; + } + + if (factory_map) + { + mFactoryStack.pop_front(); + } + + LLViewHandle handle = floaterp->getHandle(); + mBuiltFloaters[handle] = filename; +} + +//----------------------------------------------------------------------------- +// saveToXML() +//----------------------------------------------------------------------------- +S32 LLUICtrlFactory::saveToXML(LLView* viewp, const LLString& filename) +{ + llofstream out(filename.c_str()); + if (!out.good()) + { + llwarns << "Unable to open " << filename << " for output." << llendl; + return 1; + } + + out << XML_HEADER; + + LLXMLNodePtr xml_node = viewp->getXML(); + + xml_node->writeToOstream(out); + + out.close(); + return 0; +} + +//----------------------------------------------------------------------------- +// buildPanel() +//----------------------------------------------------------------------------- +void LLUICtrlFactory::buildPanel(LLPanel* panelp, const LLString &filename, + const LLCallbackMap::map_t* factory_map) +{ + LLXMLNodePtr root; + + if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) + { + return; + } + + // root must be called panel + if( !root->hasName("panel" ) ) + { + llwarns << "Root node should be named panel in : " << filename << llendl; + return; + } + + if (factory_map) + { + mFactoryStack.push_front(factory_map); + } + + panelp->initPanelXML(root, NULL, this); + + if (LLUI::sShowXUINames) + { + panelp->mToolTipMsg = filename; + } + + LLViewHandle handle = panelp->getHandle(); + mBuiltPanels[handle] = filename; + + if (factory_map) + { + mFactoryStack.pop_front(); + } +} + +//----------------------------------------------------------------------------- +// buildMenu() +//----------------------------------------------------------------------------- +LLMenuGL *LLUICtrlFactory::buildMenu(const LLString &filename, LLView* parentp) +{ + // TomY TODO: Break this function into buildMenu and buildMenuBar + LLXMLNodePtr root; + + if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) + { + return NULL; + } + + // root must be called panel + if( !root->hasName( "menu_bar" ) && !root->hasName( "menu" )) + { + llwarns << "Root node should be named menu bar or menu in : " << filename << llendl; + return NULL; + } + + if (root->hasName("menu")) + { + return (LLMenuGL*)LLMenuGL::fromXML(root, parentp, this); + } + + return (LLMenuGL*)LLMenuBarGL::fromXML(root, parentp, this); +} + +//----------------------------------------------------------------------------- +// buildMenu() +//----------------------------------------------------------------------------- +LLPieMenu *LLUICtrlFactory::buildPieMenu(const LLString &filename, LLView* parentp) +{ + + LLXMLNodePtr root; + + if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) + { + return NULL; + } + + // root must be called panel + if( !root->hasName( LL_PIE_MENU_TAG )) + { + llwarns << "Root node should be named " LL_PIE_MENU_TAG " in : " << filename << llendl; + return NULL; + } + + LLString name("menu"); + root->getAttributeString("name", name); + + LLPieMenu *menu = new LLPieMenu(name); + parentp->addChild(menu); + menu->initXML(root, parentp, this); + return menu; +} + +//----------------------------------------------------------------------------- +// removePanel() +//----------------------------------------------------------------------------- +void LLUICtrlFactory::removePanel(LLPanel* panelp) +{ + mBuiltPanels.erase(panelp->getHandle()); +} + +//----------------------------------------------------------------------------- +// removeFloater() +//----------------------------------------------------------------------------- +void LLUICtrlFactory::removeFloater(LLFloater* floaterp) +{ + mBuiltFloaters.erase(floaterp->getHandle()); +} + +//----------------------------------------------------------------------------- +// rebuild() +//----------------------------------------------------------------------------- +void LLUICtrlFactory::rebuild() +{ + built_panel_t::iterator built_panel_it; + for (built_panel_it = mBuiltPanels.begin(); + built_panel_it != mBuiltPanels.end(); + ++built_panel_it) + { + LLString filename = built_panel_it->second; + LLPanel* panelp = LLPanel::getPanelByHandle(built_panel_it->first); + if (!panelp) + { + continue; + } + llinfos << "Rebuilding UI panel " << panelp->getName() + << " from " << filename + << llendl; + BOOL visible = panelp->getVisible(); + panelp->setVisible(FALSE); + panelp->setFocus(FALSE); + panelp->deleteAllChildren(); + + buildPanel(panelp, filename.c_str(), &panelp->getFactoryMap()); + panelp->setVisible(visible); + } + + built_floater_t::iterator built_floater_it; + for (built_floater_it = mBuiltFloaters.begin(); + built_floater_it != mBuiltFloaters.end(); + ++built_floater_it) + { + LLFloater* floaterp = LLFloater::getFloaterByHandle(built_floater_it->first); + if (!floaterp) + { + continue; + } + LLString filename = built_floater_it->second; + llinfos << "Rebuilding UI floater " << floaterp->getName() + << " from " << filename + << llendl; + BOOL visible = floaterp->getVisible(); + floaterp->setVisible(FALSE); + floaterp->setFocus(FALSE); + floaterp->deleteAllChildren(); + + gFloaterView->removeChild(floaterp); + buildFloater(floaterp, filename, &floaterp->getFactoryMap()); + floaterp->setVisible(visible); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +// static +EWidgetType LLUICtrlFactory::getWidgetType(const LLString& ctrl_type) +{ + U32 ctrl_id; + for (ctrl_id = 0; ctrl_id < WIDGET_TYPE_COUNT; ctrl_id++) + { + if (sUICtrlNames[ctrl_id] == ctrl_type) + { + break; + } + } + return (EWidgetType) ctrl_id; +} + +LLString LLUICtrlFactory::getWidgetType(EWidgetType ctrl_type) +{ + return sUICtrlNames[ctrl_type]; +} + +LLView *LLUICtrlFactory::createCtrlWidget(LLPanel *parent, LLXMLNodePtr node) +{ + LLString ctrl_type = node->getName()->mString; + LLString::toLower(ctrl_type); + + creator_list_t::const_iterator it = mCreatorFunctions.find(ctrl_type); + if (it == mCreatorFunctions.end()) + { + llwarns << "Unknown control type " << ctrl_type << llendl; + return NULL; + } + + LLView *ctrl = (*it->second)(node, parent, this); + + return ctrl; +} + +void LLUICtrlFactory::createWidget(LLPanel *parent, LLXMLNodePtr node) +{ + LLView* view = createCtrlWidget(parent, node); + + S32 tab_group = parent->getLastTabGroup(); + node->getAttributeS32("tab_group", tab_group); + + if (view) + { + parent->addChild(view, tab_group); + } +} + +//----------------------------------------------------------------------------- +// createFactoryPanel() +//----------------------------------------------------------------------------- +LLPanel* LLUICtrlFactory::createFactoryPanel(LLString name) +{ + std::deque::iterator itor; + for (itor = mFactoryStack.begin(); itor != mFactoryStack.end(); ++itor) + { + const LLCallbackMap::map_t* factory_map = *itor; + + // Look up this panel's name in the map. + LLCallbackMap::map_const_iter_t iter = factory_map->find( name ); + if (iter != factory_map->end()) + { + // Use the factory to create the panel, instead of using a default LLPanel. + LLPanel *ret = (LLPanel*) iter->second.mCallback( iter->second.mData ); + return ret; + } + } + return NULL; +} + +//----------------------------------------------------------------------------- + +//static +BOOL LLUICtrlFactory::getAttributeColor(LLXMLNodePtr node, const LLString& name, LLColor4& color) +{ + LLString colorstring; + BOOL res = node->getAttributeString(name, colorstring); + if (res && LLUI::sColorsGroup) + { + if (LLUI::sColorsGroup->controlExists(colorstring)) + { + color.setVec(LLUI::sColorsGroup->getColor(colorstring)); + } + else + { + res = FALSE; + } + } + if (!res) + { + res = LLColor4::parseColor(colorstring.c_str(), &color); + } + if (!res) + { + res = node->getAttributeColor(name, color); + } + return res; +} + +//============================================================================ + +LLButton* LLUICtrlFactory::getButtonByName(LLPanel* panelp, const LLString& name) +{ + return (LLButton*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_BUTTON); +} + +LLCheckBoxCtrl* LLUICtrlFactory::getCheckBoxByName(LLPanel* panelp, const LLString& name) +{ + return (LLCheckBoxCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_CHECKBOX); +} + +LLComboBox* LLUICtrlFactory::getComboBoxByName(LLPanel* panelp, const LLString& name) +{ + return (LLComboBox*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_COMBO_BOX); +} + +LLIconCtrl* LLUICtrlFactory::getIconByName(LLPanel* panelp, const LLString& name) +{ + return (LLIconCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_ICON); +} + +LLLineEditor* LLUICtrlFactory::getLineEditorByName(LLPanel* panelp, const LLString& name) +{ + return (LLLineEditor*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_LINE_EDITOR); +} + +LLNameListCtrl* LLUICtrlFactory::getNameListByName(LLPanel* panelp, const LLString& name) +{ + return (LLNameListCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_NAME_LIST); +} + +LLRadioGroup* LLUICtrlFactory::getRadioGroupByName(LLPanel* panelp, const LLString& name) +{ + return (LLRadioGroup*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_RADIO_GROUP); +} + +LLScrollListCtrl* LLUICtrlFactory::getScrollListByName(LLPanel* panelp, const LLString& name) +{ + return (LLScrollListCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_SCROLL_LIST); +} + +LLSliderCtrl* LLUICtrlFactory::getSliderByName(LLPanel* panelp, const LLString& name) +{ + return (LLSliderCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_SLIDER); +} + +LLSlider* LLUICtrlFactory::getSliderBarByName(LLPanel* panelp, const LLString& name) +{ + return (LLSlider*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_SLIDER_BAR); +} + +LLSpinCtrl* LLUICtrlFactory::getSpinnerByName(LLPanel* panelp, const LLString& name) +{ + return (LLSpinCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_SPINNER); +} + +LLTextBox* LLUICtrlFactory::getTextBoxByName(LLPanel* panelp, const LLString& name) +{ + return (LLTextBox*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_TEXT_BOX); +} + +LLTextEditor* LLUICtrlFactory::getTextEditorByName(LLPanel* panelp, const LLString& name) +{ + return (LLTextEditor*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_TEXT_EDITOR); +} + +LLTabContainerCommon* LLUICtrlFactory::getTabContainerByName(LLPanel* panelp, const LLString& name) +{ + return (LLTabContainerCommon*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_TAB_CONTAINER); +} + +LLScrollableContainerView* LLUICtrlFactory::getScrollableContainerByName(LLPanel* panelp, const LLString& name) +{ + return (LLScrollableContainerView*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_SCROLL_CONTAINER); +} + +LLTextureCtrl* LLUICtrlFactory::getTexturePickerByName(LLPanel* panelp, const LLString& name) +{ + return (LLTextureCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_TEXTURE_PICKER); +} + +LLPanel* LLUICtrlFactory::getPanelByName(LLPanel* panelp, const LLString& name) +{ + return (LLPanel*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_PANEL); +} + +LLColorSwatchCtrl* LLUICtrlFactory::getColorSwatchByName(LLPanel* panelp, const LLString& name) +{ + return (LLColorSwatchCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_COLOR_SWATCH); +} + +LLWebBrowserCtrl* LLUICtrlFactory::getWebBrowserCtrlByName(LLPanel* panelp, const LLString& name) +{ + return (LLWebBrowserCtrl*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_WEBBROWSER); +} + +LLMenuItemCallGL* LLUICtrlFactory::getMenuItemCallByName(LLPanel* panelp, const LLString& name) +{ + return (LLMenuItemCallGL*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_MENU_ITEM_CALL); +} + +LLScrollingPanelList* LLUICtrlFactory::getScrollingPanelList(LLPanel* panelp, const LLString& name) +{ + return (LLScrollingPanelList*)panelp->getCtrlByNameAndType(name, WIDGET_TYPE_SCROLLING_PANEL_LIST); +} + +void LLUICtrlFactory::registerCreator(LLString ctrlname, creator_function_t function) +{ + LLString::toLower(ctrlname); + mCreatorFunctions[ctrlname] = function; +} + diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h new file mode 100644 index 0000000000..b3bd5c9020 --- /dev/null +++ b/indra/llui/lluictrlfactory.h @@ -0,0 +1,142 @@ +/** + * @file lluictrlfactory.h + * @brief Factory class for creating UI controls + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LLUICTRLFACTORY_H +#define LLUICTRLFACTORY_H + +#include +#include + +#include "llcallbackmap.h" +#include "llfloater.h" + +class LLControlGroup; +class LLView; +class LLFontGL; + +class LLFloater; +class LLPanel; +class LLButton; +class LLCheckBoxCtrl; +class LLComboBox; +class LLIconCtrl; +class LLLineEditor; +class LLMenuGL; +class LLMenuBarGL; +class LLMenuItemCallGL; +class LLNameListCtrl; +class LLPieMenu; +class LLRadioGroup; +class LLSearchEditor; +class LLScrollableContainerView; +class LLScrollListCtrl; +class LLSlider; +class LLSliderCtrl; +class LLSpinCtrl; +class LLTextBox; +class LLTextEditor; +class LLTextureCtrl; +class LLWebBrowserCtrl; +class LLViewBorder; +class LLColorSwatchCtrl; +class LLScrollingPanelList; + +// Widget + +class LLUICtrlFactory +{ +public: + LLUICtrlFactory(); + // do not call! needs to be public so run-time can clean up the singleton + virtual ~LLUICtrlFactory(); + + void buildFloater(LLFloater* floaterp, const LLString &filename, + const LLCallbackMap::map_t* factory_map = NULL, BOOL open = TRUE); + + void buildPanel(LLPanel* panelp, const LLString &filename, + const LLCallbackMap::map_t* factory_map = NULL); + + LLMenuGL *buildMenu(const LLString &filename, LLView* parentp); + + LLPieMenu *buildPieMenu(const LLString &filename, LLView* parentp); + + // Does what you want for LLFloaters and LLPanels + // Returns 0 on success + S32 saveToXML(LLView* viewp, const LLString& filename); + + void removePanel(LLPanel* panelp); + void removeFloater(LLFloater* floaterp); + + void rebuild(); + + static EWidgetType getWidgetType(const LLString& ctrl_type); + static LLString getWidgetType(EWidgetType ctrl_type); + static BOOL getAttributeColor(LLXMLNodePtr node, const LLString& name, LLColor4& color); + + // specific typed getters + static LLButton* getButtonByName( LLPanel* panelp, const LLString& name); + static LLCheckBoxCtrl* getCheckBoxByName( LLPanel* panelp, const LLString& name); + static LLComboBox* getComboBoxByName( LLPanel* panelp, const LLString& name); + static LLIconCtrl* getIconByName( LLPanel* panelp, const LLString& name); + static LLLineEditor* getLineEditorByName( LLPanel* panelp, const LLString& name); + static LLNameListCtrl* getNameListByName( LLPanel* panelp, const LLString& name); + static LLRadioGroup* getRadioGroupByName( LLPanel* panelp, const LLString& name); + static LLScrollListCtrl* getScrollListByName( LLPanel* panelp, const LLString& name); + static LLSliderCtrl* getSliderByName( LLPanel* panelp, const LLString& name); + static LLSlider* getSliderBarByName( LLPanel* panelp, const LLString& name); + static LLSpinCtrl* getSpinnerByName( LLPanel* panelp, const LLString& name); + static LLTextBox* getTextBoxByName( LLPanel* panelp, const LLString& name); + static LLTextEditor* getTextEditorByName( LLPanel* panelp, const LLString& name); + static LLTabContainerCommon* getTabContainerByName( LLPanel* panelp, const LLString& name); + static LLScrollableContainerView* getScrollableContainerByName(LLPanel* panelp, const LLString& name); + static LLTextureCtrl* getTexturePickerByName( LLPanel* panelp, const LLString& name); + static LLPanel* getPanelByName(LLPanel* panelp, const LLString& name); + static LLColorSwatchCtrl* getColorSwatchByName(LLPanel* panelp, const LLString& name); + static LLWebBrowserCtrl* getWebBrowserCtrlByName(LLPanel* panelp, const LLString& name); + static LLMenuItemCallGL* getMenuItemCallByName(LLPanel* panelp, const LLString& name); + static LLScrollingPanelList* getScrollingPanelList(LLPanel* panelp, const LLString& name); + + LLPanel* createFactoryPanel(LLString name); + + virtual LLView* createCtrlWidget(LLPanel *parent, LLXMLNodePtr node); + virtual void createWidget(LLPanel *parent, LLXMLNodePtr node); + + // Creator library + typedef LLView* (*creator_function_t)(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + void registerCreator(LLString ctrlname, creator_function_t function); + + static bool getLayeredXMLNode(const LLString &filename, LLXMLNodePtr& root); + +protected: + + + typedef std::map built_panel_t; + built_panel_t mBuiltPanels; + typedef std::map built_floater_t; + built_floater_t mBuiltFloaters; + + std::deque mFactoryStack; + + static const LLString sUICtrlNames[]; + + typedef std::map creator_list_t; + creator_list_t mCreatorFunctions; + static std::vector mXUIPaths; +}; + +template +class LLUICtrlCreator +{ +public: + static void registerCreator(LLString name, LLUICtrlFactory *factory) + { + factory->registerCreator(name, T::fromXML); + } +}; + +#endif //LL_LLWIDGETFACTORY_H diff --git a/indra/llui/lluistring.cpp b/indra/llui/lluistring.cpp new file mode 100755 index 0000000000..8c5b587158 --- /dev/null +++ b/indra/llui/lluistring.cpp @@ -0,0 +1,87 @@ +/** + * @file lluistring.cpp + * @brief LLUIString base class + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lluistring.h" + +// public + +LLUIString::LLUIString(const LLString& instring, const LLString::format_map_t& args) + : mOrig(instring), + mArgs(args) +{ + format(); +} + +void LLUIString::assign(const LLString& s) +{ + mOrig = s; + format(); +} + +void LLUIString::setArgList(const LLString::format_map_t& args) +{ + mArgs = args; + format(); +} + +void LLUIString::setArg(const LLString& key, const LLString& replacement) +{ + mArgs[key] = replacement; + format(); +} + +void LLUIString::truncate(S32 maxchars) +{ + if (mWResult.size() > (size_t)maxchars) + { + LLWString::truncate(mWResult, maxchars); + mResult = wstring_to_utf8str(mWResult); + } +} + +void LLUIString::erase(S32 charidx, S32 len) +{ + mWResult.erase(charidx, len); + mResult = wstring_to_utf8str(mWResult); +} + +void LLUIString::insert(S32 charidx, const LLWString& wchars) +{ + mWResult.insert(charidx, wchars); + mResult = wstring_to_utf8str(mWResult); +} + +void LLUIString::replace(S32 charidx, llwchar wc) +{ + mWResult[charidx] = wc; + mResult = wstring_to_utf8str(mWResult); +} + +void LLUIString::clear() +{ + // Keep Args + mOrig.clear(); + mResult.clear(); + mWResult.clear(); +} + +void LLUIString::clearArgs() +{ + mArgs.clear(); +} + +// private + +void LLUIString::format() +{ + mResult = mOrig; + LLString::format(mResult, mArgs); + mWResult = utf8str_to_wstring(mResult); +} diff --git a/indra/llui/lluistring.h b/indra/llui/lluistring.h new file mode 100755 index 0000000000..8c2e3c481c --- /dev/null +++ b/indra/llui/lluistring.h @@ -0,0 +1,87 @@ +/** + * @file lluistring.h + * @author: Steve Bennetts + * @brief LLUIString base class + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLUISTRING_H +#define LL_LLUISTRING_H + +// lluistring.h +// +// Copyright 2006, Linden Research, Inc. +// Original aurthor: Steve + +#include "stdtypes.h" +#include "llstring.h" +#include + +// Use this class to store translated text that may have arguments +// e.g. "Welcome [USERNAME] to [SECONDLIFE]!" + +// Adding or changing an argument will update the result string, preserving the origianl +// Thus, subsequent changes to arguments or even the original string will produce +// the correct result + +// Example Usage: +// LLUIString mMessage("Welcome [USERNAME] to [SECONDLIFE]!"); +// mMessage.setArg("[USERNAME]", "Steve"); +// mMessage.setArg("[SECONDLIFE]", "Second Life"); +// llinfos << mMessage.getString().c_str() << llendl; // outputs "Welcome Steve to Second Life" +// mMessage.setArg("[USERNAME]", "Joe"); +// llinfos << mMessage.getString().c_str() << llendl; // outputs "Welcome Joe to Second Life" +// mMessage = "Recepción a la [SECONDLIFE] [USERNAME]" +// mMessage.setArg("[SECONDLIFE]", "Segunda Vida"); +// llinfos << mMessage.getString().c_str() << llendl; // outputs "Recepción a la Segunda Vida Joe" + +// Implementation Notes: +// Attempting to have operator[](const LLString& s) return mArgs[s] fails because we have +// to call format() after the assignment happens. + +class LLUIString +{ +public: + // These methods all perform appropriate argument substitution + // and modify mOrig where appropriate + LLUIString() {} + LLUIString(const LLString& instring, const LLString::format_map_t& args); + LLUIString(const LLString& instring) { assign(instring); } + + void assign(const LLString& instring); + LLUIString& operator=(const LLString& s) { assign(s); return *this; } + + void setArgList(const LLString::format_map_t& args); + void setArg(const LLString& key, const LLString& replacement); + + const LLString& getString() const { return mResult; } + operator LLString() const { return mResult; } + + const LLWString& getWString() const { return mWResult; } + operator LLWString() const { return mWResult; } + + bool empty() const { return mWResult.empty(); } + S32 length() const { return mWResult.size(); } + + void clear(); + void clearArgs(); + + // These utuilty functions are included for text editing. + // They do not affect mOrig and do not perform argument substitution + void truncate(S32 maxchars); + void erase(S32 charidx, S32 len); + void insert(S32 charidx, const LLWString& wchars); + void replace(S32 charidx, llwchar wc); + +private: + void format(); + + LLString mOrig; + LLString mResult; + LLWString mWResult; // for displaying + LLString::format_map_t mArgs; +}; + +#endif // LL_LLUISTRING_H diff --git a/indra/llui/llundo.cpp b/indra/llui/llundo.cpp new file mode 100644 index 0000000000..61e17c1879 --- /dev/null +++ b/indra/llui/llundo.cpp @@ -0,0 +1,161 @@ +/** + * @file llundo.cpp + * @brief LLUndo class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// Generic interface for undo/redo circular buffer + +#include "linden_common.h" + +#include "llundo.h" +#include "llerror.h" + + +// TODO: +// implement doubly linked circular list for ring buffer +// this will allow us to easily change the size of an undo buffer on the fly + +//----------------------------------------------------------------------------- +// LLUndoBuffer() +//----------------------------------------------------------------------------- +LLUndoBuffer::LLUndoBuffer( LLUndoAction (*create_func()), S32 initial_count ) +{ + mNextAction = 0; + mLastAction = 0; + mFirstAction = 0; + mOperationID = 0; + + mNumActions = initial_count; + + mActions = new LLUndoAction *[initial_count]; + + //initialize buffer with actions + for (S32 i = 0; i < initial_count; i++) + { + mActions[i] = create_func(); + if (!mActions[i]) + { + llerrs << "Unable to create action for undo buffer" << llendl; + } + } +} + +//----------------------------------------------------------------------------- +// ~LLUndoBuffer() +//----------------------------------------------------------------------------- +LLUndoBuffer::~LLUndoBuffer() +{ + for (S32 i = 0; i < mNumActions; i++) + { + delete mActions[i]; + } + + delete [] mActions; +} + +//----------------------------------------------------------------------------- +// getNextAction() +//----------------------------------------------------------------------------- +LLUndoAction *LLUndoBuffer::getNextAction(BOOL setClusterBegin) +{ + LLUndoAction *nextAction = mActions[mNextAction]; + + if (setClusterBegin) + { + mOperationID++; + } + mActions[mNextAction]->mClusterID = mOperationID; + + mNextAction = (mNextAction + 1) % mNumActions; + mLastAction = mNextAction; + + if (mNextAction == mFirstAction) + { + mActions[mFirstAction]->cleanup(); + mFirstAction = (mFirstAction + 1) % mNumActions; + } + + return nextAction; +} + +//----------------------------------------------------------------------------- +// undoAction() +//----------------------------------------------------------------------------- +BOOL LLUndoBuffer::undoAction() +{ + if (!canUndo()) + { + return FALSE; + } + + S32 prevAction = (mNextAction + mNumActions - 1) % mNumActions; + + while(mActions[prevAction]->mClusterID == mOperationID) + { + // go ahead and decrement action index + mNextAction = prevAction; + + // undo this action + mActions[mNextAction]->undo(); + + // we're at the first action, so we don't know if we've actually undid everything + if (mNextAction == mFirstAction) + { + mOperationID--; + return FALSE; + } + + // do wrap-around of index, but avoid negative numbers for modulo operator + prevAction = (mNextAction + mNumActions - 1) % mNumActions; + } + + mOperationID--; + + return TRUE; +} + +//----------------------------------------------------------------------------- +// redoAction() +//----------------------------------------------------------------------------- +BOOL LLUndoBuffer::redoAction() +{ + if (!canRedo()) + { + return FALSE; + } + + mOperationID++; + + while(mActions[mNextAction]->mClusterID == mOperationID) + { + if (mNextAction == mLastAction) + { + return FALSE; + } + + mActions[mNextAction]->redo(); + + // do wrap-around of index + mNextAction = (mNextAction + 1) % mNumActions; + } + + return TRUE; +} + +//----------------------------------------------------------------------------- +// flushActions() +//----------------------------------------------------------------------------- +void LLUndoBuffer::flushActions() +{ + for (S32 i = 0; i < mNumActions; i++) + { + mActions[i]->cleanup(); + } + mNextAction = 0; + mLastAction = 0; + mFirstAction = 0; + mOperationID = 0; +} diff --git a/indra/llui/llundo.h b/indra/llui/llundo.h new file mode 100644 index 0000000000..8d0428ad3e --- /dev/null +++ b/indra/llui/llundo.h @@ -0,0 +1,52 @@ +/** + * @file llundo.h + * @brief LLUndo class header file + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLUNDO_H +#define LL_LLUNDO_H + +class LLUndoAction +{ + friend class LLUndoBuffer; +protected: + S32 mClusterID; +protected: + LLUndoAction(): mClusterID(0) {}; + virtual ~LLUndoAction(){}; + +public: + static LLUndoAction *create() { return NULL; } + + virtual void undo() = 0; + virtual void redo() = 0; + virtual void cleanup() {}; +}; + +class LLUndoBuffer +{ +protected: + LLUndoAction **mActions; // array of pointers to undoactions + S32 mNumActions; // total number of actions in ring buffer + S32 mNextAction; // next action to perform undo/redo on + S32 mLastAction; // last action actually added to undo buffer + S32 mFirstAction; // beginning of ring buffer (don't undo any further) + S32 mOperationID; // current operation id, for undoing and redoing in clusters + +public: + LLUndoBuffer( LLUndoAction (*create_func()), S32 initial_count ); + virtual ~LLUndoBuffer(); + + LLUndoAction *getNextAction(BOOL setClusterBegin = TRUE); + BOOL undoAction(); + BOOL redoAction(); + BOOL canUndo() { return (mNextAction != mFirstAction); } + BOOL canRedo() { return (mNextAction != mLastAction); } + + void flushActions(); +}; + +#endif //LL_LLUNDO_H diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp new file mode 100644 index 0000000000..f8d1504f3c --- /dev/null +++ b/indra/llui/llview.cpp @@ -0,0 +1,2924 @@ +/** + * @file llview.cpp + * @author James Cook + * @brief Container for other views, anything that draws. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llview.h" + +#include "llstring.h" +#include "llrect.h" +#include "llgl.h" +#include "llevent.h" +#include "llfontgl.h" +#include "llfocusmgr.h" +#include "llglheaders.h" +#include "llwindow.h" +#include "llstl.h" +#include "lluictrl.h" +#include "llui.h" // colors saved settings +#include "v3color.h" +#include "llstl.h" + +#include + +#include + +BOOL LLView::sDebugRects = FALSE; +BOOL LLView::sDebugKeys = FALSE; +S32 LLView::sDepth = 0; +LLView* LLView::sFastFrameView = NULL; +BOOL LLView::sDebugMouseHandling = FALSE; +LLString LLView::sMouseHandlerMessage; +S32 LLView::sSelectID = GL_NAME_UI_RESERVED; +BOOL LLView::sEditingUI = FALSE; +BOOL LLView::sForceReshape = FALSE; +LLView* LLView::sEditingUIView = NULL; +S32 LLView::sLastLeftXML = S32_MIN; +S32 LLView::sLastBottomXML = S32_MIN; +std::map LLView::sViewHandleMap; + +S32 LLViewHandle::sNextID = 0; +LLViewHandle LLViewHandle::sDeadHandle; + +#if LL_DEBUG +BOOL LLView::sIsDrawing = FALSE; +#endif + +//static +LLView* LLView::getViewByHandle(LLViewHandle handle) +{ + if (handle == LLViewHandle::sDeadHandle) + { + return NULL; + } + std::map::iterator iter = sViewHandleMap.find(handle); + if (iter != sViewHandleMap.end()) + { + return iter->second; + } + else + { + return NULL; + } +} + +//static +BOOL LLView::deleteViewByHandle(LLViewHandle handle) +{ + std::map::iterator iter = sViewHandleMap.find(handle); + if (iter != sViewHandleMap.end()) + { + delete iter->second; // will remove from map + return TRUE; + } + else + { + return FALSE; + } +} + +LLView::LLView() : + mParentView(NULL), + mReshapeFlags(FOLLOWS_NONE), + mDefaultTabGroup(0), + mEnabled(TRUE), + mMouseOpaque(TRUE), + mSoundFlags(MOUSE_UP), // default to only make sound on mouse up + mSaveToXML(TRUE), + mIsFocusRoot(FALSE), + mLastVisible(TRUE), + mRenderInFastFrame(TRUE), + mSpanChildren(FALSE), + mVisible(TRUE), + mHidden(FALSE), + mNextInsertionOrdinal(0) +{ + mViewHandle.init(); + sViewHandleMap[mViewHandle] = this; +} + +LLView::LLView(const LLString& name, BOOL mouse_opaque) : + mParentView(NULL), + mName(name), + mReshapeFlags(FOLLOWS_NONE), + mDefaultTabGroup(0), + mEnabled(TRUE), + mMouseOpaque(mouse_opaque), + mSoundFlags(MOUSE_UP), // default to only make sound on mouse up + mSaveToXML(TRUE), + mIsFocusRoot(FALSE), + mLastVisible(TRUE), + mRenderInFastFrame(TRUE), + mSpanChildren(FALSE), + mVisible(TRUE), + mHidden(FALSE), + mNextInsertionOrdinal(0) +{ + mViewHandle.init(); + sViewHandleMap[mViewHandle] = this; +} + + +LLView::LLView( + const LLString& name, const LLRect& rect, BOOL mouse_opaque, U32 reshape) : + mParentView(NULL), + mName(name), + mRect(rect), + mReshapeFlags(reshape), + mDefaultTabGroup(0), + mEnabled(TRUE), + mMouseOpaque(mouse_opaque), + mSoundFlags(MOUSE_UP), // default to only make sound on mouse up + mSaveToXML(TRUE), + mIsFocusRoot(FALSE), + mLastVisible(TRUE), + mRenderInFastFrame(TRUE), + mSpanChildren(FALSE), + mVisible(TRUE), + mHidden(FALSE), + mNextInsertionOrdinal(0) +{ + mViewHandle.init(); + sViewHandleMap[mViewHandle] = this; +} + + +LLView::~LLView() +{ + //llinfos << "Deleting view " << mName << ":" << (void*) this << llendl; +// llassert(LLView::sIsDrawing == FALSE); + if( gFocusMgr.getKeyboardFocus() == this ) + { + llwarns << "View holding keyboard focus deleted: " << getName() << ". Keyboard focus removed." << llendl; + gFocusMgr.removeKeyboardFocusWithoutCallback( this ); + } + + if( gFocusMgr.getMouseCapture() == this ) + { + llwarns << "View holding mouse capture deleted: " << getName() << ". Mouse capture removed." << llendl; + gFocusMgr.removeMouseCaptureWithoutCallback( this ); + } + + if( gFocusMgr.getTopView() == this ) + { + llwarns << "View holding top view deleted: " << getName() << ". Top view removed." << llendl; + gFocusMgr.removeTopViewWithoutCallback( this ); + } + + sViewHandleMap.erase(mViewHandle); + + deleteAllChildren(); + + if (mParentView != NULL) + { + mParentView->removeChild(this); + } + + if(LLView::sFastFrameView == this) + { + LLView::sFastFrameView = NULL; + } + + dispatch_list_t::iterator itor; + for (itor = mDispatchList.begin(); itor != mDispatchList.end(); ++itor) + { + (*itor).second->clearDispatchers(); + delete (*itor).second; + } +} + +// virtual +BOOL LLView::isView() +{ + return TRUE; +} + +// virtual +BOOL LLView::isCtrl() const +{ + return FALSE; +} + +// virtual +BOOL LLView::isPanel() +{ + return FALSE; +} + +void LLView::setMouseOpaque(BOOL b) +{ + mMouseOpaque = b; +} + +void LLView::setToolTip(const LLString& msg) +{ + mToolTipMsg = msg; +} + +// virtual +void LLView::setRect(const LLRect& rect) +{ + mRect = rect; +} + + +void LLView::setFollows(U32 flags) +{ + mReshapeFlags = flags; +} + +void LLView::setFollowsNone() +{ + mReshapeFlags = FOLLOWS_NONE; +} + +void LLView::setFollowsLeft() +{ + mReshapeFlags |= FOLLOWS_LEFT; +} + +void LLView::setFollowsTop() +{ + mReshapeFlags |= FOLLOWS_TOP; +} + +void LLView::setFollowsRight() +{ + mReshapeFlags |= FOLLOWS_RIGHT; +} + +void LLView::setFollowsBottom() +{ + mReshapeFlags |= FOLLOWS_BOTTOM; +} + +void LLView::setFollowsAll() +{ + mReshapeFlags |= FOLLOWS_ALL; +} + +void LLView::setSoundFlags(U8 flags) +{ + mSoundFlags = flags; +} + +void LLView::setName(LLString name) +{ + mName = name; +} + +void LLView::setSpanChildren( BOOL span_children ) +{ + mSpanChildren = span_children; updateRect(); +} + +const LLString& LLView::getToolTip() +{ + return mToolTipMsg; +} + +// virtual +const LLString& LLView::getName() const +{ + static const LLString unnamed("(no name)"); + return mName.empty() ? unnamed : mName; +} + +void LLView::sendChildToFront(LLView* child) +{ + if (child->mParentView == this) + { + mChildList.remove( child ); + mChildList.push_front(child); + } +} + +void LLView::sendChildToBack(LLView* child) +{ + if (child->mParentView == this) + { + mChildList.remove( child ); + mChildList.push_back(child); + } +} + +void LLView::moveChildToFrontOfTabGroup(LLUICtrl* child) +{ + if(mCtrlOrder.find(child) != mCtrlOrder.end()) + { + mCtrlOrder[child].second = -1 * mNextInsertionOrdinal++; + } +} + +void LLView::addChild(LLView* child, S32 tab_group) +{ + // remove from current parent + if (child->mParentView) + { + child->mParentView->removeChild(child); + } + + // add to front of child list, as normal + mChildList.push_front(child); + + // add to ctrl list if is LLUICtrl + if (child->isCtrl()) + { + // controls are stored in reverse order from render order + addCtrlAtEnd((LLUICtrl*) child, tab_group); + } + + child->mParentView = this; + updateRect(); +} + + +void LLView::addChildAtEnd(LLView* child, S32 tab_group) +{ + // remove from current parent + if (child->mParentView) + { + child->mParentView->removeChild(child); + } + + // add to back of child list + mChildList.push_back(child); + + // add to ctrl list if is LLUICtrl + if (child->isCtrl()) + { + // controls are stored in reverse order from render order + addCtrl((LLUICtrl*) child, tab_group); + } + + child->mParentView = this; + updateRect(); +} + +// remove the specified child from the view, and set it's parent to NULL. +void LLView::removeChild( LLView* child ) +{ + if (child->mParentView == this) + { + mChildList.remove( child ); + child->mParentView = NULL; + } + else + { + llerrs << "LLView::removeChild called with non-child" << llendl; + } + + if (child->isCtrl()) + { + removeCtrl((LLUICtrl*)child); + } +} + +void LLView::addCtrlAtEnd(LLUICtrl* ctrl, S32 tab_group) +{ + mCtrlOrder.insert(tab_order_pair_t(ctrl, + tab_order_t(tab_group, mNextInsertionOrdinal++))); +} + +void LLView::addCtrl( LLUICtrl* ctrl, S32 tab_group) +{ + // add to front of list by using negative ordinal, which monotonically increases + mCtrlOrder.insert(tab_order_pair_t(ctrl, + tab_order_t(tab_group, -1 * mNextInsertionOrdinal++))); +} + +void LLView::removeCtrl(LLUICtrl* ctrl) +{ + child_tab_order_t::iterator found = mCtrlOrder.find(ctrl); + if(found != mCtrlOrder.end()) + { + mCtrlOrder.erase(found); + } +} + +S32 LLView::getDefaultTabGroup() const { return mDefaultTabGroup; } + +LLView::ctrl_list_t LLView::getCtrlList() const +{ + ctrl_list_t controls; + for(child_list_const_iter_t iter = mChildList.begin(); + iter != mChildList.end(); + iter++) + { + if((*iter)->isCtrl()) + { + controls.push_back(static_cast(*iter)); + } + } + return controls; +} + +LLView::ctrl_list_t LLView::getCtrlListSorted() const +{ + ctrl_list_t controls = getCtrlList(); + std::sort(controls.begin(), controls.end(), LLCompareByTabOrder(mCtrlOrder)); + return controls; +} + +LLCompareByTabOrder::LLCompareByTabOrder(LLView::child_tab_order_t order): mTabOrder(order) {} + +bool LLCompareByTabOrder::compareTabOrders(const LLView::tab_order_t & a, const LLView::tab_order_t & b) const +{ + return a < b; +} + +// This method compares two LLViews by the tab order specified in the comparator object. The +// code for this is a little convoluted because each argument can have four states: +// 1) not a control, 2) a control but not in the tab order, 3) a control in the tab order, 4) null +bool LLCompareByTabOrder::operator() (const LLView* const a, const LLView* const b) const +{ + S32 a_score = 0, b_score = 0; + if(a) a_score--; + if(b) b_score--; + if(a && a->isCtrl()) a_score--; + if(b && b->isCtrl()) b_score--; + if(a_score == -2 && b_score == -2) + { + const LLUICtrl * const a_ctrl = static_cast(a); + const LLUICtrl * const b_ctrl = static_cast(b); + LLView::child_tab_order_const_iter_t a_found = mTabOrder.find(a_ctrl), b_found = mTabOrder.find(b_ctrl); + if(a_found != mTabOrder.end()) a_score--; + if(b_found != mTabOrder.end()) b_score--; + if(a_score == -3 && b_score == -3) + { + // whew! Once we're in here, they're both in the tab order, and we can compare based on that + return compareTabOrders(a_found->second, b_found->second); + } + } + return (a_score == b_score) ? a < b : a_score < b_score; +} + +BOOL LLView::isInVisibleChain() const +{ + const LLView* cur_view = this; + while(cur_view) + { + if (!cur_view->getVisible()) + { + return FALSE; + } + cur_view = cur_view->getParent(); + } + return TRUE; +} + +BOOL LLView::isInEnabledChain() const +{ + const LLView* cur_view = this; + while(cur_view) + { + if (!cur_view->getEnabled()) + { + return FALSE; + } + cur_view = cur_view->getParent(); + } + return TRUE; +} + +BOOL LLView::isFocusRoot() const +{ + return mIsFocusRoot; +} + +LLView* LLView::findRootMostFocusRoot() +{ + LLView* focus_root = NULL; + LLView* next_view = this; + while(next_view) + { + if (next_view->isFocusRoot()) + { + focus_root = next_view; + } + next_view = next_view->getParent(); + } + return focus_root; +} + +BOOL LLView::canFocusChildren() const +{ + return TRUE; +} + +BOOL LLView::focusNextRoot() +{ + LLView::child_list_t result = LLView::getFocusRootsQuery().run(this); + return LLView::focusNext(result); +} + +BOOL LLView::focusPrevRoot() +{ + LLView::child_list_t result = LLView::getFocusRootsQuery().run(this); + return LLView::focusPrev(result); +} + +BOOL LLView::focusNextItem(BOOL text_fields_only) +{ + // this assumes that this method is called on the focus root. + LLCtrlQuery query = LLView::getTabOrderQuery(); + if(text_fields_only || LLUI::sConfigGroup->getBOOL("TabToTextFieldsOnly")) + { + query.addPreFilter(LLUICtrl::LLTextInputFilter::getInstance()); + } + LLView::child_list_t result = query(this); + return LLView::focusNext(result); +} + +BOOL LLView::focusPrevItem(BOOL text_fields_only) +{ + // this assumes that this method is called on the focus root. + LLCtrlQuery query = LLView::getTabOrderQuery(); + if(text_fields_only || LLUI::sConfigGroup->getBOOL("TabToTextFieldsOnly")) + { + query.addPreFilter(LLUICtrl::LLTextInputFilter::getInstance()); + } + LLView::child_list_t result = query(this); + return LLView::focusPrev(result); +} + + +// static +BOOL LLView::focusNext(LLView::child_list_t & result) +{ + LLView::child_list_iter_t focused = result.end(); + for(LLView::child_list_iter_t iter = result.begin(); + iter != result.end(); + ++iter) + { + if(gFocusMgr.childHasKeyboardFocus(*iter)) + { + focused = iter; + break; + } + } + LLView::child_list_iter_t next = focused; + next = (next == result.end()) ? result.begin() : ++next; + while(next != focused) + { + // wrap around to beginning if necessary + if(next == result.end()) + { + next = result.begin(); + } + if((*next)->isCtrl()) + { + LLUICtrl * ctrl = static_cast(*next); + ctrl->setFocus(TRUE); + ctrl->onTabInto(); + gFocusMgr.triggerFocusFlash(); + return TRUE; + } + ++next; + } + return FALSE; +} + +// static +BOOL LLView::focusPrev(LLView::child_list_t & result) +{ + LLView::child_list_reverse_iter_t focused = result.rend(); + for(LLView::child_list_reverse_iter_t iter = result.rbegin(); + iter != result.rend(); + ++iter) + { + if(gFocusMgr.childHasKeyboardFocus(*iter)) + { + focused = iter; + break; + } + } + LLView::child_list_reverse_iter_t next = focused; + next = (next == result.rend()) ? result.rbegin() : ++next; + while(next != focused) + { + // wrap around to beginning if necessary + if(next == result.rend()) + { + next = result.rbegin(); + } + if((*next)->isCtrl()) + { + LLUICtrl * ctrl = static_cast(*next); + if (!ctrl->hasFocus()) + { + ctrl->setFocus(TRUE); + ctrl->onTabInto(); + gFocusMgr.triggerFocusFlash(); + } + return TRUE; + } + ++next; + } + return FALSE; +} + +BOOL LLView::focusFirstItem(BOOL prefer_text_fields) +{ + // search for text field first + if(prefer_text_fields) + { + LLCtrlQuery query = LLView::getTabOrderQuery(); + query.addPreFilter(LLUICtrl::LLTextInputFilter::getInstance()); + LLView::child_list_t result = query(this); + if(result.size() > 0) + { + LLUICtrl * ctrl = static_cast(result.front()); + if(!ctrl->hasFocus()) + { + ctrl->setFocus(TRUE); + ctrl->onTabInto(); + gFocusMgr.triggerFocusFlash(); + } + return TRUE; + } + } + // no text field found, or we don't care about text fields + LLView::child_list_t result = LLView::getTabOrderQuery().run(this); + if(result.size() > 0) + { + LLUICtrl * ctrl = static_cast(result.front()); + if(!ctrl->hasFocus()) + { + ctrl->setFocus(TRUE); + ctrl->onTabInto(); + gFocusMgr.triggerFocusFlash(); + } + return TRUE; + } + return FALSE; +} + +BOOL LLView::focusLastItem(BOOL prefer_text_fields) +{ + // search for text field first + if(prefer_text_fields) + { + LLCtrlQuery query = LLView::getTabOrderQuery(); + query.addPreFilter(LLUICtrl::LLTextInputFilter::getInstance()); + LLView::child_list_t result = query(this); + if(result.size() > 0) + { + LLUICtrl * ctrl = static_cast(result.back()); + if(!ctrl->hasFocus()) + { + ctrl->setFocus(TRUE); + ctrl->onTabInto(); + gFocusMgr.triggerFocusFlash(); + } + return TRUE; + } + } + // no text field found, or we don't care about text fields + LLView::child_list_t result = LLView::getTabOrderQuery().run(this); + if(result.size() > 0) + { + LLUICtrl * ctrl = static_cast(result.back()); + if(!ctrl->hasFocus()) + { + ctrl->setFocus(TRUE); + ctrl->onTabInto(); + gFocusMgr.triggerFocusFlash(); + } + return TRUE; + } + return FALSE; +} + + + +// delete all children. Override this function if you need to +// perform any extra clean up such as cached pointers to selected +// children, etc. +void LLView::deleteAllChildren() +{ + // clear out the control ordering + mCtrlOrder.clear(); + + while (!mChildList.empty()) + { + LLView* viewp = mChildList.front(); + delete viewp; // will remove the child from mChildList + } +} + +void LLView::setAllChildrenEnabled(BOOL b) +{ + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + viewp->setEnabled(b); + } +} + +// virtual +void LLView::setTentative(BOOL b) +{ +} + +// virtual +BOOL LLView::getTentative() const +{ + return FALSE; +} + +// virtual +void LLView::setEnabled(BOOL enabled) +{ + mEnabled = enabled; +} + +// virtual +void LLView::setVisible(BOOL visible) +{ + if( !visible && (gFocusMgr.getTopView() == this) ) + { + gFocusMgr.setTopView( NULL, NULL ); + } + + if ( mVisible != visible ) + { + // tell all children of this view that the visibility may have changed + onVisibilityChange ( visible ); + } + + mVisible = visible; +} + +// virtual +void LLView::setHidden(BOOL hidden) +{ + mHidden = hidden; +} + +// virtual +BOOL LLView::setLabelArg(const LLString& key, const LLString& text) +{ + return FALSE; +} + +void LLView::onVisibilityChange ( BOOL curVisibilityIn ) +{ + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + // only views that are themselves visible will have their overall visibility affected by their ancestors + if (viewp->getVisible()) + { + viewp->onVisibilityChange ( curVisibilityIn ); + } + } +} + +// virtual +void LLView::translate(S32 x, S32 y) +{ + mRect.translate(x, y); +} + +// virtual +BOOL LLView::canSnapTo(LLView* other_view) +{ + return other_view->getVisible(); +} + +// virtual +void LLView::snappedTo(LLView* snap_view) +{ +} + +BOOL LLView::handleHover(S32 x, S32 y, MASK mask) +{ + BOOL handled = childrenHandleHover( x, y, mask ) != NULL; + if( !handled && mMouseOpaque && pointInView( x, y ) ) + { + LLUI::sWindow->setCursor(UI_CURSOR_ARROW); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; + handled = TRUE; + } + + return handled; +} + +BOOL LLView::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen) +{ + BOOL handled = FALSE; + + LLString tool_tip; + + if ( getVisible() && getEnabled()) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + if( viewp->handleToolTip(local_x, local_y, msg, sticky_rect_screen ) ) + { + handled = TRUE; + break; + } + } + + if (LLUI::sShowXUINames && (mToolTipMsg.find(".xml", 0) == LLString::npos) && + (mName.find("Drag", 0) == LLString::npos)) + { + tool_tip = mName; + } + else + { + tool_tip = mToolTipMsg; + } + + + + BOOL showNamesTextBox = LLUI::sShowXUINames && (getWidgetType() == WIDGET_TYPE_TEXT_BOX); + + if( !handled && (mMouseOpaque || showNamesTextBox) && pointInView( x, y ) && !tool_tip.empty()) + { + + msg = tool_tip; + + // Convert rect local to screen coordinates + localPointToScreen( + 0, 0, + &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); + localPointToScreen( + mRect.getWidth(), mRect.getHeight(), + &(sticky_rect_screen->mRight), &(sticky_rect_screen->mTop) ); + + handled = TRUE; + } + } + + return handled; +} + + +BOOL LLView::handleKey(KEY key, MASK mask, BOOL called_from_parent) +{ + BOOL handled = FALSE; + + if( called_from_parent ) + { + // Downward traversal + if (getVisible() && mEnabled) + { + handled = childrenHandleKey( key, mask ) != NULL; + } + } + + if( !handled ) + { + // JC: Must pass to disabled views, since they could have + // keyboard focus, which requires the escape key to exit. + if (getVisible()) + { + handled = handleKeyHere( key, mask, called_from_parent ); + if (handled && LLView::sDebugKeys) + { + llinfos << "Key handled by " << getName() << llendl; + } + } + } + + if( !handled && !called_from_parent) + { + if (mIsFocusRoot) + { + // stop processing at focus root + handled = FALSE; + } + else if (mParentView) + { + // Upward traversal + handled = mParentView->handleKey( key, mask, FALSE ); + } + } + return handled; +} + +// Called from handleKey() +// Handles key in this object. Checking parents and children happens in handleKey() +BOOL LLView::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) +{ + return FALSE; +} + + +BOOL LLView::handleUnicodeChar(llwchar uni_char, BOOL called_from_parent) +{ + BOOL handled = FALSE; + + /* + if( called_from_parent ) + { + // Downward traversal + if (getVisible() && mEnabled) + { + handled = childrenHandleKey( key, mask ) != NULL; + } + } + */ + + // JC: Must pass to disabled views, since they could have + // keyboard focus, which requires the escape key to exit. + if (getVisible()) + { + handled = handleUnicodeCharHere(uni_char, called_from_parent); + if (handled && LLView::sDebugKeys) + { + llinfos << "Unicode key handled by " << getName() << llendl; + } + } + + + if (!handled && !called_from_parent) + { + if (mIsFocusRoot) + { + // stop processing at focus root + handled = FALSE; + } + else if(mParentView) + { + // Upward traversal + handled = mParentView->handleUnicodeChar(uni_char, FALSE); + } + } + + return handled; +} + + +BOOL LLView::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent ) +{ + return FALSE; +} + + +BOOL LLView::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, void* cargo_data, + EAcceptance* accept, + LLString& tooltip_msg) +{ + // CRO this is an experiment to allow drag and drop into object inventory based on the DragAndDrop tool's permissions rather than the parent + BOOL handled = childrenHandleDragAndDrop( x, y, mask, drop, + cargo_type, + cargo_data, + accept, + tooltip_msg) != NULL; + if( !handled && mMouseOpaque ) + { + *accept = ACCEPT_NO; + handled = TRUE; + lldebugst(LLERR_USER_INPUT) << "dragAndDrop handled by LLView " << getName() << llendl; + } + + return handled; +} + +LLView* LLView::childrenHandleDragAndDrop(S32 x, S32 y, MASK mask, + BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + LLString& tooltip_msg) +{ + LLView* handled_view = FALSE; + // CRO this is an experiment to allow drag and drop into object inventory based on the DragAndDrop tool's permissions rather than the parent + if( getVisible() ) +// if( getVisible() && mEnabled ) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + if( viewp->pointInView(local_x, local_y) && + viewp->getVisible() && + viewp->mEnabled && + viewp->handleDragAndDrop(local_x, local_y, mask, drop, + cargo_type, + cargo_data, + accept, + tooltip_msg)) + { + handled_view = viewp; + break; + } + } + } + return handled_view; +} + + + +BOOL LLView::handleMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = childrenHandleMouseUp( x, y, mask ) != NULL; + if( !handled && mMouseOpaque ) + { + handled = TRUE; + } + return handled; +} + +BOOL LLView::handleMouseDown(S32 x, S32 y, MASK mask) +{ + LLView* handled_view = childrenHandleMouseDown( x, y, mask ); + BOOL handled = (handled_view != NULL); + if( !handled && mMouseOpaque ) + { + handled = TRUE; + handled_view = this; + } + + // HACK If we're editing UI, select the leaf view that ate the click. + if (sEditingUI && handled_view) + { + // need to find leaf views, big hack + EWidgetType type = handled_view->getWidgetType(); + if (type == WIDGET_TYPE_BUTTON + || type == WIDGET_TYPE_LINE_EDITOR + || type == WIDGET_TYPE_TEXT_EDITOR + || type == WIDGET_TYPE_TEXT_BOX) + { + sEditingUIView = handled_view; + } + } + + return handled; +} + +BOOL LLView::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + BOOL handled = childrenHandleDoubleClick( x, y, mask ) != NULL; + if( !handled && mMouseOpaque ) + { + handleMouseDown(x, y, mask); + handled = TRUE; + } + return handled; +} + +BOOL LLView::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + BOOL handled = FALSE; + if( getVisible() && mEnabled ) + { + handled = childrenHandleScrollWheel( x, y, clicks ) != NULL; + if( !handled && mMouseOpaque ) + { + handled = TRUE; + } + } + return handled; +} + +BOOL LLView::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL handled = childrenHandleRightMouseDown( x, y, mask ) != NULL; + if( !handled && mMouseOpaque ) + { + handled = TRUE; + } + return handled; +} + +BOOL LLView::handleRightMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = childrenHandleRightMouseUp( x, y, mask ) != NULL; + if( !handled && mMouseOpaque ) + { + handled = TRUE; + } + return handled; +} + +LLView* LLView::childrenHandleScrollWheel(S32 x, S32 y, S32 clicks) +{ + LLView* handled_view = NULL; + if (getVisible() && mEnabled ) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + if (viewp->pointInView(local_x, local_y) && + viewp->handleScrollWheel( local_x, local_y, clicks )) + { + if (sDebugMouseHandling) + { + sMouseHandlerMessage = LLString("->") + viewp->mName.c_str() + sMouseHandlerMessage; + } + + handled_view = viewp; + break; + } + } + } + return handled_view; +} + +LLView* LLView::childrenHandleHover(S32 x, S32 y, MASK mask) +{ + LLView* handled_view = NULL; + if (getVisible() && mEnabled ) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + if(viewp->pointInView(local_x, local_y) && + viewp->getVisible() && + viewp->getEnabled() && + viewp->handleHover(local_x, local_y, mask) ) + { + if (sDebugMouseHandling) + { + sMouseHandlerMessage = LLString("->") + viewp->mName.c_str() + sMouseHandlerMessage; + } + + handled_view = viewp; + break; + } + } + } + return handled_view; +} + +// Called during downward traversal +LLView* LLView::childrenHandleKey(KEY key, MASK mask) +{ + LLView* handled_view = NULL; + + if ( getVisible() && mEnabled ) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + if (viewp->handleKey(key, mask, TRUE)) + { + if (LLView::sDebugKeys) + { + llinfos << "Key handled by " << viewp->getName() << llendl; + } + handled_view = viewp; + break; + } + } + } + + return handled_view; +} + + +LLView* LLView::childrenHandleMouseDown(S32 x, S32 y, MASK mask) +{ + LLView* handled_view = NULL; + + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + + if (viewp->pointInView(local_x, local_y) && + viewp->getVisible() && + viewp->mEnabled && + viewp->handleMouseDown( local_x, local_y, mask )) + { + if (sDebugMouseHandling) + { + sMouseHandlerMessage = LLString("->") + viewp->mName.c_str() + sMouseHandlerMessage; + } + handled_view = viewp; + break; + } + } + return handled_view; +} + +LLView* LLView::childrenHandleRightMouseDown(S32 x, S32 y, MASK mask) +{ + LLView* handled_view = NULL; + + if (getVisible() && mEnabled ) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + if (viewp->pointInView(local_x, local_y) && + viewp->getVisible() && + viewp->mEnabled && + viewp->handleRightMouseDown( local_x, local_y, mask )) + { + if (sDebugMouseHandling) + { + sMouseHandlerMessage = LLString("->") + viewp->mName.c_str() + sMouseHandlerMessage; + } + handled_view = viewp; + break; + } + } + } + return handled_view; +} + +LLView* LLView::childrenHandleDoubleClick(S32 x, S32 y, MASK mask) +{ + LLView* handled_view = NULL; + + if (getVisible() && mEnabled ) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + if (viewp->pointInView(local_x, local_y) && + viewp->getVisible() && + viewp->mEnabled && + viewp->handleDoubleClick( local_x, local_y, mask )) + { + if (sDebugMouseHandling) + { + sMouseHandlerMessage = LLString("->") + viewp->mName.c_str() + sMouseHandlerMessage; + } + handled_view = viewp; + break; + } + } + } + return handled_view; +} + +LLView* LLView::childrenHandleMouseUp(S32 x, S32 y, MASK mask) +{ + LLView* handled_view = NULL; + if( getVisible() && mEnabled ) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + if (!viewp->pointInView(local_x, local_y)) + continue; + if (!viewp->getVisible()) + continue; + if (!viewp->mEnabled) + continue; + if (viewp->handleMouseUp( local_x, local_y, mask )) + { + if (sDebugMouseHandling) + { + sMouseHandlerMessage = LLString("->") + viewp->mName.c_str() + sMouseHandlerMessage; + } + handled_view = viewp; + break; + } + } + } + return handled_view; +} + +LLView* LLView::childrenHandleRightMouseUp(S32 x, S32 y, MASK mask) +{ + LLView* handled_view = NULL; + if( getVisible() && mEnabled ) + { + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->mRect.mLeft; + S32 local_y = y - viewp->mRect.mBottom; + if (viewp->pointInView(local_x, local_y) && + viewp->getVisible() && + viewp->mEnabled && + viewp->handleRightMouseUp( local_x, local_y, mask )) + { + if (sDebugMouseHandling) + { + sMouseHandlerMessage = LLString("->") + viewp->mName.c_str() + sMouseHandlerMessage; + } + handled_view = viewp; + break; + } + } + } + return handled_view; +} + + +void LLView::draw() +{ + if (getVisible()) + { + if (sDebugRects) + { + drawDebugRect(); + + // Check for bogus rectangle + if (mRect.mRight <= mRect.mLeft + || mRect.mTop <= mRect.mBottom) + { + llwarns << "Bogus rectangle for " << getName() << " with " << mRect << llendl; + } + } + + LLRect rootRect = getRootView()->getRect(); + LLRect screenRect; + + // draw focused control on top of everything else + LLView* focus_view = gFocusMgr.getKeyboardFocus(); + if (focus_view && focus_view->getParent() != this) + { + focus_view = NULL; + } + + for (child_list_reverse_iter_t child_iter = mChildList.rbegin(); child_iter != mChildList.rend(); ++child_iter) + { + LLView *viewp = *child_iter; + ++sDepth; + + if (viewp->getVisible() && viewp != focus_view) + { + // Only draw views that are within the root view + localRectToScreen(viewp->getRect(),&screenRect); + if ( rootRect.rectInRect(&screenRect) ) + { + glMatrixMode(GL_MODELVIEW); + LLUI::pushMatrix(); + { + LLUI::translate((F32)viewp->getRect().mLeft, (F32)viewp->getRect().mBottom, 0.f); + viewp->draw(); + } + LLUI::popMatrix(); + } + } + + --sDepth; + } + + if (focus_view && focus_view->getVisible()) + { + drawChild(focus_view); + } + + // HACK + if (sEditingUI && this == sEditingUIView) + { + drawDebugRect(); + } + } +} + +//Draw a box for debugging. +void LLView::drawDebugRect() +{ + // drawing solids requires texturing be disabled + LLGLSNoTexture no_texture; + + // draw red rectangle for the border + LLColor4 border_color(0.f, 0.f, 0.f, 1.f); + if (sEditingUI) + { + border_color.mV[0] = 1.f; + } + else + { + border_color.mV[sDepth%3] = 1.f; + } + + glColor4fv( border_color.mV ); + + glBegin(GL_LINES); + glVertex2i(0, mRect.getHeight() - 1); + glVertex2i(0, 0); + + glVertex2i(0, 0); + glVertex2i(mRect.getWidth() - 1, 0); + + glVertex2i(mRect.getWidth() - 1, 0); + glVertex2i(mRect.getWidth() - 1, mRect.getHeight() - 1); + + glVertex2i(mRect.getWidth() - 1, mRect.getHeight() - 1); + glVertex2i(0, mRect.getHeight() - 1); + glEnd(); + + // Draw the name if it's not a leaf node + if (mChildList.size() && !sEditingUI) + { + //char temp[256]; + S32 x, y; + glColor4fv( border_color.mV ); + x = mRect.getWidth()/2; + y = mRect.getHeight()/2; + LLString debug_text = llformat("%s (%d x %d)", getName().c_str(), + mRect.getWidth(), mRect.getHeight()); + LLFontGL::sSansSerifSmall->renderUTF8(debug_text, 0, (F32)x, (F32)y, border_color, + LLFontGL::HCENTER, LLFontGL::BASELINE, LLFontGL::NORMAL, + S32_MAX, S32_MAX, NULL, FALSE); + } +} + +void LLView::drawChild(LLView* childp, S32 x_offset, S32 y_offset) +{ + if (childp && childp->getParent() == this) + { + ++sDepth; + + if (childp->getVisible()) + { + glMatrixMode(GL_MODELVIEW); + LLUI::pushMatrix(); + { + LLUI::translate((F32)childp->getRect().mLeft + x_offset, (F32)childp->getRect().mBottom + y_offset, 0.f); + childp->draw(); + } + LLUI::popMatrix(); + } + + --sDepth; + } +} + + +void LLView::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + // make sure this view contains all its children + updateRect(); + + // compute how much things changed and apply reshape logic to children + S32 delta_width = width - mRect.getWidth(); + S32 delta_height = height - mRect.getHeight(); + + if (delta_width || delta_height || sForceReshape) + { + // adjust our rectangle + mRect.mRight = mRect.mLeft + width; + mRect.mTop = mRect.mBottom + height; + + // move child views according to reshape flags + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + LLRect child_rect( viewp->mRect ); + + if (viewp->followsRight() && viewp->followsLeft()) + { + child_rect.mRight += delta_width; + } + else if (viewp->followsRight()) + { + child_rect.mLeft += delta_width; + child_rect.mRight += delta_width; + } + else if (viewp->followsLeft()) + { + // left is 0, don't need to adjust coords + } + else + { + // BUG what to do when we don't follow anyone? + // for now, same as followsLeft + } + + if (viewp->followsTop() && viewp->followsBottom()) + { + child_rect.mTop += delta_height; + } + else if (viewp->followsTop()) + { + child_rect.mTop += delta_height; + child_rect.mBottom += delta_height; + } + else if (viewp->followsBottom()) + { + // bottom is 0, so don't need to adjust coords + } + else + { + // BUG what to do when we don't follow? + // for now, same as bottom + } + + S32 delta_x = child_rect.mLeft - viewp->mRect.mLeft; + S32 delta_y = child_rect.mBottom - viewp->mRect.mBottom; + viewp->translate( delta_x, delta_y ); + viewp->reshape(child_rect.getWidth(), child_rect.getHeight()); + } + } + + if (!called_from_parent) + { + if (mParentView) + { + mParentView->reshape(mParentView->getRect().getWidth(), mParentView->getRect().getHeight(), FALSE); + } + } +} + +LLRect LLView::getRequiredRect() +{ + return mRect; +} + +const LLRect LLView::getScreenRect() const +{ + //FIXME: check for one-off error + LLRect screen_rect; + localPointToScreen(0, 0, &screen_rect.mLeft, &screen_rect.mBottom); + localPointToScreen(mRect.getWidth(), mRect.getHeight(), &screen_rect.mRight, &screen_rect.mTop); + return screen_rect; +} + +const LLRect LLView::getLocalRect() const +{ + LLRect local_rect(0, mRect.getHeight(), mRect.getWidth(), 0); + return local_rect; +} + +void LLView::updateRect() +{ + if (mSpanChildren && mChildList.size()) + { + LLView* first_child = (*mChildList.begin()); + LLRect child_spanning_rect = first_child->mRect; + + for ( child_list_iter_t child_it = ++mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + if (viewp->getVisible()) + { + child_spanning_rect |= viewp->mRect; + } + } + + S32 translate_x = llmin(0, child_spanning_rect.mLeft); + S32 translate_y = llmin(0, child_spanning_rect.mBottom); + S32 new_width = llmax(mRect.getWidth() + translate_x, child_spanning_rect.getWidth()); + S32 new_height = llmax(mRect.getHeight() + translate_y, child_spanning_rect.getHeight()); + + mRect.setOriginAndSize(mRect.mLeft + translate_x, mRect.mBottom + translate_y, new_width, new_height); + + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* viewp = *child_it; + viewp->mRect.translate(-translate_x, -translate_y); + } + } +} + +BOOL LLView::hasAncestor(LLView* parentp) +{ + if (!parentp) + { + return FALSE; + } + + LLView* viewp = getParent(); + while(viewp) + { + if (viewp == parentp) + { + return TRUE; + } + viewp = viewp->getParent(); + } + + return FALSE; +} + +//----------------------------------------------------------------------------- + +BOOL LLView::childHasKeyboardFocus( const LLString& childname ) const +{ + LLView *child = getChildByName(childname); + if (child) + { + return gFocusMgr.childHasKeyboardFocus(child); + } + else + { + return FALSE; + } +} + +//----------------------------------------------------------------------------- + +BOOL LLView::hasChild(const LLString& childname, BOOL recurse) const +{ + return getChildByName(childname, recurse) ? TRUE : FALSE; +} + +//----------------------------------------------------------------------------- +// getChildByName() +//----------------------------------------------------------------------------- +LLView* LLView::getChildByName(const LLString& name, BOOL recurse) const +{ + if(name.empty()) return NULL; + child_list_const_iter_t child_it; + // Look for direct children *first* + for ( child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* childp = *child_it; + if (childp->getName() == name) + { + return childp; + } + } + if (recurse) + { + // Look inside the child as well. + for ( child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + LLView* childp = *child_it; + LLView* viewp = childp->getChildByName(name, recurse); + if ( viewp ) + { + return viewp; + } + } + } + return NULL; +} + +// virtual +void LLView::onFocusLost() +{ +} + +// virtual +void LLView::onFocusReceived() +{ +} + +// virtual +void LLView::screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const +{ + *local_x = screen_x - mRect.mLeft; + *local_y = screen_y - mRect.mBottom; + + const LLView* cur = this; + while( cur->mParentView ) + { + cur = cur->mParentView; + *local_x -= cur->mRect.mLeft; + *local_y -= cur->mRect.mBottom; + } +} + +void LLView::localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const +{ + *screen_x = local_x + mRect.mLeft; + *screen_y = local_y + mRect.mBottom; + + const LLView* cur = this; + while( cur->mParentView ) + { + cur = cur->mParentView; + *screen_x += cur->mRect.mLeft; + *screen_y += cur->mRect.mBottom; + } +} + +void LLView::screenRectToLocal(const LLRect& screen, LLRect* local) const +{ + *local = screen; + local->translate( -mRect.mLeft, -mRect.mBottom ); + + const LLView* cur = this; + while( cur->mParentView ) + { + cur = cur->mParentView; + local->translate( -cur->mRect.mLeft, -cur->mRect.mBottom ); + } +} + +void LLView::localRectToScreen(const LLRect& local, LLRect* screen) const +{ + *screen = local; + screen->translate( mRect.mLeft, mRect.mBottom ); + + const LLView* cur = this; + while( cur->mParentView ) + { + cur = cur->mParentView; + screen->translate( cur->mRect.mLeft, cur->mRect.mBottom ); + } +} + +LLView* LLView::getRootMostFastFrameView() +{ + if (gFocusMgr.getTopView() == this) + { + return this; + } + + if (getParent()) + { + LLView* rootmost_view = getParent()->getRootMostFastFrameView(); + if (rootmost_view) + { + return rootmost_view; + } + } + + return mRenderInFastFrame ? this : NULL; +} + + +LLView* LLView::getRootView() +{ + LLView* view = this; + while( view->mParentView ) + { + view = view->mParentView; + } + return view; +} + +//static +LLWindow* LLView::getWindow(void) +{ + return LLUI::sWindow; +} + +// Moves the view so that it is entirely inside of constraint. +// If the view will not fit because it's too big, aligns with the top and left. +// (Why top and left? That's where the drag bars are for floaters.) +BOOL LLView::translateIntoRect(const LLRect& constraint, BOOL allow_partial_outside ) +{ + S32 delta_x = 0; + S32 delta_y = 0; + + if (allow_partial_outside) + { + const S32 KEEP_ONSCREEN_PIXELS = 16; + + if( mRect.mRight - KEEP_ONSCREEN_PIXELS < constraint.mLeft ) + { + delta_x = constraint.mLeft - (mRect.mRight - KEEP_ONSCREEN_PIXELS); + } + else + if( mRect.mLeft + KEEP_ONSCREEN_PIXELS > constraint.mRight ) + { + delta_x = constraint.mRight - (mRect.mLeft + KEEP_ONSCREEN_PIXELS); + delta_x += llmax( 0, mRect.getWidth() - constraint.getWidth() ); + } + + if( mRect.mTop > constraint.mTop ) + { + delta_y = constraint.mTop - mRect.mTop; + } + else + if( mRect.mTop - KEEP_ONSCREEN_PIXELS < constraint.mBottom ) + { + delta_y = constraint.mBottom - (mRect.mTop - KEEP_ONSCREEN_PIXELS); + delta_y -= llmax( 0, mRect.getHeight() - constraint.getHeight() ); + } + } + else + { + if( mRect.mLeft < constraint.mLeft ) + { + delta_x = constraint.mLeft - mRect.mLeft; + } + else + if( mRect.mRight > constraint.mRight ) + { + delta_x = constraint.mRight - mRect.mRight; + delta_x += llmax( 0, mRect.getWidth() - constraint.getWidth() ); + } + + if( mRect.mTop > constraint.mTop ) + { + delta_y = constraint.mTop - mRect.mTop; + } + else + if( mRect.mBottom < constraint.mBottom ) + { + delta_y = constraint.mBottom - mRect.mBottom; + delta_y -= llmax( 0, mRect.getHeight() - constraint.getHeight() ); + } + } + + if (delta_x != 0 || delta_y != 0) + { + translate(delta_x, delta_y); + return TRUE; + } + return FALSE; +} + +BOOL LLView::localPointToOtherView( S32 x, S32 y, S32 *other_x, S32 *other_y, LLView* other_view) +{ + LLView* cur_view = this; + LLView* root_view = NULL; + + while (cur_view) + { + if (cur_view == other_view) + { + *other_x = x; + *other_y = y; + return TRUE; + } + + x += cur_view->getRect().mLeft; + y += cur_view->getRect().mBottom; + + cur_view = cur_view->getParent(); + root_view = cur_view; + } + + // assuming common root between two views, chase other_view's parents up to root + cur_view = other_view; + while (cur_view) + { + x -= cur_view->getRect().mLeft; + y -= cur_view->getRect().mBottom; + + cur_view = cur_view->getParent(); + + if (cur_view == root_view) + { + *other_x = x; + *other_y = y; + return TRUE; + } + } + + *other_x = x; + *other_y = y; + return FALSE; +} + +BOOL LLView::localRectToOtherView( const LLRect& local, LLRect* other, LLView* other_view ) const +{ + LLRect cur_rect = local; + const LLView* cur_view = this; + const LLView* root_view = NULL; + + while (cur_view) + { + if (cur_view == other_view) + { + *other = cur_rect; + return TRUE; + } + + cur_rect.translate(cur_view->getRect().mLeft, cur_view->getRect().mBottom); + + cur_view = cur_view->getParent(); + root_view = cur_view; + } + + // assuming common root between two views, chase other_view's parents up to root + cur_view = other_view; + while (cur_view) + { + cur_rect.translate(-cur_view->getRect().mLeft, -cur_view->getRect().mBottom); + + cur_view = cur_view->getParent(); + + if (cur_view == root_view) + { + *other = cur_rect; + return TRUE; + } + } + + *other = cur_rect; + return FALSE; +} + +// virtual +LLXMLNodePtr LLView::getXML(bool save_children) const +{ + const LLString& type_name = getWidgetTag(); + + LLXMLNodePtr node = new LLXMLNode(type_name, FALSE); + + node->createChild("name", TRUE)->setStringValue(getName()); + node->createChild("width", TRUE)->setIntValue(getRect().getWidth()); + node->createChild("height", TRUE)->setIntValue(getRect().getHeight()); + + LLView* parent = getParent(); + S32 left = mRect.mLeft; + S32 bottom = mRect.mBottom; + if (parent) bottom -= parent->getRect().getHeight(); + + node->createChild("left", TRUE)->setIntValue(left); + node->createChild("bottom", TRUE)->setIntValue(bottom); + + U32 follows_flags = getFollows(); + if (follows_flags) + { + std::stringstream buffer; + bool pipe = false; + if (followsLeft()) + { + buffer << "left"; + pipe = true; + } + if (followsTop()) + { + if (pipe) buffer << "|"; + buffer << "top"; + pipe = true; + } + if (followsRight()) + { + if (pipe) buffer << "|"; + buffer << "right"; + pipe = true; + } + if (followsBottom()) + { + if (pipe) buffer << "|"; + buffer << "bottom"; + } + node->createChild("follows", TRUE)->setStringValue(buffer.str()); + } + // Export all widgets as enabled and visible - code must disable. + node->createChild("hidden", TRUE)->setBoolValue(mHidden); + node->createChild("mouse_opaque", TRUE)->setBoolValue(mMouseOpaque ); + if (!mToolTipMsg.empty()) + { + node->createChild("tool_tip", TRUE)->setStringValue(mToolTipMsg); + } + if (mSoundFlags != MOUSE_UP) + { + node->createChild("sound_flags", TRUE)->setIntValue((S32)mSoundFlags); + } + + node->createChild("enabled", TRUE)->setBoolValue(mEnabled); + + if (!mControlName.empty()) + { + node->createChild("control_name", TRUE)->setStringValue(mControlName); + } + return node; +} + +// static +void LLView::addColorXML(LLXMLNodePtr node, const LLColor4& color, + const LLString& xml_name, const LLString& control_name) +{ + if (color != LLUI::sColorsGroup->getColor(control_name)) + { + node->createChild(xml_name, TRUE)->setFloatValue(4, color.mV); + } +} + +// static +void LLView::saveColorToXML(std::ostream& out, const LLColor4& color, + const LLString& xml_name, const LLString& control_name, + const LLString& indent) +{ + if (color != LLUI::sColorsGroup->getColor(control_name)) + { + out << indent << xml_name << "=\"" + << color.mV[VRED] << ", " + << color.mV[VGREEN] << ", " + << color.mV[VBLUE] << ", " + << color.mV[VALPHA] << "\"\n"; + } +} + +//static +LLString LLView::escapeXML(const LLString& xml, LLString& indent) +{ + LLString ret = indent + "\"" + LLXMLNode::escapeXML(xml); + + //replace every newline with a close quote, new line, indent, open quote + size_t index = ret.size()-1; + size_t fnd; + + while ((fnd = ret.rfind("\n", index)) != std::string::npos) + { + ret.replace(fnd, 1, "\"\n" + indent + "\""); + index = fnd-1; + } + + //append close quote + ret.append("\""); + + return ret; +} + +// static +LLWString LLView::escapeXML(const LLWString& xml) +{ + LLWString out; + for (LLWString::size_type i = 0; i < xml.size(); ++i) + { + llwchar c = xml[i]; + switch(c) + { + case '"': out.append(utf8string_to_wstring(""")); break; + case '\'': out.append(utf8string_to_wstring("'")); break; + case '&': out.append(utf8string_to_wstring("&")); break; + case '<': out.append(utf8string_to_wstring("<")); break; + case '>': out.append(utf8string_to_wstring(">")); break; + default: out.push_back(c); break; + } + } + return out; +} + +// static +const LLCtrlQuery & LLView::getTabOrderQuery() +{ + static LLCtrlQuery query; + if(query.getPreFilters().size() == 0) { + query.addPreFilter(LLVisibleFilter::getInstance()); + query.addPreFilter(LLEnabledFilter::getInstance()); + query.addPreFilter(LLTabStopFilter::getInstance()); + query.addPostFilter(LLUICtrl::LLTabStopPostFilter::getInstance()); + } + return query; +} + +// static +const LLCtrlQuery & LLView::getFocusRootsQuery() +{ + static LLCtrlQuery query; + if(query.getPreFilters().size() == 0) { + query.addPreFilter(LLVisibleFilter::getInstance()); + query.addPreFilter(LLEnabledFilter::getInstance()); + query.addPreFilter(LLView::LLFocusRootsFilter::getInstance()); + } + return query; +} + + +LLView* LLView::findSnapRect(LLRect& new_rect, const LLCoordGL& mouse_dir, + LLView::ESnapType snap_type, S32 threshold, S32 padding) +{ + LLView* snap_view = NULL; + + if (!mParentView) + { + new_rect = mRect; + return snap_view; + } + + // If the view is near the edge of its parent, snap it to + // the edge. + LLRect test_rect = getSnapRect(); + LLRect view_rect = getSnapRect(); + test_rect.stretch(padding); + view_rect.stretch(padding); + + BOOL snapped_x = FALSE; + BOOL snapped_y = FALSE; + + LLRect parent_local_snap_rect = mParentView->getSnapRect(); + parent_local_snap_rect.translate(-mParentView->getRect().mLeft, -mParentView->getRect().mBottom); + + if (snap_type == SNAP_PARENT || snap_type == SNAP_PARENT_AND_SIBLINGS) + { + if (llabs(parent_local_snap_rect.mRight - test_rect.mRight) <= threshold && (parent_local_snap_rect.mRight - test_rect.mRight) * mouse_dir.mX >= 0) + { + view_rect.translate(parent_local_snap_rect.mRight - view_rect.mRight, 0); + snap_view = mParentView; + snapped_x = TRUE; + } + + if (llabs(test_rect.mLeft - parent_local_snap_rect.mLeft) <= threshold && test_rect.mLeft * mouse_dir.mX <= 0) + { + view_rect.translate(parent_local_snap_rect.mLeft - view_rect.mLeft, 0); + snap_view = mParentView; + snapped_x = TRUE; + } + + if (llabs(test_rect.mBottom - parent_local_snap_rect.mBottom) <= threshold && test_rect.mBottom * mouse_dir.mY <= 0) + { + view_rect.translate(0, parent_local_snap_rect.mBottom - view_rect.mBottom); + snap_view = mParentView; + snapped_y = TRUE; + } + + if (llabs(parent_local_snap_rect.mTop - test_rect.mTop) <= threshold && (parent_local_snap_rect.mTop - test_rect.mTop) * mouse_dir.mY >= 0) + { + view_rect.translate(0, parent_local_snap_rect.mTop - view_rect.mTop); + snap_view = mParentView; + snapped_y = TRUE; + } + } + if (snap_type == SNAP_SIBLINGS || snap_type == SNAP_PARENT_AND_SIBLINGS) + { + for ( child_list_const_iter_t child_it = mParentView->getChildList()->begin(); + child_it != mParentView->getChildList()->end(); ++child_it) + { + LLView* siblingp = *child_it; + // skip self + if (siblingp == this || !siblingp->getVisible() || !canSnapTo(siblingp)) + { + continue; + } + + LLRect sibling_rect = siblingp->getSnapRect(); + + if (!snapped_x && llabs(test_rect.mRight - sibling_rect.mLeft) <= threshold && (test_rect.mRight - sibling_rect.mLeft) * mouse_dir.mX <= 0) + { + view_rect.translate(sibling_rect.mLeft - view_rect.mRight, 0); + if (!snapped_y) + { + if (llabs(test_rect.mTop - sibling_rect.mTop) <= threshold && (test_rect.mTop - sibling_rect.mTop) * mouse_dir.mY <= 0) + { + view_rect.translate(0, sibling_rect.mTop - test_rect.mTop); + snapped_y = TRUE; + } + else if (llabs(test_rect.mBottom - sibling_rect.mBottom) <= threshold && (test_rect.mBottom - sibling_rect.mBottom) * mouse_dir.mY <= 0) + { + view_rect.translate(0, sibling_rect.mBottom - test_rect.mBottom); + snapped_y = TRUE; + } + } + snap_view = siblingp; + snapped_x = TRUE; + } + + if (!snapped_x && llabs(test_rect.mLeft - sibling_rect.mRight) <= threshold && (test_rect.mLeft - sibling_rect.mRight) * mouse_dir.mX <= 0) + { + view_rect.translate(sibling_rect.mRight - view_rect.mLeft, 0); + if (!snapped_y) + { + if (llabs(test_rect.mTop - sibling_rect.mTop) <= threshold && (test_rect.mTop - sibling_rect.mTop) * mouse_dir.mY <= 0) + { + view_rect.translate(0, sibling_rect.mTop - test_rect.mTop); + snapped_y = TRUE; + } + else if (llabs(test_rect.mBottom - sibling_rect.mBottom) <= threshold && (test_rect.mBottom - sibling_rect.mBottom) * mouse_dir.mY <= 0) + { + view_rect.translate(0, sibling_rect.mBottom - test_rect.mBottom); + snapped_y = TRUE; + } + } + snap_view = siblingp; + snapped_x = TRUE; + } + + if (!snapped_y && llabs(test_rect.mBottom - sibling_rect.mTop) <= threshold && (test_rect.mBottom - sibling_rect.mTop) * mouse_dir.mY <= 0) + { + view_rect.translate(0, sibling_rect.mTop - view_rect.mBottom); + if (!snapped_x) + { + if (llabs(test_rect.mLeft - sibling_rect.mLeft) <= threshold && (test_rect.mLeft - sibling_rect.mLeft) * mouse_dir.mX <= 0) + { + view_rect.translate(sibling_rect.mLeft - test_rect.mLeft, 0); + snapped_x = TRUE; + } + else if (llabs(test_rect.mRight - sibling_rect.mRight) <= threshold && (test_rect.mRight - sibling_rect.mRight) * mouse_dir.mX <= 0) + { + view_rect.translate(sibling_rect.mRight - test_rect.mRight, 0); + snapped_x = TRUE; + } + } + snap_view = siblingp; + snapped_y = TRUE; + } + + if (!snapped_y && llabs(test_rect.mTop - sibling_rect.mBottom) <= threshold && (test_rect.mTop - sibling_rect.mBottom) * mouse_dir.mY <= 0) + { + view_rect.translate(0, sibling_rect.mBottom - view_rect.mTop); + if (!snapped_x) + { + if (llabs(test_rect.mLeft - sibling_rect.mLeft) <= threshold && (test_rect.mLeft - sibling_rect.mLeft) * mouse_dir.mX <= 0) + { + view_rect.translate(sibling_rect.mLeft - test_rect.mLeft, 0); + snapped_x = TRUE; + } + else if (llabs(test_rect.mRight - sibling_rect.mRight) <= threshold && (test_rect.mRight - sibling_rect.mRight) * mouse_dir.mX <= 0) + { + view_rect.translate(sibling_rect.mRight - test_rect.mRight, 0); + snapped_x = TRUE; + } + } + snap_view = siblingp; + snapped_y = TRUE; + } + + if (snapped_x && snapped_y) + { + break; + } + } + } + + // shrink actual view rect back down + view_rect.stretch(-padding); + new_rect = view_rect; + return snap_view; +} + +LLView* LLView::findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding) +{ + LLRect snap_rect = getSnapRect(); + S32 snap_pos = 0; + switch(snap_edge) + { + case SNAP_LEFT: + snap_pos = snap_rect.mLeft; + break; + case SNAP_RIGHT: + snap_pos = snap_rect.mRight; + break; + case SNAP_TOP: + snap_pos = snap_rect.mTop; + break; + case SNAP_BOTTOM: + snap_pos = snap_rect.mBottom; + break; + } + + if (!mParentView) + { + new_edge_val = snap_pos; + return NULL; + } + + LLView* snap_view = NULL; + + // If the view is near the edge of its parent, snap it to + // the edge. + LLRect test_rect = snap_rect; + test_rect.stretch(padding); + + BOOL snapped_x = FALSE; + BOOL snapped_y = FALSE; + + LLRect parent_local_snap_rect = mParentView->getSnapRect(); + parent_local_snap_rect.translate(-mParentView->getRect().mLeft, -mParentView->getRect().mBottom); + + if (snap_type == SNAP_PARENT || snap_type == SNAP_PARENT_AND_SIBLINGS) + { + switch(snap_edge) + { + case SNAP_RIGHT: + if (llabs(parent_local_snap_rect.mRight - test_rect.mRight) <= threshold && (parent_local_snap_rect.mRight - test_rect.mRight) * mouse_dir.mX >= 0) + { + snap_pos = parent_local_snap_rect.mRight - padding; + snap_view = mParentView; + snapped_x = TRUE; + } + break; + case SNAP_LEFT: + if (llabs(test_rect.mLeft - parent_local_snap_rect.mLeft) <= threshold && test_rect.mLeft * mouse_dir.mX <= 0) + { + snap_pos = parent_local_snap_rect.mLeft + padding; + snap_view = mParentView; + snapped_x = TRUE; + } + break; + case SNAP_BOTTOM: + if (llabs(test_rect.mBottom - parent_local_snap_rect.mBottom) <= threshold && test_rect.mBottom * mouse_dir.mY <= 0) + { + snap_pos = parent_local_snap_rect.mBottom + padding; + snap_view = mParentView; + snapped_y = TRUE; + } + break; + case SNAP_TOP: + if (llabs(parent_local_snap_rect.mTop - test_rect.mTop) <= threshold && (parent_local_snap_rect.mTop - test_rect.mTop) * mouse_dir.mY >= 0) + { + snap_pos = parent_local_snap_rect.mTop - padding; + snap_view = mParentView; + snapped_y = TRUE; + } + break; + default: + llerrs << "Invalid snap edge" << llendl; + } + } + + if (snap_type == SNAP_SIBLINGS || snap_type == SNAP_PARENT_AND_SIBLINGS) + { + for ( child_list_const_iter_t child_it = mParentView->getChildList()->begin(); + child_it != mParentView->getChildList()->end(); ++child_it) + { + LLView* siblingp = *child_it; + // skip self + if (siblingp == this || !siblingp->getVisible() || !canSnapTo(siblingp)) + { + continue; + } + + LLRect sibling_rect = siblingp->getSnapRect(); + + switch(snap_edge) + { + case SNAP_RIGHT: + if (!snapped_x) + { + if (llabs(test_rect.mRight - sibling_rect.mLeft) <= threshold && (test_rect.mRight - sibling_rect.mLeft) * mouse_dir.mX <= 0) + { + snap_pos = sibling_rect.mLeft - padding; + snap_view = siblingp; + snapped_x = TRUE; + } + // if snapped with sibling along other axis, check for shared edge + else if (llabs(sibling_rect.mTop - (test_rect.mBottom - padding)) <= threshold || + llabs(sibling_rect.mBottom - (test_rect.mTop + padding)) <= threshold) + { + if (llabs(test_rect.mRight - sibling_rect.mRight) <= threshold && (test_rect.mRight - sibling_rect.mRight) * mouse_dir.mX <= 0) + { + snap_pos = sibling_rect.mRight; + snap_view = siblingp; + snapped_x = TRUE; + } + } + } + break; + case SNAP_LEFT: + if (!snapped_x) + { + if (llabs(test_rect.mLeft - sibling_rect.mRight) <= threshold && (test_rect.mLeft - sibling_rect.mRight) * mouse_dir.mX <= 0) + { + snap_pos = sibling_rect.mRight + padding; + snap_view = siblingp; + snapped_x = TRUE; + } + // if snapped with sibling along other axis, check for shared edge + else if (llabs(sibling_rect.mTop - (test_rect.mBottom - padding)) <= threshold || + llabs(sibling_rect.mBottom - (test_rect.mTop + padding)) <= threshold) + { + if (llabs(test_rect.mLeft - sibling_rect.mLeft) <= threshold && (test_rect.mLeft - sibling_rect.mLeft) * mouse_dir.mX <= 0) + { + snap_pos = sibling_rect.mLeft; + snap_view = siblingp; + snapped_x = TRUE; + } + } + } + break; + case SNAP_BOTTOM: + if (!snapped_y) + { + if (llabs(test_rect.mBottom - sibling_rect.mTop) <= threshold && (test_rect.mBottom - sibling_rect.mTop) * mouse_dir.mY <= 0) + { + snap_pos = sibling_rect.mTop + padding; + snap_view = siblingp; + snapped_y = TRUE; + } + // if snapped with sibling along other axis, check for shared edge + else if (llabs(sibling_rect.mRight - (test_rect.mLeft - padding)) <= threshold || + llabs(sibling_rect.mLeft - (test_rect.mRight + padding)) <= threshold) + { + if (llabs(test_rect.mBottom - sibling_rect.mBottom) <= threshold && (test_rect.mBottom - sibling_rect.mBottom) * mouse_dir.mY <= 0) + { + snap_pos = sibling_rect.mBottom; + snap_view = siblingp; + snapped_y = TRUE; + } + } + } + break; + case SNAP_TOP: + if (!snapped_y) + { + if (llabs(test_rect.mTop - sibling_rect.mBottom) <= threshold && (test_rect.mTop - sibling_rect.mBottom) * mouse_dir.mY <= 0) + { + snap_pos = sibling_rect.mBottom - padding; + snap_view = siblingp; + snapped_y = TRUE; + } + // if snapped with sibling along other axis, check for shared edge + else if (llabs(sibling_rect.mRight - (test_rect.mLeft - padding)) <= threshold || + llabs(sibling_rect.mLeft - (test_rect.mRight + padding)) <= threshold) + { + if (llabs(test_rect.mTop - sibling_rect.mTop) <= threshold && (test_rect.mTop - sibling_rect.mTop) * mouse_dir.mY <= 0) + { + snap_pos = sibling_rect.mTop; + snap_view = siblingp; + snapped_y = TRUE; + } + } + } + break; + default: + llerrs << "Invalid snap edge" << llendl; + } + if (snapped_x && snapped_y) + { + break; + } + } + } + + new_edge_val = snap_pos; + return snap_view; +} + +bool operator==(const LLViewHandle& lhs, const LLViewHandle& rhs) +{ + return lhs.mID == rhs.mID; +} + +bool operator!=(const LLViewHandle& lhs, const LLViewHandle& rhs) +{ + return lhs.mID != rhs.mID; +} + +bool operator<(const LLViewHandle &lhs, const LLViewHandle &rhs) +{ + return lhs.mID < rhs.mID; +} + +//----------------------------------------------------------------------------- +// Listener dispatch functions +//----------------------------------------------------------------------------- + +void LLView::registerEventListener(LLString name, LLSimpleListener* function) +{ + mDispatchList.insert(std::pair(name, function)); +} + +void LLView::deregisterEventListener(LLString name) +{ + dispatch_list_t::iterator itor = mDispatchList.find(name); + if (itor != mDispatchList.end()) + { + delete itor->second; + mDispatchList.erase(itor); + } +} + +LLString LLView::findEventListener(LLSimpleListener *listener) const +{ + dispatch_list_t::const_iterator itor; + for (itor = mDispatchList.begin(); itor != mDispatchList.end(); ++itor) + { + if (itor->second == listener) + { + return itor->first; + } + } + if (mParentView) + { + return mParentView->findEventListener(listener); + } + return LLString::null; +} + +LLSimpleListener* LLView::getListenerByName(const LLString& callback_name) +{ + LLSimpleListener* callback = NULL; + dispatch_list_t::iterator itor = mDispatchList.find(callback_name); + if (itor != mDispatchList.end()) + { + callback = itor->second; + } + else if (mParentView) + { + callback = mParentView->getListenerByName(callback_name); + } + return callback; +} + +void LLView::addListenerToControl(LLEventDispatcher *dispatcher, const LLString& name, LLSD filter, LLSD userdata) +{ + LLSimpleListener* listener = getListenerByName(name); + if (listener) + { + dispatcher->addListener(listener, filter, userdata); + } +} + +LLControlBase *LLView::findControl(LLString name) +{ + control_map_t::iterator itor = mFloaterControls.find(name); + if (itor != mFloaterControls.end()) + { + return itor->second; + } + if (mParentView) + { + return mParentView->findControl(name); + } + return LLUI::sConfigGroup->getControl(name); +} + +const S32 FLOATER_H_MARGIN = 15; +const S32 MIN_WIDGET_HEIGHT = 10; +const S32 VPAD = 4; + +// static +U32 LLView::createRect(LLXMLNodePtr node, LLRect &rect, LLView* parent_view, const LLRect &required_rect) +{ + U32 follows = 0; + S32 x = FLOATER_H_MARGIN; + S32 y = 0; + S32 w = 0; + S32 h = 0; + + U32 last_x = 0; + U32 last_y = 0; + if (parent_view) + { + last_y = parent_view->getRect().getHeight(); + child_list_t::const_iterator itor = parent_view->getChildList()->begin(); + if (itor != parent_view->getChildList()->end()) + { + LLView *last_view = (*itor); + if (last_view->getSaveToXML()) + { + last_x = last_view->getRect().mLeft; + last_y = last_view->getRect().mBottom; + } + } + } + + LLString rect_control; + node->getAttributeString("rect_control", rect_control); + if (! rect_control.empty()) + { + LLRect rect = LLUI::sConfigGroup->getRect(rect_control); + x = rect.mLeft; + y = rect.mBottom; + w = rect.getWidth(); + h = rect.getHeight(); + } + + if (node->hasAttribute("left")) + { + node->getAttributeS32("left", x); + } + if (node->hasAttribute("bottom")) + { + node->getAttributeS32("bottom", y); + } + + // Make your width the width of the containing + // view if you don't specify a width. + if (parent_view) + { + w = llmax(required_rect.getWidth(), parent_view->getRect().getWidth() - (FLOATER_H_MARGIN) - x); + h = llmax(MIN_WIDGET_HEIGHT, required_rect.getHeight()); + } + + if (node->hasAttribute("width")) + { + node->getAttributeS32("width", w); + } + if (node->hasAttribute("height")) + { + node->getAttributeS32("height", h); + } + + if (parent_view) + { + if (node->hasAttribute("left_delta")) + { + S32 left_delta = 0; + node->getAttributeS32("left_delta", left_delta); + x = last_x + left_delta; + } + else if (node->hasAttribute("left") && node->hasAttribute("right")) + { + // compute width based on left and right + S32 right = 0; + node->getAttributeS32("right", right); + if (right < 0) + { + right = parent_view->getRect().getWidth() + right; + } + w = right - x; + } + else if (node->hasAttribute("left")) + { + if (x < 0) + { + x = parent_view->getRect().getWidth() + x; + follows |= FOLLOWS_RIGHT; + } + else + { + follows |= FOLLOWS_LEFT; + } + } + else if (node->hasAttribute("width") && node->hasAttribute("right")) + { + S32 right = 0; + node->getAttributeS32("right", right); + if (right < 0) + { + right = parent_view->getRect().getWidth() + right; + } + x = right - w; + } + else + { + // left not specified, same as last + x = last_x; + } + + if (node->hasAttribute("bottom_delta")) + { + S32 bottom_delta = 0; + node->getAttributeS32("bottom_delta", bottom_delta); + y = last_y + bottom_delta; + } + else if (node->hasAttribute("top")) + { + // compute height based on top + S32 top = 0; + node->getAttributeS32("top", top); + if (top < 0) + { + top = parent_view->getRect().getHeight() + top; + } + h = top - y; + } + else if (node->hasAttribute("bottom")) + { + if (y < 0) + { + y = parent_view->getRect().getHeight() + y; + follows |= FOLLOWS_TOP; + } + else + { + follows |= FOLLOWS_BOTTOM; + } + } + else + { + // if bottom not specified, generate automatically + if (last_y == 0) + { + // treat first child as "bottom" + y = parent_view->getRect().getHeight() - (h + VPAD); + follows |= FOLLOWS_TOP; + } + else + { + // treat subsequent children as "bottom_delta" + y = last_y - (h + VPAD); + } + } + } + else + { + x = llmax(x, 0); + y = llmax(y, 0); + follows = FOLLOWS_LEFT | FOLLOWS_TOP; + } + rect.setOriginAndSize(x, y, w, h); + + return follows; +} + +void LLView::initFromXML(LLXMLNodePtr node, LLView* parent) +{ + // create rect first, as this will supply initial follows flags + LLRect view_rect; + U32 follows_flags = createRect(node, view_rect, parent, getRequiredRect()); + // call reshape in case there are any child elements that need to be layed out + reshape(view_rect.getWidth(), view_rect.getHeight()); + setRect(view_rect); + setFollows(follows_flags); + + if (node->hasAttribute("follows")) + { + setFollowsNone(); + + LLString follows; + node->getAttributeString("follows", follows); + + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("|"); + tokenizer tokens(follows, sep); + tokenizer::iterator token_iter = tokens.begin(); + + while(token_iter != tokens.end()) + { + const std::string& token_str = *token_iter; + if (token_str == "left") + { + setFollowsLeft(); + } + else if (token_str == "right") + { + setFollowsRight(); + } + else if (token_str == "top") + { + setFollowsTop(); + } + else if (token_str == "bottom") + { + setFollowsBottom(); + } + else if (token_str == "all") + { + setFollowsAll(); + } + ++token_iter; + } + } + + if (node->hasAttribute("control_name")) + { + LLString control_name; + node->getAttributeString("control_name", control_name); + setControlName(control_name, NULL); + } + + if (node->hasAttribute("tool_tip")) + { + LLString tool_tip_msg(""); + node->getAttributeString("tool_tip", tool_tip_msg); + setToolTip(tool_tip_msg); + } + + if (node->hasAttribute("enabled")) + { + BOOL enabled; + node->getAttributeBOOL("enabled", enabled); + setEnabled(enabled); + } + + if (node->hasAttribute("visible")) + { + BOOL visible; + node->getAttributeBOOL("visible", visible); + setVisible(visible); + } + + if (node->hasAttribute("hidden")) + { + BOOL hidden; + node->getAttributeBOOL("hidden", hidden); + setHidden(hidden); + } + + node->getAttributeS32("default_tab_group", mDefaultTabGroup); + + reshape(view_rect.getWidth(), view_rect.getHeight()); +} + +// static +LLFontGL* LLView::selectFont(LLXMLNodePtr node) +{ + LLFontGL* gl_font = NULL; + + if (node->hasAttribute("font")) + { + LLString font_name; + node->getAttributeString("font", font_name); + + gl_font = LLFontGL::fontFromName(font_name.c_str()); + } + return gl_font; +} + +// static +LLFontGL::HAlign LLView::selectFontHAlign(LLXMLNodePtr node) +{ + LLFontGL::HAlign gl_hfont_align = LLFontGL::LEFT; + + if (node->hasAttribute("halign")) + { + LLString horizontal_align_name; + node->getAttributeString("halign", horizontal_align_name); + gl_hfont_align = LLFontGL::hAlignFromName(horizontal_align_name); + } + return gl_hfont_align; +} + +// static +LLFontGL::VAlign LLView::selectFontVAlign(LLXMLNodePtr node) +{ + LLFontGL::VAlign gl_vfont_align = LLFontGL::BASELINE; + + if (node->hasAttribute("valign")) + { + LLString vert_align_name; + node->getAttributeString("valign", vert_align_name); + gl_vfont_align = LLFontGL::vAlignFromName(vert_align_name); + } + return gl_vfont_align; +} + +// static +LLFontGL::StyleFlags LLView::selectFontStyle(LLXMLNodePtr node) +{ + LLFontGL::StyleFlags gl_font_style = LLFontGL::NORMAL; + + if (node->hasAttribute("style")) + { + LLString style_flags_name; + node->getAttributeString("style", style_flags_name); + + if (style_flags_name == "normal") + { + gl_font_style = LLFontGL::NORMAL; + } + else if (style_flags_name == "bold") + { + gl_font_style = LLFontGL::BOLD; + } + else if (style_flags_name == "italic") + { + gl_font_style = LLFontGL::ITALIC; + } + else if (style_flags_name == "underline") + { + gl_font_style = LLFontGL::UNDERLINE; + } + //else leave left + } + return gl_font_style; +} + +void LLView::setControlValue(const LLSD& value) +{ + LLUI::sConfigGroup->setValue(getControlName(), value); +} + +//virtual +LLString LLView::getControlName() const +{ + return mControlName; +} + +//virtual +void LLView::setControlName(const LLString& control_name, LLView *context) +{ + if (context == NULL) + { + context = this; + } + + // Unregister from existing listeners + if (!mControlName.empty()) + { + clearDispatchers(); + } + + // Register new listener + if (!control_name.empty()) + { + LLControlBase *control = context->findControl(control_name); + if (control) + { + mControlName = control_name; + LLSD state = control->registerListener(this, "DEFAULT"); + setValue(state); + } + } +} + +// virtual +bool LLView::handleEvent(LLPointer event, const LLSD& userdata) +{ + if (userdata.asString() == "DEFAULT" && event->desc() == "value_changed") + { + LLSD state = event->getValue(); + setValue(state); + return TRUE; + } + return FALSE; +} + +void LLView::setValue(const LLSD& value) +{ +} + + +void LLView::addBoolControl(LLString name, bool initial_value) +{ + mFloaterControls[name] = new LLControl(name, TYPE_BOOLEAN, initial_value, "Internal floater control"); +} + +LLControlBase *LLView::getControl(LLString name) +{ + control_map_t::iterator itor = mFloaterControls.find(name); + if (itor != mFloaterControls.end()) + { + return itor->second; + } + return NULL; +} diff --git a/indra/llui/llview.h b/indra/llui/llview.h new file mode 100644 index 0000000000..63d85fbcdc --- /dev/null +++ b/indra/llui/llview.h @@ -0,0 +1,489 @@ +/** + * @file llview.h + * @brief Container for other views, anything that draws. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLVIEW_H +#define LL_LLVIEW_H + +// A view is an area in a window that can draw. It might represent +// the HUD or a dialog box or a button. It can also contain sub-views +// and child widgets + +#include +#include + +#include "lluixmltags.h" +#include "llrect.h" +#include "llmousehandler.h" +#include "stdenums.h" +#include "llsd.h" +#include "llstring.h" +#include "llnametable.h" +#include "llcoord.h" +#include "llmortician.h" +#include "llxmlnode.h" +#include "llfontgl.h" +#include "llviewquery.h" + +#include "llui.h" + +class LLColor4; +class LLWindow; +class LLUICtrl; +class LLScrollListItem; + +const U32 FOLLOWS_NONE = 0x00; +const U32 FOLLOWS_LEFT = 0x01; +const U32 FOLLOWS_RIGHT = 0x02; +const U32 FOLLOWS_TOP = 0x10; +const U32 FOLLOWS_BOTTOM = 0x20; +const U32 FOLLOWS_ALL = 0x33; + +const BOOL MOUSE_OPAQUE = TRUE; +const BOOL NOT_MOUSE_OPAQUE = FALSE; + +const U32 GL_NAME_UI_RESERVED = 2; + +class LLSimpleListener; +class LLEventDispatcher; + +class LLViewHandle +{ +public: + LLViewHandle() { mID = 0; } + + void init() { mID = ++sNextID; } + void markDead() { mID = 0; } + BOOL isDead() { return (mID == 0); } + friend bool operator==(const LLViewHandle& lhs, const LLViewHandle& rhs); + friend bool operator!=(const LLViewHandle& lhs, const LLViewHandle& rhs); + friend bool operator<(const LLViewHandle &a, const LLViewHandle &b); + +public: + static LLViewHandle sDeadHandle; + +protected: + S32 mID; + + static S32 sNextID; +}; + +class LLView : public LLMouseHandler, public LLMortician, public LLSimpleListenerObservable +{ + +public: +#if LL_DEBUG + static BOOL sIsDrawing; +#endif + enum ESoundFlags + { + SILENT = 0, + MOUSE_DOWN = 1, + MOUSE_UP = 2 + }; + + enum ESnapType + { + SNAP_PARENT, + SNAP_SIBLINGS, + SNAP_PARENT_AND_SIBLINGS + }; + + enum ESnapEdge + { + SNAP_LEFT, + SNAP_TOP, + SNAP_RIGHT, + SNAP_BOTTOM + }; + + typedef std::list child_list_t; + typedef child_list_t::iterator child_list_iter_t; + typedef child_list_t::const_iterator child_list_const_iter_t; + typedef child_list_t::reverse_iterator child_list_reverse_iter_t; + typedef child_list_t::const_reverse_iterator child_list_const_reverse_iter_t; + + typedef std::vector ctrl_list_t; + + typedef std::pair tab_order_t; + typedef std::pair tab_order_pair_t; + // this structure primarily sorts by the tab group, secondarily by the insertion ordinal (lastly by the value of the pointer) + typedef std::map child_tab_order_t; + typedef child_tab_order_t::iterator child_tab_order_iter_t; + typedef child_tab_order_t::const_iterator child_tab_order_const_iter_t; + typedef child_tab_order_t::reverse_iterator child_tab_order_reverse_iter_t; + typedef child_tab_order_t::const_reverse_iterator child_tab_order_const_reverse_iter_t; + +private: + LLView* mParentView; + child_list_t mChildList; + +protected: + LLString mName; + // location in pixels, relative to surrounding structure, bottom,left=0,0 + LLRect mRect; + + U32 mReshapeFlags; + + child_tab_order_t mCtrlOrder; + S32 mDefaultTabGroup; + + BOOL mEnabled; // Enabled means "accepts input that has an effect on the state of the application." + // A disabled view, for example, may still have a scrollbar that responds to mouse events. + BOOL mMouseOpaque; // Opaque views handle all mouse events that are over their rect. + LLString mToolTipMsg; // isNull() is true if none. + + U8 mSoundFlags; + BOOL mSaveToXML; + + BOOL mIsFocusRoot; + +public: + LLViewHandle mViewHandle; + BOOL mLastVisible; + BOOL mRenderInFastFrame; + BOOL mSpanChildren; + +private: + BOOL mVisible; + BOOL mHidden; // Never show (generally for replacement text only) + + S32 mNextInsertionOrdinal; + +protected: + static LLWindow* sWindow; // All root views must know about their window. + +public: + static BOOL sDebugRects; // Draw debug rects behind everything. + static BOOL sDebugKeys; + static S32 sDepth; + static LLView* sFastFrameView; + static BOOL sDebugMouseHandling; + static LLString sMouseHandlerMessage; + static S32 sSelectID; + static BOOL sEditingUI; + static LLView* sEditingUIView; + static S32 sLastLeftXML; + static S32 sLastBottomXML; + static std::map sViewHandleMap; + static BOOL sForceReshape; + +public: + static LLView* getViewByHandle(LLViewHandle handle); + static BOOL deleteViewByHandle(LLViewHandle handle); + +public: + LLView(); + LLView(const LLString& name, BOOL mouse_opaque); + LLView(const LLString& name, const LLRect& rect, BOOL mouse_opaque, U32 follows=FOLLOWS_NONE); + + virtual ~LLView(); + + // Hack to support LLFocusMgr + virtual BOOL isView(); + + // Some UI widgets need to be added as controls. Others need to + // be added as regular view children. isCtrl should return TRUE + // if a widget needs to be added as a ctrl + virtual BOOL isCtrl() const; + + virtual BOOL isPanel(); + + // + // MANIPULATORS + // + void setMouseOpaque( BOOL b ); + void setToolTip( const LLString& msg ); + + virtual void setRect(const LLRect &rect); + void setFollows(U32 flags); + + // deprecated, use setFollows() with FOLLOWS_LEFT | FOLLOWS_TOP, etc. + void setFollowsNone(); + void setFollowsLeft(); + void setFollowsTop(); + void setFollowsRight(); + void setFollowsBottom(); + void setFollowsAll(); + + void setSoundFlags(U8 flags); + void setName(LLString name); + void setSpanChildren( BOOL span_children ); + + const LLString& getToolTip(); + + void sendChildToFront(LLView* child); + void sendChildToBack(LLView* child); + void moveChildToFrontOfTabGroup(LLUICtrl* child); + + void addChild(LLView* view, S32 tab_group = 0); + void addChildAtEnd(LLView* view, S32 tab_group = 0); + // remove the specified child from the view, and set it's parent to NULL. + void removeChild( LLView* view ); + + virtual void addCtrl( LLUICtrl* ctrl, S32 tab_group); + virtual void addCtrlAtEnd( LLUICtrl* ctrl, S32 tab_group); + virtual void removeCtrl( LLUICtrl* ctrl); + + child_tab_order_t getCtrlOrder() const { return mCtrlOrder; } + ctrl_list_t getCtrlList() const; + ctrl_list_t getCtrlListSorted() const; + S32 getDefaultTabGroup() const; + + BOOL isInVisibleChain() const; + BOOL isInEnabledChain() const; + + BOOL isFocusRoot() const; + LLView* findRootMostFocusRoot(); + virtual BOOL canFocusChildren() const; + + class LLFocusRootsFilter : public LLQueryFilter, public LLSingleton + { + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const + { + return filterResult_t(view->isCtrl() && view->isFocusRoot(), !view->isFocusRoot()); + } + }; + + virtual BOOL focusNextRoot(); + virtual BOOL focusPrevRoot(); + + virtual BOOL focusNextItem(BOOL text_entry_only); + virtual BOOL focusPrevItem(BOOL text_entry_only); + virtual BOOL focusFirstItem(BOOL prefer_text_fields = FALSE ); + virtual BOOL focusLastItem(BOOL prefer_text_fields = FALSE); + + // delete all children. Override this function if you need to + // perform any extra clean up such as cached pointers to selected + // children, etc. + virtual void deleteAllChildren(); + + // by default, does nothing + virtual void setTentative(BOOL b); + // by default, returns false + virtual BOOL getTentative() const; + virtual void setAllChildrenEnabled(BOOL b); + + virtual void setEnabled(BOOL enabled); + virtual void setVisible(BOOL visible); + virtual void setHidden(BOOL hidden); // Never show (replacement text) + + // by default, does nothing and returns false + virtual BOOL setLabelArg( const LLString& key, const LLString& text ); + + virtual void onVisibilityChange ( BOOL curVisibilityIn ); + + void pushVisible(BOOL visible) { mLastVisible = mVisible; setVisible(visible); } + void popVisible() { setVisible(mLastVisible); mLastVisible = TRUE; } + + // + // ACCESSORS + // + BOOL getMouseOpaque() const { return mMouseOpaque; } + + U32 getFollows() const { return mReshapeFlags; } + BOOL followsLeft() const { return mReshapeFlags & FOLLOWS_LEFT; } + BOOL followsRight() const { return mReshapeFlags & FOLLOWS_RIGHT; } + BOOL followsTop() const { return mReshapeFlags & FOLLOWS_TOP; } + BOOL followsBottom() const { return mReshapeFlags & FOLLOWS_BOTTOM; } + BOOL followsAll() const { return mReshapeFlags & FOLLOWS_ALL; } + + const LLRect& getRect() const { return mRect; } + const LLRect getScreenRect() const; + const LLRect getLocalRect() const; + virtual const LLRect getSnapRect() const { return mRect; } + + virtual LLRect getRequiredRect(); // Get required size for this object. 0 for width/height means don't care. + virtual void updateRect(); // apply procedural updates to own rectangle + + LLView* getRootView(); + LLView* getParent() const { return mParentView; } + LLView* getFirstChild() { return (mChildList.empty()) ? NULL : *(mChildList.begin()); } + S32 getChildCount() const { return (S32)mChildList.size(); } + template void sortChildren(_Pr3 _Pred) { mChildList.sort(_Pred); } + BOOL hasAncestor(LLView* parentp); + + BOOL hasChild(const LLString& childname, BOOL recurse = FALSE) const; + + BOOL childHasKeyboardFocus( const LLString& childname ) const; + + // + // UTILITIES + // + + // Default behavior is to use reshape flags to resize child views + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + + virtual void translate( S32 x, S32 y ); + BOOL translateIntoRect( const LLRect& constraint, BOOL allow_partial_outside ); + void setOrigin( S32 x, S32 y ) { mRect.translate( x - mRect.mLeft, y - mRect.mBottom ); } + LLView* findSnapRect(LLRect& new_rect, const LLCoordGL& mouse_dir, LLView::ESnapType snap_type, S32 threshold, S32 padding = 0); + LLView* findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding = 0); + + // Defaults to other_view->getVisible() + virtual BOOL canSnapTo(LLView* other_view); + + virtual void snappedTo(LLView* snap_view); + + virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleUnicodeChar(llwchar uni_char, BOOL called_from_parent); + virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + LLString& tooltip_msg); + + // LLMouseHandler functions + // Default behavior is to pass events to children + + /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); + /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleRightMouseUp(S32 x, S32 y, MASK mask); + + // Default behavior is to pass the tooltip event to children, + // then display mToolTipMsg if no child handled it. + /*virtual*/ BOOL handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect); + + virtual void draw(); + + void drawDebugRect(); + void drawChild(LLView* childp, S32 x_offset = 0, S32 y_offset = 0); + + virtual const LLString& getName() const; + + virtual EWidgetType getWidgetType() const = 0; + virtual LLString getWidgetTag() const = 0; + virtual LLXMLNodePtr getXML(bool save_children = true) const; + + static U32 createRect(LLXMLNodePtr node, LLRect &rect, LLView* parent_view, const LLRect &required_rect = LLRect()); + virtual void initFromXML(LLXMLNodePtr node, LLView* parent); + + static LLFontGL* selectFont(LLXMLNodePtr node); + static LLFontGL::HAlign selectFontHAlign(LLXMLNodePtr node); + static LLFontGL::VAlign selectFontVAlign(LLXMLNodePtr node); + static LLFontGL::StyleFlags selectFontStyle(LLXMLNodePtr node); + + // Some widgets, like close box buttons, don't need to be saved + BOOL getSaveToXML() const { return mSaveToXML; } + void setSaveToXML(BOOL b) { mSaveToXML = b; } + + // Only saves color if different from default setting. + static void addColorXML(LLXMLNodePtr node, const LLColor4& color, + const LLString& xml_name, const LLString& control_name); + static void saveColorToXML(std::ostream& out, const LLColor4& color, + const LLString& xml_name, const LLString& control_name, + const LLString& indent); // DEPRECATED + // Escapes " (quot) ' (apos) & (amp) < (lt) > (gt) + //static LLString escapeXML(const LLString& xml); + static LLWString escapeXML(const LLWString& xml); + + //same as above, but wraps multiple lines in quotes and prepends + //indent as leading white space on each line + static LLString escapeXML(const LLString& xml, LLString& indent); + + // focuses the item in the list after the currently-focused item, wrapping if necessary + static BOOL focusNext(LLView::child_list_t & result); + // focuses the item in the list before the currently-focused item, wrapping if necessary + static BOOL focusPrev(LLView::child_list_t & result); + + // returns query for iterating over controls in tab order + static const LLCtrlQuery & getTabOrderQuery(); + // return query for iterating over focus roots in tab order + static const LLCtrlQuery & getFocusRootsQuery(); + + BOOL getEnabled() const { return mEnabled; } + BOOL getVisible() const { return mVisible && !mHidden; } + U8 getSoundFlags() const { return mSoundFlags; } + + // Default to no action + virtual void onFocusLost(); + virtual void onFocusReceived(); + + BOOL parentPointInView(S32 x, S32 y) const { return mRect.pointInRect( x, y ); } + BOOL pointInView(S32 x, S32 y) const { return mRect.localPointInRect( x, y ); } + virtual void screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const; + virtual void localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const; + virtual BOOL localPointToOtherView( S32 x, S32 y, S32 *other_x, S32 *other_y, LLView* other_view); + virtual void screenRectToLocal( const LLRect& screen, LLRect* local ) const; + virtual void localRectToScreen( const LLRect& local, LLRect* screen ) const; + virtual BOOL localRectToOtherView( const LLRect& local, LLRect* other, LLView* other_view ) const; + + void setRenderInFastFrame(BOOL render) { mRenderInFastFrame = render; } + virtual LLView* getRootMostFastFrameView(); + + static LLWindow* getWindow(void); + + // Listener dispatching functions (Dispatcher deletes pointers to listeners on deregistration or destruction) + LLSimpleListener* getListenerByName(const LLString &callback_name); + void registerEventListener(LLString name, LLSimpleListener* function); + void deregisterEventListener(LLString name); + LLString findEventListener(LLSimpleListener *listener) const; + void addListenerToControl(LLEventDispatcher *observer, const LLString& name, LLSD filter, LLSD userdata); + + virtual LLView* getChildByName(const LLString& name, BOOL recurse = FALSE) const; + + void addBoolControl(LLString name, bool initial_value); + LLControlBase *getControl(LLString name); + virtual LLControlBase *findControl(LLString name); + + void setControlValue(const LLSD& value); + virtual void setControlName(const LLString& control, LLView *context); + virtual LLString getControlName() const; + virtual bool handleEvent(LLPointer event, const LLSD& userdata); + virtual void setValue(const LLSD& value); + const child_list_t* getChildList() const { return &mChildList; } + +protected: + virtual BOOL handleKeyHere(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent); + + LLView* childrenHandleKey(KEY key, MASK mask); + LLView* childrenHandleDragAndDrop(S32 x, S32 y, MASK mask, + BOOL drop, + EDragAndDropType type, + void* data, + EAcceptance* accept, + LLString& tooltip_msg); + + LLView* childrenHandleHover(S32 x, S32 y, MASK mask); + LLView* childrenHandleMouseUp(S32 x, S32 y, MASK mask); + LLView* childrenHandleMouseDown(S32 x, S32 y, MASK mask); + LLView* childrenHandleDoubleClick(S32 x, S32 y, MASK mask); + LLView* childrenHandleScrollWheel(S32 x, S32 y, S32 clicks); + LLView* childrenHandleRightMouseDown(S32 x, S32 y, MASK mask); + LLView* childrenHandleRightMouseUp(S32 x, S32 y, MASK mask); + + typedef std::map dispatch_list_t; + dispatch_list_t mDispatchList; + +protected: + typedef std::map control_map_t; + control_map_t mFloaterControls; + + LLString mControlName; + friend class LLUICtrlFactory; +}; + + + + +class LLCompareByTabOrder +{ +public: + LLCompareByTabOrder(LLView::child_tab_order_t order); + virtual ~LLCompareByTabOrder() {} + bool operator() (const LLView* const a, const LLView* const b) const; +protected: + virtual bool compareTabOrders(const LLView::tab_order_t & a, const LLView::tab_order_t & b) const; + LLView::child_tab_order_t mTabOrder; +}; + +#endif diff --git a/indra/llui/llviewborder.cpp b/indra/llui/llviewborder.cpp new file mode 100644 index 0000000000..24cc57e709 --- /dev/null +++ b/indra/llui/llviewborder.cpp @@ -0,0 +1,337 @@ +/** + * @file llviewborder.cpp + * @brief LLViewBorder base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// A customizable decorative border. Does not interact with mouse events. + +#include "linden_common.h" + +#include "llviewborder.h" + +#include "llgl.h" +#include "llui.h" +#include "llimagegl.h" +//#include "llviewerimagelist.h" +#include "llcontrol.h" +#include "llglheaders.h" +#include "v2math.h" +#include "llfocusmgr.h" + +LLViewBorder::LLViewBorder( const LLString& name, const LLRect& rect, EBevel bevel, EStyle style, S32 width ) + : + LLView( name, rect, FALSE ), + mBevel( bevel ), + mStyle( style ), + mHighlightLight( LLUI::sColorsGroup->getColor( "DefaultHighlightLight" ) ), + mHighlightDark( LLUI::sColorsGroup->getColor( "DefaultHighlightDark" ) ), + mShadowLight( LLUI::sColorsGroup->getColor( "DefaultShadowLight" ) ), + mShadowDark( LLUI::sColorsGroup->getColor( "DefaultShadowDark" ) ), +// mKeyboardFocusColor(LLUI::sColorsGroup->getColor( "FocusColor" ) ), + mBorderWidth( width ), + mTexture( NULL ), + mHasKeyboardFocus( FALSE ) +{ + setFollowsAll(); +} + +// virtual +BOOL LLViewBorder::isCtrl() +{ + return FALSE; +} + +void LLViewBorder::setColors( const LLColor4& shadow_dark, const LLColor4& highlight_light ) +{ + mShadowDark = shadow_dark; + mHighlightLight = highlight_light; +} + +void LLViewBorder::setColorsExtended( const LLColor4& shadow_light, const LLColor4& shadow_dark, + const LLColor4& highlight_light, const LLColor4& highlight_dark ) +{ + mShadowDark = shadow_dark; + mShadowLight = shadow_light; + mHighlightLight = highlight_light; + mHighlightDark = highlight_dark; +} + +void LLViewBorder::setTexture( const LLUUID &image_id ) +{ + mTexture = LLUI::sImageProvider->getUIImageByID(image_id); +} + + +void LLViewBorder::draw() +{ + if( getVisible() ) + { + if( STYLE_LINE == mStyle ) + { + if( 0 == mBorderWidth ) + { + // no visible border + } + else + if( 1 == mBorderWidth ) + { + drawOnePixelLines(); + } + else + if( 2 == mBorderWidth ) + { + drawTwoPixelLines(); + } + else + { + llassert( FALSE ); // not implemented + } + } + else + if( STYLE_TEXTURE == mStyle ) + { + if( mTexture ) + { + drawTextures(); + } + } + + // draw the children + LLView::draw(); + } +} + +void LLViewBorder::drawOnePixelLines() +{ + LLGLSNoTexture uiNoTexture; + + LLColor4 top_color = mHighlightLight; + LLColor4 bottom_color = mHighlightLight; + switch( mBevel ) + { + case BEVEL_OUT: + top_color = mHighlightLight; + bottom_color = mShadowDark; + break; + case BEVEL_IN: + top_color = mShadowDark; + bottom_color = mHighlightLight; + break; + case BEVEL_NONE: + // use defaults + break; + default: + llassert(0); + } + + if( mHasKeyboardFocus ) + { + F32 lerp_amt = gFocusMgr.getFocusFlashAmt(); + top_color = gFocusMgr.getFocusColor(); + bottom_color = top_color; + + LLUI::setLineWidth(lerp(1.f, 3.f, lerp_amt)); + } + + S32 left = 0; + S32 top = mRect.getHeight(); + S32 right = mRect.getWidth(); + S32 bottom = 0; + + glColor4fv( top_color.mV ); + gl_line_2d(left, bottom, left, top); + gl_line_2d(left, top, right, top); + + glColor4fv( bottom_color.mV ); + gl_line_2d(right, top, right, bottom); + gl_line_2d(left, bottom, right, bottom); + + LLUI::setLineWidth(1.f); +} + +void LLViewBorder::drawTwoPixelLines() +{ + LLGLSNoTexture no_texture; + + LLColor4 focus_color = gFocusMgr.getFocusColor(); + + F32* top_in_color = mShadowDark.mV; + F32* top_out_color = mShadowDark.mV; + F32* bottom_in_color = mShadowDark.mV; + F32* bottom_out_color = mShadowDark.mV; + switch( mBevel ) + { + case BEVEL_OUT: + top_in_color = mHighlightLight.mV; + top_out_color = mHighlightDark.mV; + bottom_in_color = mShadowLight.mV; + bottom_out_color = mShadowDark.mV; + break; + case BEVEL_IN: + top_in_color = mShadowDark.mV; + top_out_color = mShadowLight.mV; + bottom_in_color = mHighlightDark.mV; + bottom_out_color = mHighlightLight.mV; + break; + case BEVEL_BRIGHT: + top_in_color = mHighlightLight.mV; + top_out_color = mHighlightLight.mV; + bottom_in_color = mHighlightLight.mV; + bottom_out_color = mHighlightLight.mV; + break; + case BEVEL_NONE: + // use defaults + break; + default: + llassert(0); + } + + if( mHasKeyboardFocus ) + { + top_out_color = focus_color.mV; + bottom_out_color = focus_color.mV; + } + + S32 left = 0; + S32 top = mRect.getHeight(); + S32 right = mRect.getWidth(); + S32 bottom = 0; + + // draw borders + glColor3fv( top_out_color ); + gl_line_2d(left, bottom, left, top-1); + gl_line_2d(left, top-1, right, top-1); + + glColor3fv( top_in_color ); + gl_line_2d(left+1, bottom+1, left+1, top-2); + gl_line_2d(left+1, top-2, right-1, top-2); + + glColor3fv( bottom_out_color ); + gl_line_2d(right-1, top-1, right-1, bottom); + gl_line_2d(left, bottom, right, bottom); + + glColor3fv( bottom_in_color ); + gl_line_2d(right-2, top-2, right-2, bottom+1); + gl_line_2d(left+1, bottom+1, right-1, bottom+1); +} + +void LLViewBorder::drawTextures() +{ + LLGLSUIDefault gls_ui; + + llassert( FALSE ); // TODO: finish implementing + + glColor4fv(UI_VERTEX_COLOR.mV); + + mTexture->bind(); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); + + drawTextureTrapezoid( 0.f, mBorderWidth, mRect.getWidth(), 0, 0 ); + drawTextureTrapezoid( 90.f, mBorderWidth, mRect.getHeight(), (F32)mRect.getWidth(),0 ); + drawTextureTrapezoid( 180.f, mBorderWidth, mRect.getWidth(), (F32)mRect.getWidth(),(F32)mRect.getHeight() ); + drawTextureTrapezoid( 270.f, mBorderWidth, mRect.getHeight(), 0, (F32)mRect.getHeight() ); +} + + +void LLViewBorder::drawTextureTrapezoid( F32 degrees, S32 width, S32 length, F32 start_x, F32 start_y ) +{ + glPushMatrix(); + { + glTranslatef(start_x, start_y, 0.f); + glRotatef( degrees, 0, 0, 1 ); + + glBegin(GL_QUADS); + { + // width, width /---------\ length-width, width // + // / \ // + // / \ // + // /---------------\ // + // 0,0 length, 0 // + + glTexCoord2f( 0, 0 ); + glVertex2i( 0, 0 ); + + glTexCoord2f( (GLfloat)length, 0 ); + glVertex2i( length, 0 ); + + glTexCoord2f( (GLfloat)(length - width), (GLfloat)width ); + glVertex2i( length - width, width ); + + glTexCoord2f( (GLfloat)width, (GLfloat)width ); + glVertex2i( width, width ); + } + glEnd(); + } + glPopMatrix(); +} + +bool LLViewBorder::getBevelFromAttribute(LLXMLNodePtr node, LLViewBorder::EBevel& bevel_style) +{ + if (node->hasAttribute("bevel_style")) + { + LLString bevel_string; + node->getAttributeString("bevel_style", bevel_string); + LLString::toLower(bevel_string); + + if (bevel_string == "none") + { + bevel_style = LLViewBorder::BEVEL_NONE; + } + else if (bevel_string == "in") + { + bevel_style = LLViewBorder::BEVEL_IN; + } + else if (bevel_string == "out") + { + bevel_style = LLViewBorder::BEVEL_OUT; + } + else if (bevel_string == "bright") + { + bevel_style = LLViewBorder::BEVEL_BRIGHT; + } + return true; + } + return false; +} + +void LLViewBorder::setValue(const LLSD& val) +{ + setRect(LLRect(val)); +} + +EWidgetType LLViewBorder::getWidgetType() const +{ + return WIDGET_TYPE_VIEW_BORDER; +} + +LLString LLViewBorder::getWidgetTag() const +{ + return LL_VIEW_BORDER_TAG; +} + +// static +LLView* LLViewBorder::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +{ + LLString name("view_border"); + node->getAttributeString("name", name); + + LLViewBorder::EBevel bevel_style = LLViewBorder::BEVEL_IN; + getBevelFromAttribute(node, bevel_style); + + S32 border_thickness = 1; + node->getAttributeS32("border_thickness", border_thickness); + + LLViewBorder* border = new LLViewBorder(name, + LLRect(), + bevel_style, + LLViewBorder::STYLE_LINE, + border_thickness); + + border->initFromXML(node, parent); + + return border; +} diff --git a/indra/llui/llviewborder.h b/indra/llui/llviewborder.h new file mode 100644 index 0000000000..984eee6e60 --- /dev/null +++ b/indra/llui/llviewborder.h @@ -0,0 +1,77 @@ +/** + * @file llviewborder.h + * @brief LLViewBorder base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// A customizable decorative border. Does not interact with mouse events. + +#ifndef LL_LLVIEWBORDER_H +#define LL_LLVIEWBORDER_H + +#include "llview.h" +#include "v4color.h" +#include "lluuid.h" +#include "llimagegl.h" +#include "llxmlnode.h" + +class LLUUID; + + +class LLViewBorder : public LLView +{ +public: + enum EBevel { BEVEL_IN, BEVEL_OUT, BEVEL_BRIGHT, BEVEL_NONE }; + + enum EStyle { STYLE_LINE, STYLE_TEXTURE }; + + LLViewBorder( const LLString& name, const LLRect& rect, EBevel bevel = BEVEL_OUT, EStyle style = STYLE_LINE, S32 width = 1 ); + + virtual void setValue(const LLSD& val); + virtual EWidgetType getWidgetType() const; + virtual LLString getWidgetTag() const; + + virtual BOOL isCtrl(); + + // llview functionality + virtual void draw(); + + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + static bool getBevelFromAttribute(LLXMLNodePtr node, LLViewBorder::EBevel& bevel_style); + + void setBorderWidth(S32 width) { mBorderWidth = width; } + void setBevel(EBevel bevel) { mBevel = bevel; } + void setColors( const LLColor4& shadow_dark, const LLColor4& highlight_light ); + void setColorsExtended( const LLColor4& shadow_light, const LLColor4& shadow_dark, + const LLColor4& highlight_light, const LLColor4& highlight_dark ); + void setTexture( const LLUUID &image_id ); + + EBevel getBevel() const { return mBevel; } + EStyle getStyle() const { return mStyle; } + S32 getBorderWidth() const { return mBorderWidth; } + + void setKeyboardFocusHighlight( BOOL b ) { mHasKeyboardFocus = b; } + +protected: + void drawOnePixelLines(); + void drawTwoPixelLines(); + void drawTextures(); + void drawTextureTrapezoid( F32 degrees, S32 width, S32 length, F32 start_x, F32 start_y ); + +protected: + EBevel mBevel; + EStyle mStyle; + LLColor4 mHighlightLight; + LLColor4 mHighlightDark; + LLColor4 mShadowLight; + LLColor4 mShadowDark; + LLColor4 mBackgroundColor; + S32 mBorderWidth; + LLPointer mTexture; + BOOL mHasKeyboardFocus; +}; + +#endif // LL_LLVIEWBORDER_H + diff --git a/indra/llui/llviewquery.cpp b/indra/llui/llviewquery.cpp new file mode 100644 index 0000000000..416ca623bc --- /dev/null +++ b/indra/llui/llviewquery.cpp @@ -0,0 +1,126 @@ +/** + * @file llviewquery.cpp + * @brief Implementation of view query class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "llview.h" +#include "lluictrl.h" +#include "llviewquery.h" + +void LLQuerySorter::operator() (LLView * parent, viewList_t &children) const {} + +filterResult_t LLNoLeavesFilter::operator() (const LLView* const view, const viewList_t & children) const +{ + return filterResult_t(!(view->getChildList()->size() == 0), TRUE); +} + +filterResult_t LLVisibleFilter::operator() (const LLView* const view, const viewList_t & children) const +{ + return filterResult_t(view->getVisible(), view->getVisible()); +} +filterResult_t LLEnabledFilter::operator() (const LLView* const view, const viewList_t & children) const +{ + return filterResult_t(view->getEnabled(), view->getEnabled()); +} +filterResult_t LLTabStopFilter::operator() (const LLView* const view, const viewList_t & children) const +{ + return filterResult_t(view->isCtrl() && static_cast(view)->hasTabStop(), + view->canFocusChildren()); +} + +// LLViewQuery + +LLViewQuery::LLViewQuery(): mPreFilters(), mPostFilters(), mSorterp() +{ +} + +void LLViewQuery::addPreFilter(const LLQueryFilter* prefilter) { mPreFilters.push_back(prefilter); } + +void LLViewQuery::addPostFilter(const LLQueryFilter* postfilter) { mPostFilters.push_back(postfilter); } + +const LLViewQuery::filterList_t & LLViewQuery::getPreFilters() const { return mPreFilters; } + +const LLViewQuery::filterList_t & LLViewQuery::getPostFilters() const { return mPostFilters; } + +void LLViewQuery::setSorter(const LLQuerySorter* sorterp) { mSorterp = sorterp; } +const LLQuerySorter* LLViewQuery::getSorter() const { return mSorterp; } + +viewList_t LLViewQuery::run(LLView * view) const +{ + viewList_t result; + + filterResult_t pre = runFilters(view, viewList_t(), mPreFilters); + if(!pre.first && !pre.second) + { + // skip post filters completely if we're not including ourselves or the children + return result; + } + if(pre.second) + { + // run filters on children + viewList_t filtered_children; + filterChildren(view, filtered_children); + filterResult_t post = runFilters(view, filtered_children, mPostFilters); + if(pre.first && post.first) + { + result.push_back(view); + } + if(post.second) + { + result.insert(result.end(), filtered_children.begin(), filtered_children.end()); + } + } + else + { + if(pre.first) + { + result.push_back(view); + } + } + return result; +} + +void LLViewQuery::filterChildren(LLView * view, viewList_t & filtered_children) const +{ + LLView::child_list_t views(*(view->getChildList())); + (*mSorterp)(view, views); // sort the children per the sorter + for(LLView::child_list_iter_t iter = views.begin(); + iter != views.end(); + iter++) + { + viewList_t indiv_children = this->run(*iter); + filtered_children.insert(filtered_children.end(), indiv_children.begin(), indiv_children.end()); + } +} + +filterResult_t LLViewQuery::runFilters(LLView * view, const viewList_t children, const filterList_t filters) const +{ + filterResult_t result = filterResult_t(TRUE, TRUE); + for(filterList_const_iter_t iter = filters.begin(); + iter != filters.end(); + iter++) + { + filterResult_t filtered = (**iter)(view, children); + result.first = result.first && filtered.first; + result.second = result.second && filtered.second; + } + return result; +} + +class SortByTabOrder : public LLQuerySorter, public LLSingleton +{ + /*virtual*/ void operator() (LLView * parent, LLView::child_list_t &children) const + { + children.sort(LLCompareByTabOrder(parent->getCtrlOrder())); + } +}; + +LLCtrlQuery::LLCtrlQuery() : + LLViewQuery() +{ + setSorter(SortByTabOrder::getInstance()); +} + diff --git a/indra/llui/llviewquery.h b/indra/llui/llviewquery.h new file mode 100644 index 0000000000..ba59965c59 --- /dev/null +++ b/indra/llui/llviewquery.h @@ -0,0 +1,89 @@ +/** + * @file llviewquery.h + * @brief Query algorithm for flattening and filtering the view hierarchy. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLVIEWQUERY_H +#define LL_LLVIEWQUERY_H + +#include + +#include "llmemory.h" + +class LLView; + +typedef std::list viewList_t; +typedef std::pair filterResult_t; + +// Abstract base class for all filters. +class LLQueryFilter : public LLRefCount +{ +public: + virtual filterResult_t operator() (const LLView* const view, const viewList_t & children) const =0; +}; + +class LLQuerySorter : public LLRefCount +{ +public: + virtual void operator() (LLView * parent, viewList_t &children) const; +}; + +class LLNoLeavesFilter : public LLQueryFilter, public LLSingleton +{ + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const; +}; +class LLVisibleFilter : public LLQueryFilter, public LLSingleton +{ + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const; +}; +class LLEnabledFilter : public LLQueryFilter, public LLSingleton +{ + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const; +}; +class LLTabStopFilter : public LLQueryFilter, public LLSingleton +{ + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const; +}; + +// Algorithm for flattening +class LLViewQuery +{ +public: + typedef std::list filterList_t; + typedef filterList_t::iterator filterList_iter_t; + typedef filterList_t::const_iterator filterList_const_iter_t; + + LLViewQuery(); + virtual ~LLViewQuery() {} + + void addPreFilter(const LLQueryFilter* prefilter); + void addPostFilter(const LLQueryFilter* postfilter); + const filterList_t & getPreFilters() const; + const filterList_t & getPostFilters() const; + + void setSorter(const LLQuerySorter* sorter); + const LLQuerySorter* getSorter() const; + + viewList_t run(LLView * view) const; + // syntactic sugar + viewList_t operator () (LLView * view) const { return run(view); } +protected: + // override this method to provide iteration over other types of children + virtual void filterChildren(LLView * view, viewList_t & filtered_children) const; + filterResult_t runFilters(LLView * view, const viewList_t children, const filterList_t filters) const; +protected: + filterList_t mPreFilters; + filterList_t mPostFilters; + const LLQuerySorter* mSorterp; +}; + +class LLCtrlQuery : public LLViewQuery +{ +public: + LLCtrlQuery(); +}; + +#endif diff --git a/indra/llvfs/lldir.cpp b/indra/llvfs/lldir.cpp new file mode 100644 index 0000000000..3c82b28c74 --- /dev/null +++ b/indra/llvfs/lldir.cpp @@ -0,0 +1,461 @@ +/** + * @file lldir.cpp + * @brief implementation of directory utilities base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#if !LL_WINDOWS +#include +#include +#else +#include +#endif + +#include "lldir.h" +#include "llerror.h" +#include "lluuid.h" + +#if LL_WINDOWS +#include "lldir_win32.h" +LLDir_Win32 gDirUtil; +#elif LL_DARWIN +#include "lldir_mac.h" +LLDir_Mac gDirUtil; +#else +#include "lldir_linux.h" +LLDir_Linux gDirUtil; +#endif + +LLDir *gDirUtilp = (LLDir *)&gDirUtil; + +LLDir::LLDir() +: mAppName(""), + mExecutablePathAndName(""), + mExecutableFilename(""), + mExecutableDir(""), + mAppRODataDir(""), + mOSUserDir(""), + mOSUserAppDir(""), + mLindenUserDir(""), + mCAFile(""), + mTempDir(""), + mDirDelimiter("") +{ +} + +LLDir::~LLDir() +{ +} + + +S32 LLDir::deleteFilesInDir(const std::string &dirname, const std::string &mask) +{ + S32 count = 0; + std::string filename; + std::string fullpath; + S32 result; + while (getNextFileInDir(dirname, mask, filename, FALSE)) + { + if ((filename == ".") || (filename == "..")) + { + // skipping directory traversal filenames + count++; + continue; + } + fullpath = dirname; + fullpath += getDirDelimiter(); + fullpath += filename; + + S32 retry_count = 0; + while (retry_count < 5) + { + if (0 != LLFile::remove(fullpath.c_str())) + { + result = errno; + llwarns << "Problem removing " << fullpath << " - errorcode: " + << result << " attempt " << retry_count << llendl; + ms_sleep(1000); + } + else + { + if (retry_count) + { + llwarns << "Successfully removed " << fullpath << llendl; + } + break; + } + retry_count++; + } + count++; + } + return count; +} + +const std::string LLDir::findFile(const std::string &filename, + const std::string searchPath1, + const std::string searchPath2, + const std::string searchPath3) +{ + std::vector search_paths; + search_paths.push_back(searchPath1); + search_paths.push_back(searchPath2); + search_paths.push_back(searchPath3); + + std::vector::iterator search_path_iter; + for (search_path_iter = search_paths.begin(); + search_path_iter != search_paths.end(); + ++search_path_iter) + { + if (!search_path_iter->empty()) + { + std::string filename_and_path = (*search_path_iter) + getDirDelimiter() + filename; + if (fileExists(filename_and_path)) + { + return filename_and_path; + } + } + } + return ""; +} + + +const std::string &LLDir::getExecutablePathAndName() const +{ + return mExecutablePathAndName; +} + +const std::string &LLDir::getExecutableFilename() const +{ + return mExecutableFilename; +} + +const std::string &LLDir::getExecutableDir() const +{ + return mExecutableDir; +} + +const std::string &LLDir::getWorkingDir() const +{ + return mWorkingDir; +} + +const std::string &LLDir::getAppName() const +{ + return mAppName; +} + +const std::string &LLDir::getAppRODataDir() const +{ + return mAppRODataDir; +} + +const std::string &LLDir::getOSUserDir() const +{ + return mOSUserDir; +} + +const std::string &LLDir::getOSUserAppDir() const +{ + return mOSUserAppDir; +} + +const std::string &LLDir::getLindenUserDir() const +{ + return mLindenUserDir; +} + +const std::string &LLDir::getChatLogsDir() const +{ + return mChatLogsDir; +} + +const std::string &LLDir::getPerAccountChatLogsDir() const +{ + return mPerAccountChatLogsDir; +} + +const std::string &LLDir::getTempDir() const +{ + return mTempDir; +} + +const std::string &LLDir::getCAFile() const +{ + return mCAFile; +} + +const std::string &LLDir::getDirDelimiter() const +{ + return mDirDelimiter; +} + +const std::string &LLDir::getSkinDir() const +{ + return mSkinDir; +} + +std::string LLDir::getExpandedFilename(ELLPath location, const std::string &filename) const +{ + std::string prefix; + switch (location) + { + case LL_PATH_NONE: + // Do nothing + break; + + case LL_PATH_APP_SETTINGS: + prefix = getAppRODataDir(); + prefix += mDirDelimiter; + prefix += "app_settings"; + break; + + case LL_PATH_CHARACTER: + prefix = getAppRODataDir(); + prefix += mDirDelimiter; + prefix += "character"; + break; + + case LL_PATH_MOTIONS: + prefix = getAppRODataDir(); + prefix += mDirDelimiter; + prefix += "motions"; + break; + + case LL_PATH_HELP: + prefix = "help"; + break; + + case LL_PATH_CACHE: + if (getOSUserAppDir().empty()) + { + prefix = "data"; + } + else + { + prefix = getOSUserAppDir(); + prefix += mDirDelimiter; + prefix += "cache"; + } + break; + + case LL_PATH_USER_SETTINGS: + prefix = getOSUserAppDir(); + prefix += mDirDelimiter; + prefix += "user_settings"; + break; + + case LL_PATH_PER_SL_ACCOUNT: + prefix = getLindenUserDir(); + break; + + case LL_PATH_CHAT_LOGS: + prefix = getChatLogsDir(); + break; + + case LL_PATH_PER_ACCOUNT_CHAT_LOGS: + prefix = getPerAccountChatLogsDir(); + break; + + case LL_PATH_LOGS: + prefix = getOSUserAppDir(); + prefix += mDirDelimiter; + prefix += "logs"; + break; + + case LL_PATH_TEMP: + prefix = getTempDir(); + break; + + case LL_PATH_TOP_SKIN: + prefix = getSkinDir(); + break; + + case LL_PATH_SKINS: + prefix = getAppRODataDir(); + prefix += mDirDelimiter; + prefix += "skins"; + break; + + case LL_PATH_MOZILLA_PROFILE: + prefix = getOSUserAppDir(); + prefix += mDirDelimiter; + prefix += "browser_profile"; + break; + + default: + llassert(0); + } + + std::string expanded_filename; + if (!filename.empty()) + { + if (!prefix.empty()) + { + expanded_filename += prefix; + expanded_filename += mDirDelimiter; + expanded_filename += filename; + } + else + { + expanded_filename = filename; + } + } + else + if (!prefix.empty()) + { + // Directory only, no file name. + expanded_filename = prefix; + } + else + { + expanded_filename.assign(""); + } + + //llinfos << "*** EXPANDED FILENAME: <" << mExpandedFilename << ">" << llendl; + + return expanded_filename; +} + +std::string LLDir::getTempFilename() const +{ + LLUUID random_uuid; + char uuid_str[64]; + + random_uuid.generate(); + random_uuid.toString(uuid_str); + + std::string temp_filename = getTempDir(); + temp_filename += mDirDelimiter; + temp_filename += uuid_str; + temp_filename += ".tmp"; + + return temp_filename; +} + +void LLDir::setLindenUserDir(const std::string &first, const std::string &last) +{ + // if both first and last aren't set, assume we're grabbing the cached dir + if (!first.empty() && !last.empty()) + { + // some platforms have case-sensitive filesystems, so be + // utterly consistent with our firstname/lastname case. + LLString firstlower(first); + LLString::toLower(firstlower); + LLString lastlower(last); + LLString::toLower(lastlower); + mLindenUserDir = getOSUserAppDir(); + mLindenUserDir += mDirDelimiter; + mLindenUserDir += firstlower.c_str(); + mLindenUserDir += "_"; + mLindenUserDir += lastlower.c_str(); + } + else + { + llerrs << "Invalid name for LLDir::setLindenUserDir" << llendl; + } + + dumpCurrentDirectories(); +} + +void LLDir::setChatLogsDir(const std::string &path) +{ + if (!path.empty() ) + { + mChatLogsDir = path; + } + else + { + llwarns << "Invalid name for LLDir::setChatLogsDir" << llendl; + } +} + +void LLDir::setPerAccountChatLogsDir(const std::string &first, const std::string &last) +{ + // if both first and last aren't set, assume we're grabbing the cached dir + if (!first.empty() && !last.empty()) + { + // some platforms have case-sensitive filesystems, so be + // utterly consistent with our firstname/lastname case. + LLString firstlower(first); + LLString::toLower(firstlower); + LLString lastlower(last); + LLString::toLower(lastlower); + mPerAccountChatLogsDir = getChatLogsDir(); + mPerAccountChatLogsDir += mDirDelimiter; + mPerAccountChatLogsDir += firstlower.c_str(); + mPerAccountChatLogsDir += "_"; + mPerAccountChatLogsDir += lastlower.c_str(); + } + else + { + llwarns << "Invalid name for LLDir::setPerAccountChatLogsDir" << llendl; + } +} + +void LLDir::setSkinFolder(const std::string &skin_folder) +{ + mSkinDir = getAppRODataDir(); + mSkinDir += mDirDelimiter; + mSkinDir += "skins"; + mSkinDir += mDirDelimiter; + mSkinDir += skin_folder; +} + +void LLDir::dumpCurrentDirectories() +{ + llinfos << "Current Directories:" << llendl; + + llinfos << " CurPath: " << getCurPath() << llendl; + llinfos << " AppName: " << getAppName() << llendl; + llinfos << " ExecutableFilename: " << getExecutableFilename() << llendl; + llinfos << " ExecutableDir: " << getExecutableDir() << llendl; + llinfos << " ExecutablePathAndName: " << getExecutablePathAndName() << llendl; + llinfos << " WorkingDir: " << getWorkingDir() << llendl; + llinfos << " AppRODataDir: " << getAppRODataDir() << llendl; + llinfos << " OSUserDir: " << getOSUserDir() << llendl; + llinfos << " OSUserAppDir: " << getOSUserAppDir() << llendl; + llinfos << " LindenUserDir: " << getLindenUserDir() << llendl; + llinfos << " TempDir: " << getTempDir() << llendl; + llinfos << " CAFile: " << getCAFile() << llendl; + llinfos << " SkinDir: " << getSkinDir() << llendl; +} + + +void dir_exists_or_crash(const std::string &dir_name) +{ +#if LL_WINDOWS + // *FIX: lame - it doesn't do the same thing on windows. not so + // important since we don't deploy simulator to windows boxes. + LLFile::mkdir(dir_name.c_str(), 0700); +#else + struct stat dir_stat; + if(0 != LLFile::stat(dir_name.c_str(), &dir_stat)) + { + S32 stat_rv = errno; + if(ENOENT == stat_rv) + { + if(0 != LLFile::mkdir(dir_name.c_str(), 0700)) // octal + { + llerrs << "Unable to create directory: " << dir_name << llendl; + } + } + else + { + llerrs << "Unable to stat: " << dir_name << " errno = " << stat_rv + << llendl; + } + } + else + { + // data_dir exists, make sure it's a directory. + if(!S_ISDIR(dir_stat.st_mode)) + { + llerrs << "Data directory collision: " << dir_name << llendl; + } + } +#endif +} diff --git a/indra/llvfs/lldir.h b/indra/llvfs/lldir.h new file mode 100644 index 0000000000..710dcd1ae3 --- /dev/null +++ b/indra/llvfs/lldir.h @@ -0,0 +1,102 @@ +/** + * @file lldir.h + * @brief Definition of directory utilities class + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLDIR_H +#define LL_LLDIR_H + +typedef enum ELLPath +{ + LL_PATH_NONE = 0, + LL_PATH_USER_SETTINGS = 1, + LL_PATH_APP_SETTINGS = 2, + LL_PATH_PER_SL_ACCOUNT = 3, + LL_PATH_CACHE = 4, + LL_PATH_CHARACTER = 5, + LL_PATH_MOTIONS = 6, + LL_PATH_HELP = 7, + LL_PATH_LOGS = 8, + LL_PATH_TEMP = 9, + LL_PATH_SKINS = 10, + LL_PATH_TOP_SKIN = 11, + LL_PATH_CHAT_LOGS = 12, + LL_PATH_PER_ACCOUNT_CHAT_LOGS = 13, + LL_PATH_MOZILLA_PROFILE = 14, + LL_PATH_COUNT = 15 +} ELLPath; + + +class LLDir +{ + public: + LLDir(); + virtual ~LLDir(); + + virtual void initAppDirs(const std::string &app_name) = 0; + public: + virtual S32 deleteFilesInDir(const std::string &dirname, const std::string &mask); + +// pure virtual functions + virtual U32 countFilesInDir(const std::string &dirname, const std::string &mask) = 0; + virtual BOOL getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname, BOOL wrap) = 0; + virtual void getRandomFileInDir(const std::string &dirname, const std::string &mask, std::string &fname) = 0; + virtual std::string getCurPath() = 0; + virtual BOOL fileExists(const std::string &filename) = 0; + + const std::string findFile(const std::string &filename, const std::string searchPath1 = "", const std::string searchPath2 = "", const std::string searchPath3 = ""); + const std::string &getExecutablePathAndName() const; // Full pathname of the executable + const std::string &getAppName() const; // install directory under progams/ ie "SecondLife" + const std::string &getExecutableDir() const; // Directory where the executable is located + const std::string &getExecutableFilename() const;// Filename of .exe + const std::string &getWorkingDir() const; // Current working directory + const std::string &getAppRODataDir() const; // Location of read-only data files + const std::string &getOSUserDir() const; // Location of the os-specific user dir + const std::string &getOSUserAppDir() const; // Location of the os-specific user app dir + const std::string &getLindenUserDir() const; // Location of the Linden user dir. + const std::string &getChatLogsDir() const; // Location of the chat logs dir. + const std::string &getPerAccountChatLogsDir() const; // Location of the per account chat logs dir. + const std::string &getTempDir() const; // Common temporary directory + const std::string &getCAFile() const; // File containing TLS certificate authorities + const std::string &getDirDelimiter() const; // directory separator for platform (ie. '\' or '/' or ':') + const std::string &getSkinDir() const; // User-specified skin folder. + + // Expanded filename + std::string getExpandedFilename(ELLPath location, const std::string &filename) const; + + // random filename in common temporary directory + std::string getTempFilename() const; + + virtual void setChatLogsDir(const std::string &path); // Set the chat logs dir to this user's dir + virtual void setPerAccountChatLogsDir(const std::string &first, const std::string &last); // Set the per user chat log directory. + virtual void setLindenUserDir(const std::string &first, const std::string &last); // Set the linden user dir to this user's dir + virtual void setSkinFolder(const std::string &skin_folder); + + virtual void dumpCurrentDirectories(); + +protected: + std::string mAppName; // install directory under progams/ ie "SecondLife" + std::string mExecutablePathAndName; // full path + Filename of .exe + std::string mExecutableFilename; // Filename of .exe + std::string mExecutableDir; // Location of executable + std::string mWorkingDir; // Current working directory + std::string mAppRODataDir; // Location for static app data + std::string mOSUserDir; // OS Specific user directory + std::string mOSUserAppDir; // OS Specific user app directory + std::string mLindenUserDir; // Location for Linden user-specific data + std::string mPerAccountChatLogsDir; // Location for chat logs. + std::string mChatLogsDir; // Location for chat logs. + std::string mCAFile; // Location of the TLS certificate authority PEM file. + std::string mTempDir; + std::string mDirDelimiter; + std::string mSkinDir; // Location for u ser-specified skin info. +}; + +void dir_exists_or_crash(const std::string &dir_name); + +extern LLDir *gDirUtilp; + +#endif // LL_LLDIR_H diff --git a/indra/llvfs/lldir_linux.cpp b/indra/llvfs/lldir_linux.cpp new file mode 100644 index 0000000000..6e50f9f239 --- /dev/null +++ b/indra/llvfs/lldir_linux.cpp @@ -0,0 +1,329 @@ +/** + * @file lldir_linux.cpp + * @brief Implementation of directory utilities for linux + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lldir_linux.h" +#include "llerror.h" +#include "llrand.h" // for gLindenLabRandomNumber +#include +#include +#include +#include +#include + + +static std::string getCurrentUserHome(char* fallback) +{ + const uid_t uid = getuid(); + struct passwd *pw; + char *result_cstr = fallback; + + pw = getpwuid(uid); + if ((pw != NULL) && (pw->pw_dir != NULL)) + { + result_cstr = (char*) pw->pw_dir; + } + else + { + llinfos << "Couldn't detect home directory from passwd - trying $HOME" << llendl; + const char *const home_env = getenv("HOME"); + if (home_env) + { + result_cstr = (char*) home_env; + } + else + { + llwarns << "Couldn't detect home directory! Falling back to " << fallback << llendl; + } + } + + return std::string(result_cstr); +} + + +LLDir_Linux::LLDir_Linux() +{ + mDirDelimiter = "/"; + mCurrentDirIndex = -1; + mCurrentDirCount = -1; + mDirp = NULL; + + char tmp_str[LL_MAX_PATH]; + getcwd(tmp_str, LL_MAX_PATH); + + mExecutableFilename = ""; + mExecutablePathAndName = ""; + mExecutableDir = tmp_str; + mWorkingDir = tmp_str; + mAppRODataDir = tmp_str; + mOSUserDir = getCurrentUserHome(tmp_str); + mOSUserAppDir = ""; + mLindenUserDir = tmp_str; + + char path [32]; + + // !!! FIXME: /proc/%d/exe doesn't work on FreeBSD. + sprintf (path, "/proc/%d/exe", (int) getpid ()); + int rc = readlink (path, tmp_str, sizeof (tmp_str)-1); + if ( (rc != -1) && (rc <= ((int) sizeof (tmp_str)-1)) ) + { + tmp_str[rc] = '\0'; //readlink() doesn't 0-terminate the buffer + mExecutablePathAndName = tmp_str; + char *path_end; + if ((path_end = strrchr(tmp_str,'/'))) + { + *path_end = '\0'; + mExecutableDir = tmp_str; + mWorkingDir = tmp_str; + mExecutableFilename = path_end+1; + } + else + { + mExecutableFilename = tmp_str; + } + } + + // !!! FIXME: don't use /tmp, use $HOME/.secondlife/tmp or something. + mTempDir = "/tmp"; +} + +LLDir_Linux::~LLDir_Linux() +{ +} + +// Implementation + + +void LLDir_Linux::initAppDirs(const std::string &app_name) +{ + mAppName = app_name; + + LLString upper_app_name(app_name); + LLString::toUpper(upper_app_name); + + char* app_home_env = getenv((upper_app_name + "_USER_DIR").c_str()); + if (app_home_env) + { + // user has specified own userappdir i.e. $SECONDLIFE_USER_DIR + mOSUserAppDir = app_home_env; + } + else + { + // traditionally on unixoids, MyApp gets ~/.myapp dir for data + mOSUserAppDir = mOSUserDir; + mOSUserAppDir += "/"; + mOSUserAppDir += "."; + LLString lower_app_name(app_name); + LLString::toLower(lower_app_name); + mOSUserAppDir += lower_app_name; + } + + // create any directories we expect to write to. + + int res = LLFile::mkdir(mOSUserAppDir.c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create app user dir " << mOSUserAppDir << llendl; + llwarns << "Default to base dir" << mOSUserDir << llendl; + mOSUserAppDir = mOSUserDir; + } + } + + res = LLFile::mkdir(getExpandedFilename(LL_PATH_LOGS,"").c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create LL_PATH_LOGS dir " << getExpandedFilename(LL_PATH_LOGS,"") << llendl; + } + } + + res = LLFile::mkdir(getExpandedFilename(LL_PATH_USER_SETTINGS,"").c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create LL_PATH_USER_SETTINGS dir " << getExpandedFilename(LL_PATH_USER_SETTINGS,"") << llendl; + } + } + + res = LLFile::mkdir(getExpandedFilename(LL_PATH_CACHE,"").c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create LL_PATH_CACHE dir " << getExpandedFilename(LL_PATH_CACHE,"") << llendl; + } + } + + res = LLFile::mkdir(getExpandedFilename(LL_PATH_MOZILLA_PROFILE,"").c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create LL_PATH_MOZILLA_PROFILE dir " << getExpandedFilename(LL_PATH_MOZILLA_PROFILE,"") << llendl; + } + } + + mCAFile = getExpandedFilename(LL_PATH_APP_SETTINGS, "CA.pem"); +} + +U32 LLDir_Linux::countFilesInDir(const std::string &dirname, const std::string &mask) +{ + U32 file_count = 0; + glob_t g; + + std::string tmp_str; + tmp_str = dirname; + tmp_str += mask; + + if(glob(tmp_str.c_str(), GLOB_NOSORT, NULL, &g) == 0) + { + file_count = g.gl_pathc; + + globfree(&g); + } + + return (file_count); +} + +// get the next file in the directory +// automatically wrap if we've hit the end +BOOL LLDir_Linux::getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname, BOOL wrap) +{ + glob_t g; + BOOL result = FALSE; + fname = ""; + + if(!(dirname == mCurrentDir)) + { + // different dir specified, close old search + mCurrentDirIndex = -1; + mCurrentDirCount = -1; + mCurrentDir = dirname; + } + + std::string tmp_str; + tmp_str = dirname; + tmp_str += mask; + + if(glob(tmp_str.c_str(), GLOB_NOSORT, NULL, &g) == 0) + { + if(g.gl_pathc > 0) + { + if((int)g.gl_pathc != mCurrentDirCount) + { + // Number of matches has changed since the last search, meaning a file has been added or deleted. + // Reset the index. + mCurrentDirIndex = -1; + mCurrentDirCount = g.gl_pathc; + } + + mCurrentDirIndex++; + + if((mCurrentDirIndex >= (int)g.gl_pathc) && wrap) + { + mCurrentDirIndex = 0; + } + + if(mCurrentDirIndex < (int)g.gl_pathc) + { +// llinfos << "getNextFileInDir: returning number " << mCurrentDirIndex << ", path is " << g.gl_pathv[mCurrentDirIndex] << llendl; + + // The API wants just the filename, not the full path. + //fname = g.gl_pathv[mCurrentDirIndex]; + + char *s = strrchr(g.gl_pathv[mCurrentDirIndex], '/'); + + if(s == NULL) + s = g.gl_pathv[mCurrentDirIndex]; + else if(s[0] == '/') + s++; + + fname = s; + + result = TRUE; + } + } + + globfree(&g); + } + + return(result); +} + + +// get a random file in the directory +// automatically wrap if we've hit the end +void LLDir_Linux::getRandomFileInDir(const std::string &dirname, const std::string &mask, std::string &fname) +{ + U32 num_files; + U32 which_file; + DIR *dirp; + dirent *entryp = NULL; + + fname = ""; + + num_files = countFilesInDir(dirname,mask); + if (!num_files) + { + return; + } + + which_file = gLindenLabRandomNumber.llrand() % num_files; + +// llinfos << "Random select file #" << which_file << llendl; + + // which_file now indicates the (zero-based) index to which file to play + + if (!((dirp = opendir(dirname.c_str())))) + { + while (which_file--) + { + if (!((entryp = readdir(dirp)))) + { + return; + } + } + + if ((!which_file) && entryp) + { + fname = entryp->d_name; + } + + closedir(dirp); + } +} + +std::string LLDir_Linux::getCurPath() +{ + char tmp_str[LL_MAX_PATH]; + getcwd(tmp_str, LL_MAX_PATH); + return tmp_str; +} + + +BOOL LLDir_Linux::fileExists(const std::string &filename) +{ + struct stat stat_data; + // Check the age of the file + // Now, we see if the files we've gathered are recent... + int res = stat(filename.c_str(), &stat_data); + if (!res) + { + return TRUE; + } + else + { + return FALSE; + } +} + diff --git a/indra/llvfs/lldir_linux.h b/indra/llvfs/lldir_linux.h new file mode 100644 index 0000000000..3e63e72303 --- /dev/null +++ b/indra/llvfs/lldir_linux.h @@ -0,0 +1,41 @@ +/** + * @file lldir_linux.h + * @brief Definition of directory utilities class for linux + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLDIR_LINUX_H +#define LL_LLDIR_LINUX_H + +#include "lldir.h" + +#include +#include +#include + +class LLDir_Linux : public LLDir +{ +public: + LLDir_Linux(); + virtual ~LLDir_Linux(); + + virtual void initAppDirs(const std::string &app_name); +public: + virtual std::string getCurPath(); + virtual U32 countFilesInDir(const std::string &dirname, const std::string &mask); + virtual BOOL getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname, BOOL wrap); + virtual void getRandomFileInDir(const std::string &dirname, const std::string &mask, std::string &fname); + /*virtual*/ BOOL fileExists(const std::string &filename); + +private: + DIR *mDirp; + int mCurrentDirIndex; + int mCurrentDirCount; + std::string mCurrentDir; +}; + +#endif // LL_LLDIR_LINUX_H + + diff --git a/indra/llvfs/lldir_mac.cpp b/indra/llvfs/lldir_mac.cpp new file mode 100644 index 0000000000..591241478d --- /dev/null +++ b/indra/llvfs/lldir_mac.cpp @@ -0,0 +1,362 @@ +/** + * @file lldir_mac.cpp + * @brief Implementation of directory utilities for linux + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#if LL_DARWIN + +#include "linden_common.h" + +#include "lldir_mac.h" +#include "llerror.h" +#include "llrand.h" +#include +#include +#include +#include + +#include + +// -------------------------------------------------------------------------------- + +static OSStatus CFCreateDirectory(FSRef *parentRef, CFStringRef name, FSRef *newRef) +{ + OSStatus result = noErr; + HFSUniStr255 uniStr; + + uniStr.length = CFStringGetLength(name); + CFStringGetCharacters(name, CFRangeMake(0, uniStr.length), uniStr.unicode); + result = FSMakeFSRefUnicode(parentRef, uniStr.length, uniStr.unicode, kTextEncodingMacRoman, newRef); + if (result != noErr) + { + result = FSCreateDirectoryUnicode(parentRef, uniStr.length, uniStr.unicode, 0, NULL, newRef, NULL, NULL); + } + + return result; +} + +// -------------------------------------------------------------------------------- + +static void CFStringRefToLLString(CFStringRef stringRef, std::string &llString, bool releaseWhenDone) +{ + if (stringRef) + { + long bufferSize = CFStringGetLength(stringRef) + 1; + char* buffer = new char[bufferSize]; + memset(buffer, 0, bufferSize); + if (CFStringGetCString(stringRef, buffer, bufferSize, kCFStringEncodingUTF8)) + llString = buffer; + delete[] buffer; + if (releaseWhenDone) + CFRelease(stringRef); + } +} + +// -------------------------------------------------------------------------------- + +static void CFURLRefToLLString(CFURLRef urlRef, std::string &llString, bool releaseWhenDone) +{ + if (urlRef) + { + CFURLRef absoluteURLRef = CFURLCopyAbsoluteURL(urlRef); + if (absoluteURLRef) + { + CFStringRef stringRef = CFURLCopyFileSystemPath(absoluteURLRef, kCFURLPOSIXPathStyle); + CFStringRefToLLString(stringRef, llString, true); + CFRelease(absoluteURLRef); + } + if (releaseWhenDone) + CFRelease(urlRef); + } +} + +// -------------------------------------------------------------------------------- + +static void FSRefToLLString(FSRef *fsRef, std::string &llString) +{ + OSStatus error = noErr; + char path[MAX_PATH]; + + error = FSRefMakePath(fsRef, (UInt8*) path, sizeof(path)); + if (error == noErr) + llString = path; +} + +// -------------------------------------------------------------------------------- + +LLDir_Mac::LLDir_Mac() +{ + mDirDelimiter = "/"; + mCurrentDirIndex = -1; + mCurrentDirCount = -1; + + CFBundleRef mainBundleRef = NULL; + CFURLRef executableURLRef = NULL; + CFStringRef stringRef = NULL; + OSStatus error = noErr; + FSRef fileRef; + CFStringRef secondLifeString = CFSTR("SecondLife"); + + mainBundleRef = CFBundleGetMainBundle(); + + executableURLRef = CFBundleCopyExecutableURL(mainBundleRef); + + if (executableURLRef != NULL) + { + // mExecutablePathAndName + CFURLRefToLLString(executableURLRef, mExecutablePathAndName, false); + + // mExecutableFilename + stringRef = CFURLCopyLastPathComponent(executableURLRef); + CFStringRefToLLString(stringRef, mExecutableFilename, true); + + // mExecutableDir + CFURLRef executableParentURLRef = CFURLCreateCopyDeletingLastPathComponent(NULL, executableURLRef); + CFURLRefToLLString(executableParentURLRef, mExecutableDir, true); + + // mAppRODataDir + CFURLRef resourcesURLRef = CFBundleCopyResourcesDirectoryURL(mainBundleRef); + CFURLRefToLLString(resourcesURLRef, mAppRODataDir, true); + + // mOSUserDir + error = FSFindFolder(kUserDomain, kApplicationSupportFolderType, true, &fileRef); + if (error == noErr) + { + FSRef newFileRef; + + // Create the directory + error = CFCreateDirectory(&fileRef, secondLifeString, &newFileRef); + if (error == noErr) + { + // Save the full path to the folder + FSRefToLLString(&newFileRef, mOSUserDir); + + // Create our sub-dirs + (void) CFCreateDirectory(&newFileRef, CFSTR("data"), NULL); + (void) CFCreateDirectory(&newFileRef, CFSTR("cache"), NULL); + (void) CFCreateDirectory(&newFileRef, CFSTR("logs"), NULL); + (void) CFCreateDirectory(&newFileRef, CFSTR("user_settings"), NULL); + (void) CFCreateDirectory(&newFileRef, CFSTR("browser_profile"), NULL); + } + } + + // mOSUserAppDir + mOSUserAppDir = mOSUserDir; + + // mTempDir + error = FSFindFolder(kOnAppropriateDisk, kTemporaryFolderType, true, &fileRef); + if (error == noErr) + { + FSRef tempRef; + error = CFCreateDirectory(&fileRef, secondLifeString, &tempRef); + if (error == noErr) + FSRefToLLString(&tempRef, mTempDir); + } + + // Set the working dir to /Contents/Resources + (void) chdir(mAppRODataDir.c_str()); + + // Canonically, since we set it here... + mWorkingDir = mAppRODataDir; + + CFRelease(executableURLRef); + executableURLRef = NULL; + } +} + +LLDir_Mac::~LLDir_Mac() +{ +} + +// Implementation + + +void LLDir_Mac::initAppDirs(const std::string &app_name) +{ + mCAFile = getExpandedFilename(LL_PATH_APP_SETTINGS, "CA.pem"); + + //dumpCurrentDirectories(); +} + +U32 LLDir_Mac::countFilesInDir(const std::string &dirname, const std::string &mask) +{ + U32 file_count = 0; + glob_t g; + + std::string tmp_str; + tmp_str = dirname; + tmp_str += mask; + + if(glob(tmp_str.c_str(), GLOB_NOSORT, NULL, &g) == 0) + { + file_count = g.gl_pathc; + + globfree(&g); + } + + return (file_count); +} + +// get the next file in the directory +// automatically wrap if we've hit the end +BOOL LLDir_Mac::getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname, BOOL wrap) +{ + glob_t g; + BOOL result = FALSE; + fname = ""; + + if(!(dirname == mCurrentDir)) + { + // different dir specified, close old search + mCurrentDirIndex = -1; + mCurrentDirCount = -1; + mCurrentDir = dirname; + } + + std::string tmp_str; + tmp_str = dirname; + tmp_str += mask; + + if(glob(tmp_str.c_str(), GLOB_NOSORT, NULL, &g) == 0) + { + if(g.gl_pathc > 0) + { + if(g.gl_pathc != mCurrentDirCount) + { + // Number of matches has changed since the last search, meaning a file has been added or deleted. + // Reset the index. + mCurrentDirIndex = -1; + mCurrentDirCount = g.gl_pathc; + } + + mCurrentDirIndex++; + + if((mCurrentDirIndex >= g.gl_pathc) && wrap) + { + mCurrentDirIndex = 0; + } + + if(mCurrentDirIndex < g.gl_pathc) + { +// llinfos << "getNextFileInDir: returning number " << mCurrentDirIndex << ", path is " << g.gl_pathv[mCurrentDirIndex] << llendl; + + // The API wants just the filename, not the full path. + //fname = g.gl_pathv[mCurrentDirIndex]; + + char *s = strrchr(g.gl_pathv[mCurrentDirIndex], '/'); + + if(s == NULL) + s = g.gl_pathv[mCurrentDirIndex]; + else if(s[0] == '/') + s++; + + fname = s; + + result = TRUE; + } + } + + globfree(&g); + } + + return(result); +} + +// get a random file in the directory +void LLDir_Mac::getRandomFileInDir(const std::string &dirname, const std::string &mask, std::string &fname) +{ + U32 which_file; + glob_t g; + fname = ""; + + std::string tmp_str; + tmp_str = dirname; + tmp_str += mask; + + if(glob(tmp_str.c_str(), GLOB_NOSORT, NULL, &g) == 0) + { + if(g.gl_pathc > 0) + { + + which_file = gLindenLabRandomNumber.llrand() % g.gl_pathc; + +// llinfos << "getRandomFileInDir: returning number " << which_file << ", path is " << g.gl_pathv[which_file] << llendl; + // The API wants just the filename, not the full path. + //fname = g.gl_pathv[which_file]; + + char *s = strrchr(g.gl_pathv[which_file], '/'); + + if(s == NULL) + s = g.gl_pathv[which_file]; + else if(s[0] == '/') + s++; + + fname = s; + } + + globfree(&g); + } +} + +S32 LLDir_Mac::deleteFilesInDir(const std::string &dirname, const std::string &mask) +{ + glob_t g; + S32 result = 0; + + std::string tmp_str; + tmp_str = dirname; + tmp_str += mask; + + if(glob(tmp_str.c_str(), GLOB_NOSORT, NULL, &g) == 0) + { + int i; + + for(i = 0; i < g.gl_pathc; i++) + { +// llinfos << "deleteFilesInDir: deleting number " << i << ", path is " << g.gl_pathv[i] << llendl; + + if(unlink(g.gl_pathv[i]) != 0) + { + result = errno; + + llwarns << "Problem removing " << g.gl_pathv[i] << " - errorcode: " + << result << llendl; + } + } + + globfree(&g); + } + + return(result); +} + +std::string LLDir_Mac::getCurPath() +{ + char tmp_str[LL_MAX_PATH]; + getcwd(tmp_str, LL_MAX_PATH); + return tmp_str; +} + + + +BOOL LLDir_Mac::fileExists(const std::string &filename) +{ + struct stat stat_data; + // Check the age of the file + // Now, we see if the files we've gathered are recent... + int res = stat(filename.c_str(), &stat_data); + if (!res) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +#endif // LL_DARWIN diff --git a/indra/llvfs/lldir_mac.h b/indra/llvfs/lldir_mac.h new file mode 100644 index 0000000000..24fb1ac583 --- /dev/null +++ b/indra/llvfs/lldir_mac.h @@ -0,0 +1,40 @@ +/** + * @file lldir_mac.h + * @brief Definition of directory utilities class for Mac OS X + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLDIR_MAC_H +#define LL_LLDIR_MAC_H + +#include "lldir.h" + +#include +#include + +class LLDir_Mac : public LLDir +{ +public: + LLDir_Mac(); + virtual ~LLDir_Mac(); + + virtual void initAppDirs(const std::string &app_name); +public: + virtual S32 deleteFilesInDir(const std::string &dirname, const std::string &mask); + virtual std::string getCurPath(); + virtual U32 countFilesInDir(const std::string &dirname, const std::string &mask); + virtual BOOL getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname, BOOL wrap); + virtual void getRandomFileInDir(const std::string &dirname, const std::string &ask, std::string &fname); + virtual BOOL fileExists(const std::string &filename); + +private: + int mCurrentDirIndex; + int mCurrentDirCount; + std::string mCurrentDir; +}; + +#endif // LL_LLDIR_MAC_H + + diff --git a/indra/llvfs/lldir_win32.cpp b/indra/llvfs/lldir_win32.cpp new file mode 100644 index 0000000000..0c5b0ecf19 --- /dev/null +++ b/indra/llvfs/lldir_win32.cpp @@ -0,0 +1,382 @@ +/** + * @file lldir_win32.cpp + * @brief Implementation of directory utilities for windows + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#if LL_WINDOWS + +#include "linden_common.h" + +#include "lldir_win32.h" +#include "llerror.h" +#include "llrand.h" // for gLindenLabRandomNumber +#include "shlobj.h" + +#include +#include +#include + +// Utility stuff to get versions of the sh +#define PACKVERSION(major,minor) MAKELONG(minor,major) +DWORD GetDllVersion(LPCTSTR lpszDllName); + +LLDir_Win32::LLDir_Win32() +{ + mDirDelimiter = "\\"; + + WCHAR w_str[MAX_PATH]; + + // Application Data is where user settings go + SHGetSpecialFolderPath(NULL, w_str, CSIDL_APPDATA, TRUE); + + mOSUserDir = utf16str_to_utf8str(llutf16string(w_str)); + + // Local Settings\Application Data is where cache files should + // go, they don't get copied to the server if the user moves his + // profile around on the network. JC + // + // TODO: patch the installer to remove old cache files on update, then + // enable this code. + //SHGetSpecialFolderPath(NULL, w_str, CSIDL_LOCAL_APPDATA, TRUE); + //mOSUserCacheDir = utf16str_to_utf8str(llutf16string(w_str)); + + if (GetTempPath(MAX_PATH, w_str)) + { + if (wcslen(w_str)) + { + w_str[wcslen(w_str)-1] = '\0'; // remove trailing slash + } + mTempDir = utf16str_to_utf8str(llutf16string(w_str)); + } + else + { + mTempDir = mOSUserDir; + } + +// fprintf(stderr, "mTempDir = <%s>",mTempDir); + +#if 1 + // Don't use the real app path for now, as we'll have to add parsing to detect if + // we're in a developer tree, which has a different structure from the installed product. + + S32 size = GetModuleFileName(NULL, w_str, MAX_PATH); + if (size) + { + w_str[size] = '\0'; + mExecutablePathAndName = utf16str_to_utf8str(llutf16string(w_str)); + S32 path_end = mExecutablePathAndName.find_last_of('\\'); + if (path_end != std::string::npos) + { + mExecutableDir = mExecutablePathAndName.substr(0, path_end); + mExecutableFilename = mExecutablePathAndName.substr(path_end+1, std::string::npos); + } + else + { + mExecutableFilename = mExecutablePathAndName; + } + GetCurrentDirectory(MAX_PATH, w_str); + mWorkingDir = utf16str_to_utf8str(llutf16string(w_str)); + + } + else + { + fprintf(stderr, "Couldn't get APP path, assuming current directory!"); + GetCurrentDirectory(MAX_PATH, w_str); + mExecutableDir = utf16str_to_utf8str(llutf16string(w_str)); + // Assume it's the current directory + } +#else + GetCurrentDirectory(MAX_PATH, w_str); + mExecutableDir = utf16str_to_utf8str(llutf16string(w_str)); +#endif + if (strstr(mExecutableDir.c_str(), "indra\\newview")) + mAppRODataDir = getCurPath(); + else + mAppRODataDir = mExecutableDir; +} + +LLDir_Win32::~LLDir_Win32() +{ +} + +// Implementation + +void LLDir_Win32::initAppDirs(const std::string &app_name) +{ + mAppName = app_name; + mOSUserAppDir = mOSUserDir; + mOSUserAppDir += "\\"; + mOSUserAppDir += app_name; + + int res = LLFile::mkdir(mOSUserAppDir.c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create app user dir " << mOSUserAppDir << llendl; + llwarns << "Default to base dir" << mOSUserDir << llendl; + mOSUserAppDir = mOSUserDir; + } + } + //dumpCurrentDirectories(); + + res = LLFile::mkdir(getExpandedFilename(LL_PATH_LOGS,"").c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create LL_PATH_LOGS dir " << getExpandedFilename(LL_PATH_LOGS,"") << llendl; + } + } + + res = LLFile::mkdir(getExpandedFilename(LL_PATH_USER_SETTINGS,"").c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create LL_PATH_USER_SETTINGS dir " << getExpandedFilename(LL_PATH_USER_SETTINGS,"") << llendl; + } + } + + res = LLFile::mkdir(getExpandedFilename(LL_PATH_CACHE,"").c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create LL_PATH_CACHE dir " << getExpandedFilename(LL_PATH_CACHE,"") << llendl; + } + } + + res = LLFile::mkdir(getExpandedFilename(LL_PATH_MOZILLA_PROFILE,"").c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create LL_PATH_MOZILLA_PROFILE dir " << getExpandedFilename(LL_PATH_MOZILLA_PROFILE,"") << llendl; + } + } + + mCAFile = getExpandedFilename(LL_PATH_APP_SETTINGS, "CA.pem"); +} + +U32 LLDir_Win32::countFilesInDir(const std::string &dirname, const std::string &mask) +{ + HANDLE count_search_h; + U32 file_count; + + file_count = 0; + + WIN32_FIND_DATA FileData; + + llutf16string pathname = utf8str_to_utf16str(dirname); + pathname += utf8str_to_utf16str(mask); + + if ((count_search_h = FindFirstFile(pathname.c_str(), &FileData)) != INVALID_HANDLE_VALUE) + { + file_count++; + + while (FindNextFile(count_search_h, &FileData)) + { + file_count++; + } + + FindClose(count_search_h); + } + + return (file_count); +} + + +// get the next file in the directory +// automatically wrap if we've hit the end +BOOL LLDir_Win32::getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname, BOOL wrap) +{ + llutf16string dirnamew = utf8str_to_utf16str(dirname); + return getNextFileInDir(dirnamew, mask, fname, wrap); + +} + +BOOL LLDir_Win32::getNextFileInDir(const llutf16string &dirname, const std::string &mask, std::string &fname, BOOL wrap) +{ + WIN32_FIND_DATAW FileData; + + fname = ""; + llutf16string pathname = dirname; + pathname += utf8str_to_utf16str(mask); + + if (pathname != mCurrentDir) + { + // different dir specified, close old search + if (mCurrentDir[0]) + { + FindClose(mDirSearch_h); + } + mCurrentDir = pathname; + + // and open new one + // Check error opening Directory structure + if ((mDirSearch_h = FindFirstFile(pathname.c_str(), &FileData)) == INVALID_HANDLE_VALUE) + { +// llinfos << "Unable to locate first file" << llendl; + return(FALSE); + } + } + else // get next file in list + { + // Find next entry + if (!FindNextFile(mDirSearch_h, &FileData)) + { + if (GetLastError() == ERROR_NO_MORE_FILES) + { + // No more files, so reset to beginning of directory + FindClose(mDirSearch_h); + mCurrentDir[0] = NULL; + + if (wrap) + { + return(getNextFileInDir(pathname,"",fname,TRUE)); + } + else + { + fname[0] = 0; + return(FALSE); + } + } + else + { + // Error +// llinfos << "Unable to locate next file" << llendl; + return(FALSE); + } + } + } + + // convert from TCHAR to char + fname = utf16str_to_utf8str(FileData.cFileName); + + // fname now first name in list + return(TRUE); +} + + +// get a random file in the directory +// automatically wrap if we've hit the end +void LLDir_Win32::getRandomFileInDir(const std::string &dirname, const std::string &mask, std::string &fname) +{ + U32 num_files; + U32 which_file; + HANDLE random_search_h; + + fname = ""; + + llutf16string pathname = utf8str_to_utf16str(dirname); + pathname += utf8str_to_utf16str(mask); + + WIN32_FIND_DATA FileData; + fname[0] = NULL; + + num_files = countFilesInDir(dirname,mask); + if (!num_files) + { + return; + } + + which_file = gLindenLabRandomNumber.llrand() % num_files; + +// llinfos << "Random select mp3 #" << which_file << llendl; + + // which_file now indicates the (zero-based) index to which file to play + + if ((random_search_h = FindFirstFile(pathname.c_str(), &FileData)) != INVALID_HANDLE_VALUE) + { + while (which_file--) + { + if (!FindNextFile(random_search_h, &FileData)) + { + return; + } + } + FindClose(random_search_h); + + fname = utf16str_to_utf8str(llutf16string(FileData.cFileName)); + } +} + +std::string LLDir_Win32::getCurPath() +{ + WCHAR w_str[MAX_PATH]; + GetCurrentDirectory(MAX_PATH, w_str); + + return utf16str_to_utf8str(llutf16string(w_str)); +} + + +BOOL LLDir_Win32::fileExists(const std::string &filename) +{ + llstat stat_data; + // Check the age of the file + // Now, we see if the files we've gathered are recent... + int res = LLFile::stat(filename.c_str(), &stat_data); + if (!res) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +#if 0 +// Utility function to get version number of a DLL + +#define PACKVERSION(major,minor) MAKELONG(minor,major) + +DWORD GetDllVersion(LPCTSTR lpszDllName) +{ + + HINSTANCE hinstDll; + DWORD dwVersion = 0; + + hinstDll = LoadLibrary(lpszDllName); + + if(hinstDll) + { + DLLGETVERSIONPROC pDllGetVersion; + + pDllGetVersion = (DLLGETVERSIONPROC) GetProcAddress(hinstDll, "DllGetVersion"); + +/*Because some DLLs might not implement this function, you + must test for it explicitly. Depending on the particular + DLL, the lack of a DllGetVersion function can be a useful + indicator of the version. +*/ + if(pDllGetVersion) + { + DLLVERSIONINFO dvi; + HRESULT hr; + + ZeroMemory(&dvi, sizeof(dvi)); + dvi.cbSize = sizeof(dvi); + + hr = (*pDllGetVersion)(&dvi); + + if(SUCCEEDED(hr)) + { + dwVersion = PACKVERSION(dvi.dwMajorVersion, dvi.dwMinorVersion); + } + } + + FreeLibrary(hinstDll); + } + return dwVersion; +} +#endif + +#endif + + diff --git a/indra/llvfs/lldir_win32.h b/indra/llvfs/lldir_win32.h new file mode 100644 index 0000000000..fbeeef2732 --- /dev/null +++ b/indra/llvfs/lldir_win32.h @@ -0,0 +1,37 @@ +/** + * @file lldir_win32.h + * @brief Definition of directory utilities class for windows + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLDIR_WIN32_H +#define LL_LLDIR_WIN32_H + +#include "lldir.h" + +class LLDir_Win32 : public LLDir +{ +public: + LLDir_Win32(); + virtual ~LLDir_Win32(); + + virtual void initAppDirs(const std::string &app_name); +public: + virtual std::string getCurPath(); + virtual U32 countFilesInDir(const std::string &dirname, const std::string &mask); + virtual BOOL getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname, BOOL wrap); + virtual void getRandomFileInDir(const std::string &dirname, const std::string &mask, std::string &fname); + /*virtual*/ BOOL fileExists(const std::string &filename); + +private: + BOOL LLDir_Win32::getNextFileInDir(const llutf16string &dirname, const std::string &mask, std::string &fname, BOOL wrap); + + HANDLE mDirSearch_h; + llutf16string mCurrentDir; +}; + +#endif // LL_LLDIR_WIN32_H + + diff --git a/indra/llvfs/lllfsthread.cpp b/indra/llvfs/lllfsthread.cpp new file mode 100644 index 0000000000..57b4bc6d47 --- /dev/null +++ b/indra/llvfs/lllfsthread.cpp @@ -0,0 +1,308 @@ +/** + * @file lllfsthread.cpp + * @brief LLLFSThread base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llmath.h" +#include "lllfsthread.h" +#include "llstl.h" +#include "llapr.h" + +//============================================================================ + +/*static*/ LLLFSThread* LLLFSThread::sLocal = NULL; + +//============================================================================ +// Run on MAIN thread +//static +void LLLFSThread::initClass(bool local_is_threaded, bool local_run_always) +{ + llassert(sLocal == NULL); + sLocal = new LLLFSThread(local_is_threaded, local_run_always); +} + +//static +S32 LLLFSThread::updateClass(U32 ms_elapsed) +{ + sLocal->update(ms_elapsed); + return sLocal->getPending(); +} + +//static +void LLLFSThread::cleanupClass() +{ + sLocal->setQuitting(); + while (sLocal->getPending()) + { + sLocal->update(0); + } + delete sLocal; + sLocal = 0; +} + +//---------------------------------------------------------------------------- + +LLLFSThread::LLLFSThread(bool threaded, bool runalways) : + LLQueuedThread("LFS", threaded, runalways) +{ +} + +LLLFSThread::~LLLFSThread() +{ + // ~LLQueuedThread() will be called here +} + +//---------------------------------------------------------------------------- + +LLLFSThread::handle_t LLLFSThread::read(const LLString& filename, + U8* buffer, S32 offset, S32 numbytes, U32 priority, U32 flags) +{ + handle_t handle = generateHandle(); + + priority = llmax(priority, (U32)PRIORITY_LOW); // All reads are at least PRIORITY_LOW + Request* req = new Request(handle, priority, flags, + FILE_READ, filename, + buffer, offset, numbytes); + + bool res = addRequest(req); + if (!res) + { + llerrs << "LLLFSThread::read called after LLLFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +S32 LLLFSThread::readImmediate(const LLString& filename, + U8* buffer, S32 offset, S32 numbytes) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, PRIORITY_IMMEDIATE, 0, + FILE_READ, filename, + buffer, offset, numbytes); + + S32 res = addRequest(req) ? 1 : 0; + if (res == 0) + { + llerrs << "LLLFSThread::read called after LLLFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + } + else + { + llverify(waitForResult(handle, false) == true); + res = req->getBytesRead(); + completeRequest(handle); + } + return res; +} + +LLLFSThread::handle_t LLLFSThread::write(const LLString& filename, + U8* buffer, S32 offset, S32 numbytes, U32 flags) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, 0, flags, + FILE_WRITE, filename, + buffer, offset, numbytes); + + bool res = addRequest(req); + if (!res) + { + llerrs << "LLLFSThread::read called after LLLFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +S32 LLLFSThread::writeImmediate(const LLString& filename, + U8* buffer, S32 offset, S32 numbytes) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, PRIORITY_IMMEDIATE, 0, + FILE_WRITE, filename, + buffer, offset, numbytes); + + S32 res = addRequest(req) ? 1 : 0; + if (res == 0) + { + llerrs << "LLLFSThread::write called after LLLFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + } + else + { + llverify(waitForResult(handle, false) == true); + res = req->getBytesRead(); + completeRequest(handle); + } + return res; +} + + +LLLFSThread::handle_t LLLFSThread::rename(const LLString& filename, const LLString& newname, U32 flags) +{ + handle_t handle = generateHandle(); + + LLString* new_name_str = new LLString(newname); // deleted with Request + Request* req = new Request(handle, 0, flags, + FILE_RENAME, filename, + (U8*)new_name_str, 0, 0); + + bool res = addRequest(req); + if (!res) + { + llerrs << "LLLFSThread::rename called after LLLFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +LLLFSThread::handle_t LLLFSThread::remove(const LLString& filename, U32 flags) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, 0, flags, + FILE_RENAME, filename, + NULL, 0, 0); + + bool res = addRequest(req); + if (!res) + { + llerrs << "LLLFSThread::remove called after LLLFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +//============================================================================ +// Runs on its OWN thread + +bool LLLFSThread::processRequest(QueuedRequest* qreq) +{ + Request *req = (Request*)qreq; + + bool complete = req->processIO(); + + return complete; +} + +//============================================================================ + +LLLFSThread::Request::Request(handle_t handle, U32 priority, U32 flags, + operation_t op, const LLString& filename, + U8* buffer, S32 offset, S32 numbytes) : + QueuedRequest(handle, priority, flags), + mOperation(op), + mFileName(filename), + mBuffer(buffer), + mOffset(offset), + mBytes(numbytes), + mBytesRead(0) +{ + llassert(mBuffer); + + if (numbytes <= 0 && mOperation != FILE_RENAME && mOperation != FILE_REMOVE) + { + llwarns << "LLLFSThread: Request with numbytes = " << numbytes << llendl; + } +} + +void LLLFSThread::Request::finishRequest() +{ +} + +void LLLFSThread::Request::deleteRequest() +{ + if (getStatus() == STATUS_QUEUED || getStatus() == STATUS_ABORT) + { + llerrs << "Attempt to delete a queued LLLFSThread::Request!" << llendl; + } + if (mOperation == FILE_WRITE) + { + if (mFlags & AUTO_DELETE) + { + delete mBuffer; + } + } + else if (mOperation == FILE_RENAME) + { + LLString* new_name = (LLString*)mBuffer; + delete new_name; + } + LLQueuedThread::QueuedRequest::deleteRequest(); +} + +bool LLLFSThread::Request::processIO() +{ + bool complete = false; + if (mOperation == FILE_READ) + { + llassert(mOffset >= 0); + apr_file_t* filep = ll_apr_file_open(mFileName, LL_APR_RB); + if (!filep) + { + llwarns << "LLLFS: Unable to read file: " << mFileName << llendl; + mBytesRead = 0; // fail + return true; + } + if (mOffset < 0) + ll_apr_file_seek(filep, APR_END, 0); + else + ll_apr_file_seek(filep, APR_SET, mOffset); + mBytesRead = ll_apr_file_read(filep, mBuffer, mBytes ); + apr_file_close(filep); + complete = true; + //llinfos << llformat("LLLFSThread::READ '%s': %d bytes",mFileName.c_str(),mBytesRead) << llendl; + } + else if (mOperation == FILE_WRITE) + { + apr_file_t* filep = ll_apr_file_open(mFileName, LL_APR_WB); + if (!filep) + { + llwarns << "LLLFS: Unable to write file: " << mFileName << llendl; + mBytesRead = 0; // fail + return true; + } + if (mOffset < 0) + ll_apr_file_seek(filep, APR_END, 0); + else + ll_apr_file_seek(filep, APR_SET, mOffset); + mBytesRead = ll_apr_file_write(filep, mBuffer, mBytes ); + complete = true; + apr_file_close(filep); + //llinfos << llformat("LLLFSThread::WRITE '%s': %d bytes",mFileName.c_str(),mBytesRead) << llendl; + } + else if (mOperation == FILE_RENAME) + { + LLString* new_name = (LLString*)mBuffer; + ll_apr_file_rename(mFileName, *new_name); + complete = true; + //llinfos << llformat("LLLFSThread::RENAME '%s': '%s'",mFileName.c_str(),new_name->c_str()) << llendl; + } + else if (mOperation == FILE_REMOVE) + { + ll_apr_file_remove(mFileName); + complete = true; + //llinfos << llformat("LLLFSThread::REMOVE '%s'",mFileName.c_str()) << llendl; + } + else + { + llerrs << llformat("LLLFSThread::unknown operation: %d", mOperation) << llendl; + } + return complete; +} + +//============================================================================ diff --git a/indra/llvfs/lllfsthread.h b/indra/llvfs/lllfsthread.h new file mode 100644 index 0000000000..a55a2668b3 --- /dev/null +++ b/indra/llvfs/lllfsthread.h @@ -0,0 +1,119 @@ +/** + * @file lllfsthread.h + * @brief LLLFSThread base class + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLLFSTHREAD_H +#define LL_LLLFSTHREAD_H + +#include +#include +#include +#include + +#include "llapr.h" + +#include "llqueuedthread.h" + +//============================================================================ +// Threaded Local File System +//============================================================================ + +class LLLFSThread : public LLQueuedThread +{ + //------------------------------------------------------------------------ +public: + enum operation_t { + FILE_READ, + FILE_WRITE, + FILE_RENAME, + FILE_REMOVE + }; + + //------------------------------------------------------------------------ +public: + + class Request : public QueuedRequest + { + protected: + ~Request() {}; // use deleteRequest() + + public: + Request(handle_t handle, U32 priority, U32 flags, + operation_t op, const LLString& filename, + U8* buffer, S32 offset, S32 numbytes); + + S32 getBytes() + { + return mBytes; + } + S32 getBytesRead() + { + return mBytesRead; + } + S32 getOperation() + { + return mOperation; + } + U8* getBuffer() + { + return mBuffer; + } + const LLString& getFilename() + { + return mFileName; + } + + /*virtual*/ void finishRequest(); + /*virtual*/ void deleteRequest(); + + bool processIO(); + + private: + operation_t mOperation; + + LLString mFileName; + + U8* mBuffer; // dest for reads, source for writes, new UUID for rename + S32 mOffset; // offset into file, -1 = append (WRITE only) + S32 mBytes; // bytes to read from file, -1 = all + S32 mBytesRead; // bytes read from file + }; + + //------------------------------------------------------------------------ +public: + LLLFSThread(bool threaded = TRUE, bool runalways = TRUE); + ~LLLFSThread(); + + // Return a Request handle + handle_t read(const LLString& filename, + U8* buffer, S32 offset, S32 numbytes, U32 pri=PRIORITY_NORMAL, U32 flags = 0); + handle_t write(const LLString& filename, + U8* buffer, S32 offset, S32 numbytes, U32 flags = 0); + handle_t rename(const LLString& filename, const LLString& newname, U32 flags = 0); + handle_t remove(const LLString& filename, U32 flags = 0); + + // Return number of bytes read + S32 readImmediate(const LLString& filename, + U8* buffer, S32 offset, S32 numbytes); + S32 writeImmediate(const LLString& filename, + U8* buffer, S32 offset, S32 numbytes); + + static void initClass(bool local_is_threaded = TRUE, bool run_always = TRUE); // Setup sLocal + static S32 updateClass(U32 ms_elapsed); + static void cleanupClass(); // Delete sLocal + +protected: + /*virtual*/ bool processRequest(QueuedRequest* req); + +public: + static LLLFSThread* sLocal; // Default local file thread +}; + +//============================================================================ + + +#endif // LL_LLLFSTHREAD_H diff --git a/indra/llvfs/llvfile.cpp b/indra/llvfs/llvfile.cpp new file mode 100644 index 0000000000..36ac569d02 --- /dev/null +++ b/indra/llvfs/llvfile.cpp @@ -0,0 +1,415 @@ +/** + * @file llvfile.cpp + * @brief Implementation of virtual file + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llvfile.h" + +#include "llerror.h" +#include "llthread.h" +#include "llvfs.h" + +const S32 LLVFile::READ = 0x00000001; +const S32 LLVFile::WRITE = 0x00000002; +const S32 LLVFile::READ_WRITE = 0x00000003; // LLVFile::READ & LLVFile::WRITE +const S32 LLVFile::APPEND = 0x00000006; // 0x00000004 & LLVFile::WRITE + +//---------------------------------------------------------------------------- +LLVFSThread* LLVFile::sVFSThread = NULL; +BOOL LLVFile::sAllocdVFSThread = FALSE; +BOOL LLVFile::ALLOW_ASYNC = TRUE; +//---------------------------------------------------------------------------- + +//============================================================================ + +LLVFile::LLVFile(LLVFS *vfs, const LLUUID &file_id, const LLAssetType::EType file_type, S32 mode) +{ + mFileType = file_type; + + mFileID = file_id; + mPosition = 0; + mMode = mode; + mVFS = vfs; + + mBytesRead = 0; + mHandle = LLVFSThread::nullHandle(); + mPriority = 128.f; + + mVFS->incLock(mFileID, mFileType, VFSLOCK_OPEN); +} + +LLVFile::~LLVFile() +{ + if (!isReadComplete()) + { + if (mHandle != LLVFSThread::nullHandle()) + { + if (!(mMode & LLVFile::WRITE)) + { + // llwarns << "Destroying LLVFile with pending async read/write, aborting..." << llendl; + sVFSThread->abortRequest(mHandle, LLVFSThread::AUTO_COMPLETE); + } + else // WRITE + { + sVFSThread->setFlags(mHandle, LLVFSThread::AUTO_COMPLETE); + } + } + } + mVFS->decLock(mFileID, mFileType, VFSLOCK_OPEN); +} + +BOOL LLVFile::read(U8 *buffer, S32 bytes, BOOL async, F32 priority) +{ + if (! (mMode & READ)) + { + llwarns << "Attempt to read from file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << llendl; + return FALSE; + } + + if (mHandle != LLVFSThread::nullHandle()) + { + llwarns << "Attempt to read from vfile object " << mFileID << " with pending async operation" << llendl; + return FALSE; + } + mPriority = priority; + + BOOL success = TRUE; + + // We can't do a read while there are pending async writes + waitForLock(VFSLOCK_APPEND); + + // FIXME + if (async) + { + mHandle = sVFSThread->read(mVFS, mFileID, mFileType, buffer, mPosition, bytes, threadPri()); + } + else + { + // We can't do a read while there are pending async writes on this file + mBytesRead = sVFSThread->readImmediate(mVFS, mFileID, mFileType, buffer, mPosition, bytes); + mPosition += mBytesRead; + if (! mBytesRead) + { + success = FALSE; + } + } + + return success; +} + +//static +U8* LLVFile::readFile(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type, S32* bytes_read) +{ + U8 *data; + LLVFile file(vfs, uuid, type, LLVFile::READ); + S32 file_size = file.getSize(); + if (file_size == 0) + { + // File is empty. + data = NULL; + } + else + { + data = new U8[file_size]; + file.read(data, file_size); + + if (file.getLastBytesRead() != (S32)file_size) + { + delete[] data; + data = NULL; + file_size = 0; + } + } + if (bytes_read) + { + *bytes_read = file_size; + } + return data; +} + +void LLVFile::setReadPriority(const F32 priority) +{ + mPriority = priority; + if (mHandle != LLVFSThread::nullHandle()) + { + sVFSThread->setPriority(mHandle, threadPri()); + } +} + +BOOL LLVFile::isReadComplete() +{ + BOOL res = TRUE; + if (mHandle != LLVFSThread::nullHandle()) + { + LLVFSThread::Request* req = (LLVFSThread::Request*)sVFSThread->getRequest(mHandle); + LLVFSThread::status_t status = req->getStatus(); + if (status == LLVFSThread::STATUS_COMPLETE) + { + mBytesRead = req->getBytesRead(); + mPosition += mBytesRead; + sVFSThread->completeRequest(mHandle); + mHandle = LLVFSThread::nullHandle(); + } + else + { + res = FALSE; + } + } + return res; +} + +S32 LLVFile::getLastBytesRead() +{ + return mBytesRead; +} + +BOOL LLVFile::eof() +{ + return mPosition >= getSize(); +} + +BOOL LLVFile::write(const U8 *buffer, S32 bytes) +{ + if (! (mMode & WRITE)) + { + llwarns << "Attempt to write to file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << llendl; + } + if (mHandle != LLVFSThread::nullHandle()) + { + llerrs << "Attempt to write to vfile object " << mFileID << " with pending async operation" << llendl; + return FALSE; + } + BOOL success = TRUE; + + // FIXME: allow async writes? potential problem wit mPosition... + if (mMode == APPEND) // all appends are async (but WRITEs are not) + { + U8* writebuf = new U8[bytes]; + memcpy(writebuf, buffer, bytes); + S32 offset = -1; + mHandle = sVFSThread->write(mVFS, mFileID, mFileType, + writebuf, offset, bytes, + LLVFSThread::AUTO_COMPLETE | LLVFSThread::AUTO_DELETE); + mHandle = LLVFSThread::nullHandle(); // AUTO_COMPLETE means we don't track this + } + else + { + // We can't do a write while there are pending reads or writes on this file + waitForLock(VFSLOCK_READ); + waitForLock(VFSLOCK_APPEND); + + S32 pos = (mMode & APPEND) == APPEND ? -1 : mPosition; + + S32 wrote = sVFSThread->writeImmediate(mVFS, mFileID, mFileType, (U8*)buffer, pos, bytes); + + mPosition += wrote; + + if (wrote < bytes) + { + llwarns << "Tried to write " << bytes << " bytes, actually wrote " << wrote << llendl; + + success = FALSE; + } + } + return success; +} + +//static +BOOL LLVFile::writeFile(const U8 *buffer, S32 bytes, LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type) +{ + LLVFile file(vfs, uuid, type, LLVFile::WRITE); + file.setMaxSize(bytes); + return file.write(buffer, bytes); +} + +BOOL LLVFile::seek(S32 offset, S32 origin) +{ + if (mMode == APPEND) + { + llwarns << "Attempt to seek on append-only file" << llendl; + return FALSE; + } + + if (-1 == origin) + { + origin = mPosition; + } + + S32 new_pos = origin + offset; + + S32 size = getSize(); // Calls waitForLock(VFSLOCK_APPEND) + + if (new_pos > size) + { + llwarns << "Attempt to seek past end of file" << llendl; + + mPosition = size; + return FALSE; + } + else if (new_pos < 0) + { + llwarns << "Attempt to seek past beginning of file" << llendl; + + mPosition = 0; + return FALSE; + } + + mPosition = new_pos; + return TRUE; +} + +S32 LLVFile::tell() const +{ + return mPosition; +} + +S32 LLVFile::getSize() +{ + waitForLock(VFSLOCK_APPEND); + S32 size = mVFS->getSize(mFileID, mFileType); + + return size; +} + +S32 LLVFile::getMaxSize() +{ + S32 size = mVFS->getMaxSize(mFileID, mFileType); + + return size; +} + +BOOL LLVFile::setMaxSize(S32 size) +{ + if (! (mMode & WRITE)) + { + llwarns << "Attempt to change size of file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << llendl; + + return FALSE; + } + + if (!mVFS->checkAvailable(size)) + { + LLFastTimer t(LLFastTimer::FTM_VFILE_WAIT); + S32 count = 0; + while (sVFSThread->getPending() > 1000) + { + if (count % 100 == 0) + { + llinfos << "VFS catching up... Pending: " << sVFSThread->getPending() << llendl; + } + if (sVFSThread->isPaused()) + { + sVFSThread->updateQueue(0); + } + ms_sleep(10); + } + } + return mVFS->setMaxSize(mFileID, mFileType, size); +} + +BOOL LLVFile::rename(const LLUUID &new_id, const LLAssetType::EType new_type) +{ + if (! (mMode & WRITE)) + { + llwarns << "Attempt to rename file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << llendl; + + return FALSE; + } + + if (mHandle != LLVFSThread::nullHandle()) + { + llwarns << "Renaming file with pending async read" << llendl; + } + + waitForLock(VFSLOCK_READ); + waitForLock(VFSLOCK_APPEND); + + // we need to release / replace our own lock + // since the renamed file will inherit locks from the new name + mVFS->decLock(mFileID, mFileType, VFSLOCK_OPEN); + mVFS->renameFile(mFileID, mFileType, new_id, new_type); + mVFS->incLock(new_id, new_type, VFSLOCK_OPEN); + + mFileID = new_id; + mFileType = new_type; + + return TRUE; +} + +BOOL LLVFile::remove() +{ +// llinfos << "Removing file " << mFileID << llendl; + + if (! (mMode & WRITE)) + { + // Leaving paranoia warning just because this should be a very infrequent + // operation. + llwarns << "Remove file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << llendl; + } + + if (mHandle != LLVFSThread::nullHandle()) + { + llwarns << "Removing file with pending async read" << llendl; + } + + // why not seek back to the beginning of the file too? + mPosition = 0; + + waitForLock(VFSLOCK_READ); + waitForLock(VFSLOCK_APPEND); + mVFS->removeFile(mFileID, mFileType); + + return TRUE; +} + +// static +void LLVFile::initClass(LLVFSThread* vfsthread) +{ + if (!vfsthread) + { + if (LLVFSThread::sLocal != NULL) + { + vfsthread = LLVFSThread::sLocal; + } + else + { + vfsthread = new LLVFSThread(); + sAllocdVFSThread = TRUE; + } + } + sVFSThread = vfsthread; +} + +// static +void LLVFile::cleanupClass() +{ + if (sAllocdVFSThread) + { + delete sVFSThread; + } + sVFSThread = NULL; +} + +bool LLVFile::isLocked(EVFSLock lock) +{ + return mVFS->isLocked(mFileID, mFileType, lock) ? true : false; +} + +void LLVFile::waitForLock(EVFSLock lock) +{ + LLFastTimer t(LLFastTimer::FTM_VFILE_WAIT); + // spin until the lock clears + while (isLocked(lock)) + { + if (sVFSThread->isPaused()) + { + sVFSThread->updateQueue(0); + } + ms_sleep(1); + } +} diff --git a/indra/llvfs/llvfile.h b/indra/llvfs/llvfile.h new file mode 100644 index 0000000000..c00e843cad --- /dev/null +++ b/indra/llvfs/llvfile.h @@ -0,0 +1,75 @@ +/** + * @file llvfile.h + * @brief Definition of virtual file + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLVFILE_H +#define LL_LLVFILE_H + +#include "lluuid.h" +#include "llassettype.h" +#include "llvfs.h" +#include "llvfsthread.h" + +class LLVFile +{ +public: + LLVFile(LLVFS *vfs, const LLUUID &file_id, const LLAssetType::EType file_type, S32 mode = LLVFile::READ); + ~LLVFile(); + + BOOL read(U8 *buffer, S32 bytes, BOOL async = FALSE, F32 priority = 128.f); + static U8* readFile(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type, S32* bytes_read = 0); + void setReadPriority(const F32 priority); + BOOL isReadComplete(); + S32 getLastBytesRead(); + BOOL eof(); + + BOOL write(const U8 *buffer, S32 bytes); + static BOOL writeFile(const U8 *buffer, S32 bytes, LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type); + BOOL seek(S32 offset, S32 origin = -1); + S32 tell() const; + + S32 getSize(); + S32 getMaxSize(); + BOOL setMaxSize(S32 size); + BOOL rename(const LLUUID &new_id, const LLAssetType::EType new_type); + BOOL remove(); + + bool isLocked(EVFSLock lock); + void waitForLock(EVFSLock lock); + + static void initClass(LLVFSThread* vfsthread = NULL); + static void cleanupClass(); + static LLVFSThread* getVFSThread() { return sVFSThread; } + +protected: + static LLVFSThread* sVFSThread; + static BOOL sAllocdVFSThread; + U32 threadPri() { return LLVFSThread::PRIORITY_NORMAL + llmin((U32)mPriority,(U32)0xfff); } + +public: + static const S32 READ; + static const S32 WRITE; + static const S32 READ_WRITE; + static const S32 APPEND; + + static BOOL ALLOW_ASYNC; + +protected: + LLAssetType::EType mFileType; + + LLUUID mFileID; + S32 mPosition; + S32 mMode; + LLVFS *mVFS; + F32 mPriority; + BOOL mOnReadQueue; + + S32 mBytesRead; + LLVFSThread::handle_t mHandle; +}; + +#endif diff --git a/indra/llvfs/llvfs.cpp b/indra/llvfs/llvfs.cpp new file mode 100644 index 0000000000..39b12035c9 --- /dev/null +++ b/indra/llvfs/llvfs.cpp @@ -0,0 +1,2048 @@ +/** + * @file llvfs.cpp + * @brief Implementation of virtual file system + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include +#include +#include +#include +#include +#if LL_WINDOWS +#include +#else +#include +#endif + +#include "llvfs.h" +#include "llstl.h" + +const S32 FILE_BLOCK_MASK = 0x000003FF; // 1024-byte blocks +const S32 VFS_CLEANUP_SIZE = 5242880; // how much space we free up in a single stroke +const S32 BLOCK_LENGTH_INVALID = -1; // mLength for invalid LLVFSFileBlocks + +LLVFS *gVFS = NULL; + +// internal class definitions +class LLVFSBlock +{ +public: + LLVFSBlock() + { + mLocation = 0; + mLength = 0; + } + + LLVFSBlock(U32 loc, S32 size) + { + mLocation = loc; + mLength = size; + } + + static BOOL insertFirstLL(LLVFSBlock *first, LLVFSBlock *second) + { + return first->mLocation != second->mLocation + ? first->mLocation < second->mLocation + : first->mLength < second->mLength; + + } + +public: + U32 mLocation; + S32 mLength; // allocated block size +}; + +LLVFSFileSpecifier::LLVFSFileSpecifier() +: mFileID(), + mFileType( LLAssetType::AT_NONE ) +{ +} + +LLVFSFileSpecifier::LLVFSFileSpecifier(const LLUUID &file_id, const LLAssetType::EType file_type) +{ + mFileID = file_id; + mFileType = file_type; +} + +bool LLVFSFileSpecifier::operator<(const LLVFSFileSpecifier &rhs) const +{ + return (mFileID == rhs.mFileID) + ? mFileType < rhs.mFileType + : mFileID < rhs.mFileID; +} + +bool LLVFSFileSpecifier::operator==(const LLVFSFileSpecifier &rhs) const +{ + return (mFileID == rhs.mFileID && + mFileType == rhs.mFileType); +} + + +class LLVFSFileBlock : public LLVFSBlock, public LLVFSFileSpecifier +{ +public: + LLVFSFileBlock() : LLVFSBlock(), LLVFSFileSpecifier() + { + init(); + } + + LLVFSFileBlock(const LLUUID &file_id, LLAssetType::EType file_type, U32 loc = 0, S32 size = 0) + : LLVFSBlock(loc, size), LLVFSFileSpecifier( file_id, file_type ) + { + init(); + } + + void init() + { + mSize = 0; + mIndexLocation = -1; + mAccessTime = (U32)time(NULL); + + for (S32 i = 0; i < (S32)VFSLOCK_COUNT; i++) + { + mLocks[(EVFSLock)i] = 0; + } + } + + #ifdef LL_LITTLE_ENDIAN + inline void swizzleCopy(void *dst, void *src, int size) { memcpy(dst, src, size); } + + #else + + inline U32 swizzle32(U32 x) + { + return(((x >> 24) & 0x000000FF) | ((x >> 8) & 0x0000FF00) | ((x << 8) & 0x00FF0000) |((x << 24) & 0xFF000000)); + } + + inline U16 swizzle16(U16 x) + { + return( ((x >> 8) & 0x000000FF) | ((x << 8) & 0x0000FF00) ); + } + + inline void swizzleCopy(void *dst, void *src, int size) + { + if(size == 4) + { + ((U32*)dst)[0] = swizzle32(((U32*)src)[0]); + } + else if(size == 2) + { + ((U16*)dst)[0] = swizzle16(((U16*)src)[0]); + } + else + { + // Perhaps this should assert... + memcpy(dst, src, size); + } + } + + #endif + + void serialize(U8 *buffer) + { + swizzleCopy(buffer, &mLocation, 4); + buffer += 4; + swizzleCopy(buffer, &mLength, 4); + buffer +=4; + swizzleCopy(buffer, &mAccessTime, 4); + buffer +=4; + memcpy(buffer, &mFileID.mData, 16); + buffer += 16; + S16 temp_type = mFileType; + swizzleCopy(buffer, &temp_type, 2); + buffer += 2; + swizzleCopy(buffer, &mSize, 4); + } + + void deserialize(U8 *buffer, const S32 index_loc) + { + mIndexLocation = index_loc; + + swizzleCopy(&mLocation, buffer, 4); + buffer += 4; + swizzleCopy(&mLength, buffer, 4); + buffer += 4; + swizzleCopy(&mAccessTime, buffer, 4); + buffer += 4; + memcpy(&mFileID.mData, buffer, 16); + buffer += 16; + S16 temp_type; + swizzleCopy(&temp_type, buffer, 2); + mFileType = (LLAssetType::EType)temp_type; + buffer += 2; + swizzleCopy(&mSize, buffer, 4); + } + + static BOOL insertLRU(LLVFSFileBlock* const& first, + LLVFSFileBlock* const& second) + { + return (first->mAccessTime == second->mAccessTime) + ? *first < *second + : first->mAccessTime < second->mAccessTime; + } + +public: + S32 mSize; + S32 mIndexLocation; // location of index entry + U32 mAccessTime; + BOOL mLocks[VFSLOCK_COUNT]; // number of outstanding locks of each type + + static const S32 SERIAL_SIZE; +}; + +// Helper structure for doing lru w/ stl... is there a simpler way? +struct LLVFSFileBlock_less +{ + bool operator()(LLVFSFileBlock* const& lhs, LLVFSFileBlock* const& rhs) const + { + return (LLVFSFileBlock::insertLRU(lhs, rhs)) ? true : false; + } +}; + + +const S32 LLVFSFileBlock::SERIAL_SIZE = 34; + + +LLVFS::LLVFS(const char *index_filename, const char *data_filename, const BOOL read_only, const U32 presize, const BOOL remove_after_crash) +: mRemoveAfterCrash(remove_after_crash) +{ + mDataMutex = new LLMutex(0); + + S32 i; + for (i = 0; i < VFSLOCK_COUNT; i++) + { + mLockCounts[i] = 0; + } + mValid = VFSVALID_OK; + mReadOnly = read_only; + mIndexFilename = new char[strlen(index_filename) + 1]; + mDataFilename = new char[strlen(data_filename) + 1]; + strcpy(mIndexFilename, index_filename); + strcpy(mDataFilename, data_filename); + + const char *file_mode = mReadOnly ? "rb" : "r+b"; + + if (! (mDataFP = openAndLock(mDataFilename, file_mode, mReadOnly))) + { + + if (mReadOnly) + { + llwarns << "Can't find " << mDataFilename << " to open read-only VFS" << llendl; + mValid = VFSVALID_BAD_CANNOT_OPEN_READONLY; + return; + } + + if((mDataFP = openAndLock(mDataFilename, "w+b", FALSE))) + { + // Since we're creating this data file, assume any index file is bogus + // remove the index, since this vfs is now blank + LLFile::remove(mIndexFilename); + } + else + { + llwarns << "Can't open VFS data file " << mDataFilename << " attempting to use alternate" << llendl; + + char *temp_index = new char[strlen(mIndexFilename) + 10]; + char *temp_data = new char[strlen(mDataFilename) + 10]; + + for (U32 count = 0; count < 256; count++) + { + sprintf(temp_index, "%s.%u", mIndexFilename, count); + sprintf(temp_data, "%s.%u", mDataFilename, count); + + // try just opening, then creating, each alternate + if ((mDataFP = openAndLock(temp_data, "r+b", FALSE))) + { + break; + } + + if ((mDataFP = openAndLock(temp_data, "w+b", FALSE))) + { + // we're creating the datafile, so nuke the indexfile + LLFile::remove(temp_index); + break; + } + } + + if (! mDataFP) + { + llwarns << "Couldn't open vfs data file after trying many alternates" << llendl; + mValid = VFSVALID_BAD_CANNOT_CREATE; + return; + } + + delete[] mIndexFilename; + delete[] mDataFilename; + + mIndexFilename = temp_index; + mDataFilename = temp_data; + } + + if (presize) + { + presizeDataFile(presize); + } + } + + // Did we leave this file open for writing last time? + // If so, close it and start over. + if (!mReadOnly && mRemoveAfterCrash) + { + llstat marker_info; + char* marker = new char[strlen(mDataFilename) + strlen(".open") + 1]; + sprintf(marker, "%s.open", mDataFilename); + if (!LLFile::stat(marker, &marker_info)) + { + // marker exists, kill the lock and the VFS files + unlockAndClose(mDataFP); + mDataFP = NULL; + + llwarns << "VFS: File left open on last run, removing old VFS file " << mDataFilename << llendl; + LLFile::remove(mIndexFilename); + LLFile::remove(mDataFilename); + LLFile::remove(marker); + + mDataFP = openAndLock(mDataFilename, "w+b", FALSE); + if (!mDataFP) + { + llwarns << "Can't open VFS data file in crash recovery" << llendl; + mValid = VFSVALID_BAD_CANNOT_CREATE; + return; + } + + if (presize) + { + presizeDataFile(presize); + } + } + delete [] marker; + marker = NULL; + } + + // determine the real file size + fseek(mDataFP, 0, SEEK_END); + U32 data_size = ftell(mDataFP); + + // read the index file + // make sure there's at least one file in it too + // if not, we'll treat this as a new vfs + llstat fbuf; + if (! LLFile::stat(mIndexFilename, &fbuf) && + fbuf.st_size >= LLVFSFileBlock::SERIAL_SIZE && + (mIndexFP = openAndLock(mIndexFilename, file_mode, mReadOnly)) + ) + { + U8 *buffer = new U8[fbuf.st_size]; + fread(buffer, fbuf.st_size, 1, mIndexFP); + + U8 *tmp_ptr = buffer; + + LLLinkedList files_by_loc; + files_by_loc.setInsertBefore(LLVFSBlock::insertFirstLL); + + while (tmp_ptr < buffer + fbuf.st_size) + { + LLVFSFileBlock *block = new LLVFSFileBlock(); + + block->deserialize(tmp_ptr, (S32)(tmp_ptr - buffer)); + + // Do sanity check on this block. + // Note that this skips zero size blocks, which helps VFS + // to heal after some errors. JC + if (block->mLength > 0 && + (U32)block->mLength <= data_size && + block->mLocation >= 0 && + block->mLocation < data_size && + block->mSize > 0 && + block->mSize <= block->mLength && + block->mFileType >= LLAssetType::AT_NONE && + block->mFileType < LLAssetType::AT_COUNT) + { + mFileBlocks.insert(fileblock_map::value_type(*block, block)); + files_by_loc.addDataSorted(block); + } + else + if (block->mLength && block->mSize > 0) + { + // this is corrupt, not empty + llwarns << "VFS corruption: " << block->mFileID << " (" << block->mFileType << ") at index " << block->mIndexLocation << " DS: " << data_size << llendl; + llwarns << "Length: " << block->mLength << "\tLocation: " << block->mLocation << "\tSize: " << block->mSize << llendl; + llwarns << "File has bad data - VFS removed" << llendl; + + delete[] buffer; + delete block; + + unlockAndClose( mIndexFP ); + mIndexFP = NULL; + LLFile::remove( mIndexFilename ); + + unlockAndClose( mDataFP ); + mDataFP = NULL; + LLFile::remove( mDataFilename ); + + mValid = VFSVALID_BAD_CORRUPT; + return; + } + else + { + // this is a null or bad entry, skip it + S32 index_loc = (S32)(tmp_ptr - buffer); + mIndexHoles.push_back(index_loc); + + delete block; + } + + tmp_ptr += block->SERIAL_SIZE; + } + delete[] buffer; + + // discover all the free blocks + LLVFSFileBlock *last_file_block = (LLVFSFileBlock*)files_by_loc.getFirstData(); + + if (last_file_block) + { + // check for empty space at the beginning + if (last_file_block->mLocation > 0) + { + LLVFSBlock *block = new LLVFSBlock(0, last_file_block->mLocation); + addFreeBlock(block); + } + + LLVFSFileBlock *cur_file_block; + while ((cur_file_block = (LLVFSFileBlock*)files_by_loc.getNextData())) + { + if (cur_file_block->mLocation == last_file_block->mLocation + && cur_file_block->mLength == last_file_block->mLength) + { + llwarns << "VFS: removing duplicate entry" + << " at " << cur_file_block->mLocation + << " length " << cur_file_block->mLength + << " size " << cur_file_block->mSize + << " ID " << cur_file_block->mFileID + << " type " << cur_file_block->mFileType + << llendl; + + // Duplicate entries. Nuke them both for safety. + mFileBlocks.erase(*cur_file_block); // remove ID/type entry + if (cur_file_block->mLength > 0) + { + // convert to hole + LLVFSBlock* block = new LLVFSBlock(cur_file_block->mLocation, + cur_file_block->mLength); + addFreeBlock(block); + } + lockData(); // needed for sync() + sync(cur_file_block, TRUE); // remove first on disk + sync(last_file_block, TRUE); // remove last on disk + unlockData(); // needed for sync() + last_file_block = cur_file_block; + continue; + } + + U32 loc = last_file_block->mLocation + last_file_block->mLength; + S32 length = cur_file_block->mLocation - loc; + + if (length < 0 || loc < 0 || loc > data_size) + { + // Invalid VFS + unlockAndClose( mIndexFP ); + mIndexFP = NULL; + LLFile::remove( mIndexFilename ); + + unlockAndClose( mDataFP ); + mDataFP = NULL; + LLFile::remove( mDataFilename ); + + llwarns << "VFS: overlapping entries" + << " at " << cur_file_block->mLocation + << " length " << cur_file_block->mLength + << " ID " << cur_file_block->mFileID + << " type " << cur_file_block->mFileType + << llendl; + mValid = VFSVALID_BAD_CORRUPT; + return; + } + + if (length > 0) + { + LLVFSBlock *block = new LLVFSBlock(loc, length); + addFreeBlock(block); + } + + last_file_block = cur_file_block; + } + + // also note any empty space at the end + U32 loc = last_file_block->mLocation + last_file_block->mLength; + if (loc < data_size) + { + LLVFSBlock *block = new LLVFSBlock(loc, data_size - loc); + addFreeBlock(block); + } + } + else + { + LLVFSBlock *first_block = new LLVFSBlock(0, data_size); + addFreeBlock(first_block); + } + } + else + { + if (mReadOnly) + { + llwarns << "Can't find " << mIndexFilename << " to open read-only VFS" << llendl; + mValid = VFSVALID_BAD_CANNOT_OPEN_READONLY; + return; + } + + + mIndexFP = openAndLock(mIndexFilename, "w+b", FALSE); + if (!mIndexFP) + { + llwarns << "Couldn't open an index file for the VFS, probably a sharing violation!" << llendl; + + unlockAndClose( mDataFP ); + mDataFP = NULL; + LLFile::remove( mDataFilename ); + + mValid = VFSVALID_BAD_CANNOT_CREATE; + return; + } + + // no index file, start from scratch w/ 1GB allocation + LLVFSBlock *first_block = new LLVFSBlock(0, data_size ? data_size : 0x40000000); + addFreeBlock(first_block); + } + + // Open marker file to look for bad shutdowns + if (!mReadOnly && mRemoveAfterCrash) + { + char* marker = new char[strlen(mDataFilename) + strlen(".open") + 1]; + sprintf(marker, "%s.open", mDataFilename); + FILE* marker_fp = LLFile::fopen(marker, "w"); + if (marker_fp) + { + fclose(marker_fp); + marker_fp = NULL; + } + delete [] marker; + marker = NULL; + } + + llinfos << "VFS: Using index file " << mIndexFilename << " and data file " << mDataFilename << llendl; + + mValid = VFSVALID_OK; +} + +LLVFS::~LLVFS() +{ + if (mDataMutex->isLocked()) + { + llerrs << "LLVFS destroyed with mutex locked" << llendl; + } + + unlockAndClose(mIndexFP); + mIndexFP = NULL; + + fileblock_map::const_iterator it; + for (it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + delete (*it).second; + } + mFileBlocks.clear(); + + mFreeBlocksByLength.clear(); + + for_each(mFreeBlocksByLocation.begin(), mFreeBlocksByLocation.end(), DeletePairedPointer()); + + unlockAndClose(mDataFP); + mDataFP = NULL; + + // Remove marker file + if (!mReadOnly && mRemoveAfterCrash) + { + char* marker_file = new char[strlen(mDataFilename) + strlen(".open") + 1]; + sprintf(marker_file, "%s.open", mDataFilename); + LLFile::remove(marker_file); + delete [] marker_file; + marker_file = NULL; + } + + delete[] mIndexFilename; + mIndexFilename = NULL; + delete[] mDataFilename; + mDataFilename = NULL; + + delete mDataMutex; +} + +void LLVFS::presizeDataFile(const U32 size) +{ + if (!mDataFP) + { + llerrs << "LLVFS::presizeDataFile() with no data file open" << llendl; + } + + // we're creating this file for the first time, size it + fseek(mDataFP, size-1, SEEK_SET); + S32 tmp = 0; + tmp = (S32)fwrite(&tmp, 1, 1, mDataFP); + // fflush(mDataFP); + + // also remove any index, since this vfs is now blank + LLFile::remove(mIndexFilename); + + if (tmp) + { + llinfos << "Pre-sized VFS data file to " << ftell(mDataFP) << " bytes" << llendl; + } + else + { + llwarns << "Failed to pre-size VFS data file" << llendl; + } +} + +BOOL LLVFS::getExists(const LLUUID &file_id, const LLAssetType::EType file_type) +{ + LLVFSFileBlock *block = NULL; + + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + block = (*it).second; + block->mAccessTime = (U32)time(NULL); + } + + BOOL res = (block && block->mLength > 0) ? TRUE : FALSE; + + unlockData(); + + return res; +} + +S32 LLVFS::getSize(const LLUUID &file_id, const LLAssetType::EType file_type) +{ + S32 size = 0; + + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + + } + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + + block->mAccessTime = (U32)time(NULL); + size = block->mSize; + } + + unlockData(); + + return size; +} + +S32 LLVFS::getMaxSize(const LLUUID &file_id, const LLAssetType::EType file_type) +{ + S32 size = 0; + + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + + block->mAccessTime = (U32)time(NULL); + size = block->mLength; + } + + unlockData(); + + return size; +} + +BOOL LLVFS::checkAvailable(S32 max_size) +{ + blocks_length_map_t::iterator iter = mFreeBlocksByLength.lower_bound(max_size); // first entry >= size + return (iter == mFreeBlocksByLength.end()) ? FALSE : TRUE; +} + +BOOL LLVFS::setMaxSize(const LLUUID &file_id, const LLAssetType::EType file_type, S32 max_size) +{ + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + if (mReadOnly) + { + llerrs << "Attempt to write to read-only VFS" << llendl; + } + if (max_size <= 0) + { + llwarns << "VFS: Attempt to assign size " << max_size << " to vfile " << file_id << llendl; + return FALSE; + } + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + LLVFSFileBlock *block = NULL; + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + block = (*it).second; + } + + // round all sizes upward to KB increments + if (max_size & FILE_BLOCK_MASK) + { + max_size += FILE_BLOCK_MASK; + max_size &= ~FILE_BLOCK_MASK; + } + + if (block && block->mLength > 0) + { + block->mAccessTime = (U32)time(NULL); + + if (max_size == block->mLength) + { + unlockData(); + return TRUE; + } + else if (max_size < block->mLength) + { + // this file is shrinking + LLVFSBlock *free_block = new LLVFSBlock(block->mLocation + max_size, block->mLength - max_size); + + addFreeBlock(free_block); + + block->mLength = max_size; + + if (block->mLength < block->mSize) + { + // JC: Was a warning, but Ian says it's bad. + llerrs << "Truncating virtual file " << file_id << " to " << block->mLength << " bytes" << llendl; + block->mSize = block->mLength; + } + + sync(block); + //mergeFreeBlocks(); + + unlockData(); + return TRUE; + } + else if (max_size > block->mLength) + { + // this file is growing + // first check for an adjacent free block to grow into + S32 size_increase = max_size - block->mLength; + + // Find the first free block with and addres > block->mLocation + LLVFSBlock *free_block; + blocks_location_map_t::iterator iter = mFreeBlocksByLocation.upper_bound(block->mLocation); + if (iter != mFreeBlocksByLocation.end()) + { + free_block = iter->second; + + if (free_block->mLocation == block->mLocation + block->mLength && + free_block->mLength >= size_increase) + { + // this free block is at the end of the file and is large enough + + // Must call useFreeSpace before sync(), as sync() + // unlocks data structures. + useFreeSpace(free_block, size_increase); + block->mLength += size_increase; + sync(block); + + unlockData(); + return TRUE; + } + } + + // no adjecent free block, find one in the list + free_block = findFreeBlock(max_size, block); + + if (free_block) + { + if (block->mLength > 0) + { + // create a new free block where this file used to be + LLVFSBlock *new_free_block = new LLVFSBlock(block->mLocation, block->mLength); + + addFreeBlock(new_free_block); + + if (block->mSize > 0) + { + // move the file into the new block + U8 *buffer = new U8[block->mSize]; + fseek(mDataFP, block->mLocation, SEEK_SET); + fread(buffer, block->mSize, 1, mDataFP); + fseek(mDataFP, free_block->mLocation, SEEK_SET); + fwrite(buffer, block->mSize, 1, mDataFP); + // fflush(mDataFP); + + delete[] buffer; + } + } + + block->mLocation = free_block->mLocation; + + block->mLength = max_size; + + // Must call useFreeSpace before sync(), as sync() + // unlocks data structures. + useFreeSpace(free_block, max_size); + + sync(block); + + unlockData(); + return TRUE; + } + else + { + llwarns << "VFS: No space (" << max_size << ") to resize existing vfile " << file_id << llendl; + //dumpMap(); + unlockData(); + dumpStatistics(); + return FALSE; + } + } + } + else + { + // find a free block in the list + LLVFSBlock *free_block = findFreeBlock(max_size); + + if (free_block) + { + if (block) + { + block->mLocation = free_block->mLocation; + block->mLength = max_size; + } + else + { + // this file doesn't exist, create it + block = new LLVFSFileBlock(file_id, file_type, free_block->mLocation, max_size); + mFileBlocks.insert(fileblock_map::value_type(spec, block)); + } + + // Must call useFreeSpace before sync(), as sync() + // unlocks data structures. + useFreeSpace(free_block, max_size); + block->mAccessTime = (U32)time(NULL); + + sync(block); + } + else + { + llwarns << "VFS: No space (" << max_size << ") for new virtual file " << file_id << llendl; + //dumpMap(); + unlockData(); + dumpStatistics(); + return FALSE; + } + } + unlockData(); + return TRUE; +} + + +// WARNING: HERE BE DRAGONS! +// rename is the weirdest VFS op, because the file moves but the locks don't! +void LLVFS::renameFile(const LLUUID &file_id, const LLAssetType::EType file_type, + const LLUUID &new_id, const LLAssetType::EType &new_type) +{ + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + if (mReadOnly) + { + llerrs << "Attempt to write to read-only VFS" << llendl; + } + + lockData(); + + LLVFSFileSpecifier new_spec(new_id, new_type); + LLVFSFileSpecifier old_spec(file_id, file_type); + + fileblock_map::iterator it = mFileBlocks.find(old_spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *src_block = (*it).second; + + // this will purge the data but leave the file block in place, w/ locks, if any + // WAS: removeFile(new_id, new_type); NOW uses removeFileBlock() to avoid mutex lock recursion + fileblock_map::iterator new_it = mFileBlocks.find(new_spec); + if (new_it != mFileBlocks.end()) + { + LLVFSFileBlock *new_block = (*new_it).second; + removeFileBlock(new_block); + } + + // if there's something in the target location, remove it but inherit its locks + it = mFileBlocks.find(new_spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *dest_block = (*it).second; + + for (S32 i = 0; i < (S32)VFSLOCK_COUNT; i++) + { + src_block->mLocks[(EVFSLock)i] = dest_block->mLocks[(EVFSLock)i]; + } + + mFileBlocks.erase(new_spec); + delete dest_block; + } + + src_block->mFileID = new_id; + src_block->mFileType = new_type; + src_block->mAccessTime = (U32)time(NULL); + + mFileBlocks.erase(old_spec); + mFileBlocks.insert(fileblock_map::value_type(new_spec, src_block)); + + sync(src_block); + } + else + { + llwarns << "VFS: Attempt to rename nonexistent vfile " << file_id << ":" << file_type << llendl; + } + unlockData(); +} + +// mDataMutex must be LOCKED before calling this +void LLVFS::removeFileBlock(LLVFSFileBlock *fileblock) +{ + // convert this into an unsaved, dummy fileblock to preserve locks + // a more rubust solution would store the locks in a seperate data structure + sync(fileblock, TRUE); + + if (fileblock->mLength > 0) + { + // turn this file into an empty block + LLVFSBlock *free_block = new LLVFSBlock(fileblock->mLocation, fileblock->mLength); + + addFreeBlock(free_block); + } + + fileblock->mLocation = 0; + fileblock->mSize = 0; + fileblock->mLength = BLOCK_LENGTH_INVALID; + fileblock->mIndexLocation = -1; + + //mergeFreeBlocks(); +} + +void LLVFS::removeFile(const LLUUID &file_id, const LLAssetType::EType file_type) +{ + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + if (mReadOnly) + { + llerrs << "Attempt to write to read-only VFS" << llendl; + } + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + removeFileBlock(block); + } + else + { + llwarns << "VFS: attempting to remove nonexistent file " << file_id << " type " << file_type << llendl; + } + + unlockData(); +} + + +S32 LLVFS::getData(const LLUUID &file_id, const LLAssetType::EType file_type, U8 *buffer, S32 location, S32 length) +{ + S32 bytesread = 0; + + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + llassert(location >= 0); + llassert(length >= 0); + + BOOL do_read = FALSE; + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + + block->mAccessTime = (U32)time(NULL); + + if (location > block->mSize) + { + llwarns << "VFS: Attempt to read location " << location << " in file " << file_id << " of length " << block->mSize << llendl; + } + else + { + if (length > block->mSize - location) + { + length = block->mSize - location; + } + location += block->mLocation; + do_read = TRUE; + } + } + + unlockData(); + + if (do_read) + { + fseek(mDataFP, location, SEEK_SET); + bytesread = (S32)fread(buffer, 1, length, mDataFP); + } + + return bytesread; +} + +S32 LLVFS::storeData(const LLUUID &file_id, const LLAssetType::EType file_type, const U8 *buffer, S32 location, S32 length) +{ + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + if (mReadOnly) + { + llerrs << "Attempt to write to read-only VFS" << llendl; + } + + llassert(length > 0); + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + + S32 in_loc = location; + if (location == -1) + { + location = block->mSize; + } + llassert(location >= 0); + + block->mAccessTime = (U32)time(NULL); + + if (block->mLength == BLOCK_LENGTH_INVALID) + { + // Block was removed, ignore write + llwarns << "VFS: Attempt to write to invalid block" + << " in file " << file_id + << " location: " << in_loc + << " bytes: " << length + << llendl; + unlockData(); + return length; + } + else if (location > block->mLength) + { + llwarns << "VFS: Attempt to write to location " << location + << " in file " << file_id + << " type " << S32(file_type) + << " of size " << block->mSize + << " block length " << block->mLength + << llendl; + unlockData(); + return length; + } + else + { + if (length > block->mLength - location ) + { + llwarns << "VFS: Truncating write to virtual file " << file_id << " type " << S32(file_type) << llendl; + length = block->mLength - location; + } + U32 file_location = location + block->mLocation; + + unlockData(); + + fseek(mDataFP, file_location, SEEK_SET); + S32 write_len = (S32)fwrite(buffer, 1, length, mDataFP); + if (write_len != length) + { + llwarns << llformat("VFS Write Error: %d != %d",write_len,length) << llendl; + } + // fflush(mDataFP); + + lockData(); + if (location + length > block->mSize) + { + block->mSize = location + write_len; + sync(block); + } + unlockData(); + + return write_len; + } + } + else + { + unlockData(); + return 0; + } +} + +void LLVFS::incLock(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock) +{ + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + LLVFSFileBlock *block; + + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + block = (*it).second; + } + else + { + // Create a dummy block which isn't saved + block = new LLVFSFileBlock(file_id, file_type, 0, BLOCK_LENGTH_INVALID); + block->mAccessTime = (U32)time(NULL); + mFileBlocks.insert(fileblock_map::value_type(spec, block)); + } + + block->mLocks[lock]++; + mLockCounts[lock]++; + + unlockData(); +} + +void LLVFS::decLock(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock) +{ + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + + if (block->mLocks[lock] > 0) + { + block->mLocks[lock]--; + } + else + { + llwarns << "VFS: Decrementing zero-value lock " << lock << llendl; + } + mLockCounts[lock]--; + } + + unlockData(); +} + +BOOL LLVFS::isLocked(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock) +{ + lockData(); + + BOOL res = FALSE; + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + res = (block->mLocks[lock] > 0); + } + + unlockData(); + + return res; +} + +//============================================================================ +// protected +//============================================================================ + +void LLVFS::eraseBlockLength(LLVFSBlock *block) +{ + // find the corresponding map entry in the length map and erase it + S32 length = block->mLength; + blocks_length_map_t::iterator iter = mFreeBlocksByLength.lower_bound(length); + blocks_length_map_t::iterator end = mFreeBlocksByLength.end(); + while(iter != end) + { + LLVFSBlock *tblock = iter->second; + llassert(tblock->mLength == length); // there had -better- be an entry with our length! + if (tblock == block) + { + mFreeBlocksByLength.erase(iter); + break; + } + ++iter; + } + if (iter == end) + { + llerrs << "eraseBlock could not find block" << llendl; + } +} + + +// Remove block from both free lists (by location and by length). +void LLVFS::eraseBlock(LLVFSBlock *block) +{ + eraseBlockLength(block); + // find the corresponding map entry in the location map and erase it + U32 location = block->mLocation; + llverify(mFreeBlocksByLocation.erase(location) == 1); // we should only have one entry per location. +} + + +// Add the region specified by block location and length to the free lists. +// Also incrementally defragment by merging with previous and next free blocks. +void LLVFS::addFreeBlock(LLVFSBlock *block) +{ +#if LL_DEBUG + size_t dbgcount = mFreeBlocksByLocation.count(block->mLocation); + if(dbgcount > 0) + { + llerrs << "addFreeBlock called with block already in list" << llendl; + } +#endif + + // Get a pointer to the next free block (by location). + blocks_location_map_t::iterator next_free_it = mFreeBlocksByLocation.lower_bound(block->mLocation); + + // We can merge with previous if it ends at our requested location. + LLVFSBlock* prev_block = NULL; + bool merge_prev = false; + if (next_free_it != mFreeBlocksByLocation.begin()) + { + blocks_location_map_t::iterator prev_free_it = next_free_it; + --prev_free_it; + prev_block = prev_free_it->second; + merge_prev = (prev_block->mLocation + prev_block->mLength == block->mLocation); + } + + // We can merge with next if our block ends at the next block's location. + LLVFSBlock* next_block = NULL; + bool merge_next = false; + if (next_free_it != mFreeBlocksByLocation.end()) + { + next_block = next_free_it->second; + merge_next = (block->mLocation + block->mLength == next_block->mLocation); + } + + if (merge_prev && merge_next) + { + // llinfos << "VFS merge BOTH" << llendl; + // Previous block is changing length (a lot), so only need to update length map. + // Next block is going away completely. JC + eraseBlockLength(prev_block); + eraseBlock(next_block); + prev_block->mLength += block->mLength + next_block->mLength; + mFreeBlocksByLength.insert(blocks_length_map_t::value_type(prev_block->mLength, prev_block)); + delete block; + block = NULL; + delete next_block; + next_block = NULL; + } + else if (merge_prev) + { + // llinfos << "VFS merge previous" << llendl; + // Previous block is maintaining location, only changing length, + // therefore only need to update the length map. JC + eraseBlockLength(prev_block); + prev_block->mLength += block->mLength; + mFreeBlocksByLength.insert(blocks_length_map_t::value_type(prev_block->mLength, prev_block)); // multimap insert + delete block; + block = NULL; + } + else if (merge_next) + { + // llinfos << "VFS merge next" << llendl; + // Next block is changing both location and length, + // so both free lists must update. JC + eraseBlock(next_block); + next_block->mLocation = block->mLocation; + next_block->mLength += block->mLength; + // Don't hint here, next_free_it iterator may be invalid. + mFreeBlocksByLocation.insert(blocks_location_map_t::value_type(next_block->mLocation, next_block)); // multimap insert + mFreeBlocksByLength.insert(blocks_length_map_t::value_type(next_block->mLength, next_block)); // multimap insert + delete block; + block = NULL; + } + else + { + // Can't merge with other free blocks. + // Hint that insert should go near next_free_it. + mFreeBlocksByLocation.insert(next_free_it, blocks_location_map_t::value_type(block->mLocation, block)); // multimap insert + mFreeBlocksByLength.insert(blocks_length_map_t::value_type(block->mLength, block)); // multimap insert + } +} + +// Superceeded by new addFreeBlock which does incremental free space merging. +// Incremental is faster overall. +//void LLVFS::mergeFreeBlocks() +//{ +// if (!isValid()) +// { +// llerrs << "Attempting to use invalid VFS!" << llendl; +// } +// // TODO: could we optimize this with hints from the calling code? +// blocks_location_map_t::iterator iter = mFreeBlocksByLocation.begin(); +// blocks_location_map_t::iterator end = mFreeBlocksByLocation.end(); +// LLVFSBlock *first_block = iter->second; +// while(iter != end) +// { +// blocks_location_map_t::iterator first_iter = iter; // save for if we do a merge +// if (++iter == end) +// break; +// LLVFSBlock *second_block = iter->second; +// if (first_block->mLocation + first_block->mLength == second_block->mLocation) +// { +// // remove the first block from the length map +// eraseBlockLength(first_block); +// // merge first_block with second_block, since they're adjacent +// first_block->mLength += second_block->mLength; +// // add the first block to the length map (with the new size) +// mFreeBlocksByLength.insert(blocks_length_map_t::value_type(first_block->mLength, first_block)); // multimap insert +// +// // erase and delete the second block +// eraseBlock(second_block); +// delete second_block; +// +// // reset iterator +// iter = first_iter; // haven't changed first_block, so corresponding iterator is still valid +// end = mFreeBlocksByLocation.end(); +// } +// first_block = second_block; +// } +//} + + +void LLVFS::useFreeSpace(LLVFSBlock *free_block, S32 length) +{ + if (free_block->mLength == length) + { + eraseBlock(free_block); + delete free_block; + } + else + { + eraseBlock(free_block); + + free_block->mLocation += length; + free_block->mLength -= length; + + addFreeBlock(free_block); + } +} + +// NOTE! mDataMutex must be LOCKED before calling this +// sync this index entry out to the index file +// we need to do this constantly to avoid corruption on viewer crash +void LLVFS::sync(LLVFSFileBlock *block, BOOL remove) +{ + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + if (mReadOnly) + { + llwarns << "Attempt to sync read-only VFS" << llendl; + return; + } + if (block->mLength == BLOCK_LENGTH_INVALID) + { + // This is a dummy file, don't save + return; + } + if (block->mLength == 0) + { + llerrs << "VFS syncing zero-length block" << llendl; + } + + BOOL set_index_to_end = FALSE; + S32 seek_pos = block->mIndexLocation; + + if (-1 == seek_pos) + { + if (!mIndexHoles.empty()) + { + seek_pos = mIndexHoles.front(); + mIndexHoles.pop_front(); + } + else + { + set_index_to_end = TRUE; + } + } + + if (set_index_to_end) + { + // Need fseek/ftell to update the seek_pos and hence data + // structures, so can't unlockData() before this. + fseek(mIndexFP, 0, SEEK_END); + seek_pos = ftell(mIndexFP); + } + + block->mIndexLocation = seek_pos; + if (remove) + { + mIndexHoles.push_back(seek_pos); + } + + U8 buffer[LLVFSFileBlock::SERIAL_SIZE]; + if (remove) + { + memset(buffer, 0, LLVFSFileBlock::SERIAL_SIZE); + } + else + { + block->serialize(buffer); + } + + unlockData(); + + // If set_index_to_end, file pointer is already at seek_pos + // and we don't need to do anything. Only seek if not at end. + if (!set_index_to_end) + { + fseek(mIndexFP, seek_pos, SEEK_SET); + } + + fwrite(buffer, LLVFSFileBlock::SERIAL_SIZE, 1, mIndexFP); + // fflush(mIndexFP); + + lockData(); + + return; +} + +// mDataMutex must be LOCKED before calling this +// Can initiate LRU-based file removal to make space. +// The immune file block will not be removed. +LLVFSBlock *LLVFS::findFreeBlock(S32 size, LLVFSFileBlock *immune) +{ + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + + LLVFSBlock *block = NULL; + BOOL have_lru_list = FALSE; + + typedef std::set lru_set; + lru_set lru_list; + + LLTimer timer; + + while (! block) + { + // look for a suitable free block + blocks_length_map_t::iterator iter = mFreeBlocksByLength.lower_bound(size); // first entry >= size + if (iter != mFreeBlocksByLength.end()) + block = iter->second; + + // no large enough free blocks, time to clean out some junk + if (! block) + { + // create a list of files sorted by usage time + // this is far faster than sorting a linked list + if (! have_lru_list) + { + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileBlock *tmp = (*it).second; + + if (tmp != immune && + tmp->mLength > 0 && + ! tmp->mLocks[VFSLOCK_READ] && + ! tmp->mLocks[VFSLOCK_APPEND] && + ! tmp->mLocks[VFSLOCK_OPEN]) + { + lru_list.insert(tmp); + } + } + + have_lru_list = TRUE; + } + + if (lru_list.size() == 0) + { + // No more files to delete, and still not enough room! + llwarns << "VFS: Can't make " << size << " bytes of free space in VFS, giving up" << llendl; + break; + } + + // is the oldest file big enough? (Should be about half the time) + lru_set::iterator it = lru_list.begin(); + LLVFSFileBlock *file_block = *it; + if (file_block->mLength >= size && file_block != immune) + { + // ditch this file and look again for a free block - should find it + // TODO: it'll be faster just to assign the free block and break + llinfos << "LRU: Removing " << file_block->mFileID << ":" << file_block->mFileType << llendl; + lru_list.erase(it); + removeFileBlock(file_block); + file_block = NULL; + continue; + } + + + llinfos << "VFS: LRU: Aggressive: " << (S32)lru_list.size() << " files remain" << llendl; + dumpLockCounts(); + + // Now it's time to aggressively make more space + // Delete the oldest 5MB of the vfs or enough to hold the file, which ever is larger + // This may yield too much free space, but we'll use it up soon enough + U32 cleanup_target = (size > VFS_CLEANUP_SIZE) ? size : VFS_CLEANUP_SIZE; + U32 cleaned_up = 0; + for (it = lru_list.begin(); + it != lru_list.end() && cleaned_up < cleanup_target; + ) + { + file_block = *it; + + // TODO: it would be great to be able to batch all these sync() calls + // llinfos << "LRU2: Removing " << file_block->mFileID << ":" << file_block->mFileType << " last accessed" << file_block->mAccessTime << llendl; + + cleaned_up += file_block->mLength; + lru_list.erase(it++); + removeFileBlock(file_block); + file_block = NULL; + } + //mergeFreeBlocks(); + } + } + + F32 time = timer.getElapsedTimeF32(); + if (time > 0.5f) + { + llwarns << "VFS: Spent " << time << " seconds in findFreeBlock!" << llendl; + } + + return block; +} + +//============================================================================ +// public +//============================================================================ + +void LLVFS::pokeFiles() +{ + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + U32 word; + + // only write data if we actually read 4 bytes + // otherwise we're writing garbage and screwing up the file + fseek(mDataFP, 0, SEEK_SET); + if (fread(&word, 1, 4, mDataFP) == 4) + { + fseek(mDataFP, 0, SEEK_SET); + fwrite(&word, 1, 4, mDataFP); + fflush(mDataFP); + } + + fseek(mIndexFP, 0, SEEK_SET); + if (fread(&word, 1, 4, mIndexFP) == 4) + { + fseek(mIndexFP, 0, SEEK_SET); + fwrite(&word, 1, 4, mIndexFP); + fflush(mIndexFP); + } +} + + +void LLVFS::dumpMap() +{ + llinfos << "Files:" << llendl; + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileBlock *file_block = (*it).second; + llinfos << "Location: " << file_block->mLocation << "\tLength: " << file_block->mLength << "\t" << file_block->mFileID << "\t" << file_block->mFileType << llendl; + } + + llinfos << "Free Blocks:" << llendl; + for (blocks_location_map_t::iterator iter = mFreeBlocksByLocation.begin(), + end = mFreeBlocksByLocation.end(); + iter != end; iter++) + { + LLVFSBlock *free_block = iter->second; + llinfos << "Location: " << free_block->mLocation << "\tLength: " << free_block->mLength << llendl; + } +} + +// verify that the index file contents match the in-memory file structure +// Very slow, do not call routinely. JC +void LLVFS::audit() +{ + // Lock the mutex through this whole function. + LLMutexLock lock_data(mDataMutex); + + fflush(mIndexFP); + + fseek(mIndexFP, 0, SEEK_END); + S32 index_size = ftell(mIndexFP); + fseek(mIndexFP, 0, SEEK_SET); + + U8 *buffer = new U8[index_size]; + fread(buffer, index_size, 1, mIndexFP); + + U8 *tmp_ptr = buffer; + + std::map found_files; + U32 cur_time = (U32)time(NULL); + + BOOL vfs_corrupt = FALSE; + + std::vector audit_blocks; + while (tmp_ptr < buffer + index_size) + { + LLVFSFileBlock *block = new LLVFSFileBlock(); + audit_blocks.push_back(block); + + block->deserialize(tmp_ptr, (S32)(tmp_ptr - buffer)); + tmp_ptr += block->SERIAL_SIZE; + + // do sanity check on this block + if (block->mLength >= 0 && + block->mLocation >= 0 && + block->mSize >= 0 && + block->mSize <= block->mLength && + block->mFileType >= LLAssetType::AT_NONE && + block->mFileType < LLAssetType::AT_COUNT && + block->mAccessTime <= cur_time && + block->mFileID != LLUUID::null) + { + if (mFileBlocks.find(*block) == mFileBlocks.end()) + { + llwarns << "VFile " << block->mFileID << ":" << block->mFileType << " on disk, not in memory, loc " << block->mIndexLocation << llendl; + } + else if (found_files.find(*block) != found_files.end()) + { + std::map::iterator it; + it = found_files.find(*block); + LLVFSFileBlock* dupe = it->second; + // try to keep data from being lost + unlockAndClose(mIndexFP); + mIndexFP = NULL; + unlockAndClose(mDataFP); + mDataFP = NULL; + llwarns << "VFS: Original block index " << block->mIndexLocation + << " location " << block->mLocation + << " length " << block->mLength + << " size " << block->mSize + << " id " << block->mFileID + << " type " << block->mFileType + << llendl; + llwarns << "VFS: Duplicate block index " << dupe->mIndexLocation + << " location " << dupe->mLocation + << " length " << dupe->mLength + << " size " << dupe->mSize + << " id " << dupe->mFileID + << " type " << dupe->mFileType + << llendl; + llwarns << "VFS: Index size " << index_size << llendl; + llwarns << "VFS: INDEX CORRUPT" << llendl; + vfs_corrupt = TRUE; + break; + } + else + { + found_files[*block] = block; + } + } + else + { + if (block->mLength) + { + llwarns << "VFile " << block->mFileID << ":" << block->mFileType << " corrupt on disk" << llendl; + } + // else this is just a hole + } + } + + delete[] buffer; + + if (vfs_corrupt) + { + for (std::vector::iterator iter = audit_blocks.begin(); + iter != audit_blocks.end(); ++iter) + { + delete *iter; + } + audit_blocks.clear(); + return; + } + + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileBlock* block = (*it).second; + + if (block->mSize > 0) + { + if (! found_files.count(*block)) + { + llwarns << "VFile " << block->mFileID << ":" << block->mFileType << " in memory, not on disk, loc " << block->mIndexLocation<< llendl; + fseek(mIndexFP, block->mIndexLocation, SEEK_SET); + U8 buf[LLVFSFileBlock::SERIAL_SIZE]; + fread(buf, LLVFSFileBlock::SERIAL_SIZE, 1, mIndexFP); + + LLVFSFileBlock disk_block; + disk_block.deserialize(buf, block->mIndexLocation); + + llwarns << "Instead found " << disk_block.mFileID << ":" << block->mFileType << llendl; + } + else + { + block = found_files.find(*block)->second; + found_files.erase(*block); + delete block; + } + } + } + + for (std::map::iterator iter = found_files.begin(); + iter != found_files.end(); iter++) + { + LLVFSFileBlock* block = iter->second; + llwarns << "VFile " << block->mFileID << ":" << block->mFileType << " szie:" << block->mSize << " leftover" << llendl; + } + + llinfos << "VFS: audit OK" << llendl; + // mutex released by LLMutexLock() destructor. +} + + +// quick check for uninitialized blocks +// Slow, do not call in release. +void LLVFS::checkMem() +{ + lockData(); + + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileBlock *block = (*it).second; + llassert(block->mFileType >= LLAssetType::AT_NONE && + block->mFileType < LLAssetType::AT_COUNT && + block->mFileID != LLUUID::null); + + for (std::deque::iterator iter = mIndexHoles.begin(); + iter != mIndexHoles.end(); ++iter) + { + S32 index_loc = *iter; + if (index_loc == block->mIndexLocation) + { + llwarns << "VFile block " << block->mFileID << ":" << block->mFileType << " is marked as a hole" << llendl; + } + } + } + + llinfos << "VFS: mem check OK" << llendl; + + unlockData(); +} + +void LLVFS::dumpLockCounts() +{ + S32 i; + for (i = 0; i < VFSLOCK_COUNT; i++) + { + llinfos << "LockType: " << i << ": " << mLockCounts[i] << llendl; + } +} + +void LLVFS::dumpStatistics() +{ + lockData(); + + // Investigate file blocks. + std::map size_counts; + std::map location_counts; + std::map > filetype_counts; + + S32 max_file_size = 0; + S32 total_file_size = 0; + S32 invalid_file_count = 0; + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileBlock *file_block = (*it).second; + if (file_block->mLength == BLOCK_LENGTH_INVALID) + { + invalid_file_count++; + } + else if (file_block->mLength <= 0) + { + llinfos << "Bad file block at: " << file_block->mLocation << "\tLength: " << file_block->mLength << "\t" << file_block->mFileID << "\t" << file_block->mFileType << llendl; + size_counts[file_block->mLength]++; + location_counts[file_block->mLocation]++; + } + else + { + total_file_size += file_block->mLength; + } + + if (file_block->mLength > max_file_size) + { + max_file_size = file_block->mLength; + } + + filetype_counts[file_block->mFileType].first++; + filetype_counts[file_block->mFileType].second += file_block->mLength; + } + + for (std::map::iterator it = size_counts.begin(); it != size_counts.end(); ++it) + { + S32 size = it->first; + S32 size_count = it->second; + llinfos << "Bad files size " << size << " count " << size_count << llendl; + } + for (std::map::iterator it = location_counts.begin(); it != location_counts.end(); ++it) + { + U32 location = it->first; + S32 location_count = it->second; + llinfos << "Bad files location " << location << " count " << location_count << llendl; + } + + // Investigate free list. + S32 max_free_size = 0; + S32 total_free_size = 0; + std::map free_length_counts; + for (blocks_location_map_t::iterator iter = mFreeBlocksByLocation.begin(), + end = mFreeBlocksByLocation.end(); + iter != end; iter++) + { + LLVFSBlock *free_block = iter->second; + if (free_block->mLength <= 0) + { + llinfos << "Bad free block at: " << free_block->mLocation << "\tLength: " << free_block->mLength << llendl; + } + else + { + llinfos << "Block: " << free_block->mLocation + << "\tLength: " << free_block->mLength + << "\tEnd: " << free_block->mLocation + free_block->mLength + << llendl; + total_free_size += free_block->mLength; + } + + if (free_block->mLength > max_free_size) + { + max_free_size = free_block->mLength; + } + + free_length_counts[free_block->mLength]++; + } + + // Dump histogram of free block sizes + for (std::map::iterator it = free_length_counts.begin(); it != free_length_counts.end(); ++it) + { + llinfos << "Free length " << it->first << " count " << it->second << llendl; + } + + llinfos << "Invalid blocks: " << invalid_file_count << llendl; + llinfos << "File blocks: " << mFileBlocks.size() << llendl; + + S32 length_list_count = (S32)mFreeBlocksByLength.size(); + S32 location_list_count = (S32)mFreeBlocksByLocation.size(); + if (length_list_count == location_list_count) + { + llinfos << "Free list lengths match, free blocks: " << location_list_count << llendl; + } + else + { + llwarns << "Free list lengths do not match!" << llendl; + llwarns << "By length: " << length_list_count << llendl; + llwarns << "By location: " << location_list_count << llendl; + } + llinfos << "Max file: " << max_file_size/1024 << "K" << llendl; + llinfos << "Max free: " << max_free_size/1024 << "K" << llendl; + llinfos << "Total file size: " << total_file_size/1024 << "K" << llendl; + llinfos << "Total free size: " << total_free_size/1024 << "K" << llendl; + llinfos << "Sum: " << (total_file_size + total_free_size) << " bytes" << llendl; + llinfos << llformat("%.0f%% full",((F32)(total_file_size)/(F32)(total_file_size+total_free_size))*100.f) << llendl; + + llinfos << " " << llendl; + for (std::map >::iterator iter = filetype_counts.begin(); + iter != filetype_counts.end(); ++iter) + { + llinfos << "Type: " << LLAssetType::getDesc(iter->first) + << " Count: " << iter->second.first + << " Bytes: " << (iter->second.second>>20) << " MB" << llendl; + } + + // Look for potential merges + { + blocks_location_map_t::iterator iter = mFreeBlocksByLocation.begin(); + blocks_location_map_t::iterator end = mFreeBlocksByLocation.end(); + LLVFSBlock *first_block = iter->second; + while(iter != end) + { + if (++iter == end) + break; + LLVFSBlock *second_block = iter->second; + if (first_block->mLocation + first_block->mLength == second_block->mLocation) + { + llinfos << "Potential merge at " << first_block->mLocation << llendl; + } + first_block = second_block; + } + } + unlockData(); +} + +// Debug Only! +#include "llapr.h" +void LLVFS::dumpFiles() +{ + lockData(); + + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileSpecifier file_spec = it->first; + LLVFSFileBlock *file_block = it->second; + S32 length = file_block->mLength; + S32 size = file_block->mSize; + if (length != BLOCK_LENGTH_INVALID && size > 0) + { + LLUUID id = file_spec.mFileID; + LLAssetType::EType type = file_spec.mFileType; + U8* buffer = new U8[size]; + + unlockData(); + getData(id, type, buffer, 0, size); + lockData(); + + LLString extention = ".data"; + switch(type) + { + case LLAssetType::AT_TEXTURE: + extention = ".jp2"; // ".j2c"; // IrfanView recognizes .jp2 -sjb + break; + default: + break; + } + LLString filename = id.getString() + extention; + llinfos << " Writing " << filename << llendl; + apr_file_t* file = ll_apr_file_open(filename, LL_APR_WB); + ll_apr_file_write(file, buffer, size); + apr_file_close(file); + delete[] buffer; + } + } + + unlockData(); +} + +//============================================================================ +// protected +//============================================================================ + +// static +FILE *LLVFS::openAndLock(const char *filename, const char *mode, BOOL read_lock) +{ +#if LL_WINDOWS + + return LLFile::_fsopen(filename, mode, (read_lock ? _SH_DENYWR : _SH_DENYRW)); + +#else + + FILE *fp; + int fd; + + // first test the lock in a non-destructive way + if (strstr(mode, "w")) + { + fp = LLFile::fopen(filename, "rb"); + if (fp) + { + fd = fileno(fp); + if (flock(fd, (read_lock ? LOCK_SH : LOCK_EX) | LOCK_NB) == -1) + { + fclose(fp); + return NULL; + } + + fclose(fp); + } + } + + // now actually open the file for use + fp = LLFile::fopen(filename, mode); + if (fp) + { + fd = fileno(fp); + if (flock(fd, (read_lock ? LOCK_SH : LOCK_EX) | LOCK_NB) == -1) + { + fclose(fp); + fp = NULL; + } + } + + return fp; + +#endif +} + +// static +void LLVFS::unlockAndClose(FILE *fp) +{ + if (fp) + { + // IW: we don't actually want to unlock on linux + // this is because a forked process can kill the parent's lock + // with an explicit unlock + // however, fclose() will implicitly remove the lock + // but only once both parent and child have closed the file + /* + #if !LL_WINDOWS + int fd = fileno(fp); + flock(fd, LOCK_UN); + #endif + */ + + fclose(fp); + } +} diff --git a/indra/llvfs/llvfs.h b/indra/llvfs/llvfs.h new file mode 100644 index 0000000000..c3fb0cdf22 --- /dev/null +++ b/indra/llvfs/llvfs.h @@ -0,0 +1,151 @@ +/** + * @file llvfs.h + * @brief Definition of virtual file system + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLVFS_H +#define LL_LLVFS_H + +#include +#include +#include +#include "lluuid.h" +#include "linked_lists.h" +#include "llassettype.h" +#include "llthread.h" + +enum EVFSValid +{ + VFSVALID_UNKNOWN = 0, + VFSVALID_OK = 1, + VFSVALID_BAD_CORRUPT = 2, + VFSVALID_BAD_CANNOT_OPEN_READONLY = 3, + VFSVALID_BAD_CANNOT_CREATE = 4 +}; + +// Lock types for open vfiles, pending async reads, and pending async appends +// (There are no async normal writes, currently) +enum EVFSLock +{ + VFSLOCK_OPEN = 0, + VFSLOCK_READ = 1, + VFSLOCK_APPEND = 2, + + VFSLOCK_COUNT = 3 +}; + +// internal classes +class LLVFSBlock; +class LLVFSFileBlock; +class LLVFSFileSpecifier +{ +public: + LLVFSFileSpecifier(); + LLVFSFileSpecifier(const LLUUID &file_id, const LLAssetType::EType file_type); + bool operator<(const LLVFSFileSpecifier &rhs) const; + bool operator==(const LLVFSFileSpecifier &rhs) const; + +public: + LLUUID mFileID; + LLAssetType::EType mFileType; +}; + +class LLVFS +{ +public: + // Pass 0 to not presize + LLVFS(const char *index_filename, const char *data_filename, const BOOL read_only, const U32 presize, const BOOL remove_after_crash); + ~LLVFS(); + + BOOL isValid() const { return (VFSVALID_OK == mValid); } + EVFSValid getValidState() const { return mValid; } + + // ---------- The following fucntions lock/unlock mDataMutex ---------- + BOOL getExists(const LLUUID &file_id, const LLAssetType::EType file_type); + S32 getSize(const LLUUID &file_id, const LLAssetType::EType file_type); + + BOOL checkAvailable(S32 max_size); + + S32 getMaxSize(const LLUUID &file_id, const LLAssetType::EType file_type); + BOOL setMaxSize(const LLUUID &file_id, const LLAssetType::EType file_type, S32 max_size); + + void renameFile(const LLUUID &file_id, const LLAssetType::EType file_type, + const LLUUID &new_id, const LLAssetType::EType &new_type); + void removeFile(const LLUUID &file_id, const LLAssetType::EType file_type); + + S32 getData(const LLUUID &file_id, const LLAssetType::EType file_type, U8 *buffer, S32 location, S32 length); + S32 storeData(const LLUUID &file_id, const LLAssetType::EType file_type, const U8 *buffer, S32 location, S32 length); + + void incLock(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock); + void decLock(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock); + BOOL isLocked(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock); + // ---------------------------------------------------------------- + + // Used to trigger evil WinXP behavior of "preloading" entire file into memory. + void pokeFiles(); + + // Verify that the index file contents match the in-memory file structure + // Very slow, do not call routinely. JC + void audit(); + // Check for uninitialized blocks. Slow, do not call in release. JC + void checkMem(); + // for debugging, prints a map of the vfs + void dumpMap(); + void dumpLockCounts(); + void dumpStatistics(); + void dumpFiles(); + +protected: + void removeFileBlock(LLVFSFileBlock *fileblock); + + void eraseBlockLength(LLVFSBlock *block); + void eraseBlock(LLVFSBlock *block); + void addFreeBlock(LLVFSBlock *block); + //void mergeFreeBlocks(); + void useFreeSpace(LLVFSBlock *free_block, S32 length); + void sync(LLVFSFileBlock *block, BOOL remove = FALSE); + void presizeDataFile(const U32 size); + + static FILE *openAndLock(const char *filename, const char *mode, BOOL read_lock); + static void unlockAndClose(FILE *fp); + + // Can initiate LRU-based file removal to make space. + // The immune file block will not be removed. + LLVFSBlock *findFreeBlock(S32 size, LLVFSFileBlock *immune = NULL); + + // lock/unlock data mutex (mDataMutex) + void lockData() { mDataMutex->lock(); } + void unlockData() { mDataMutex->unlock(); } + +protected: + LLMutex* mDataMutex; + + typedef std::map fileblock_map; + fileblock_map mFileBlocks; + + typedef std::multimap blocks_length_map_t; + blocks_length_map_t mFreeBlocksByLength; + typedef std::multimap blocks_location_map_t; + blocks_location_map_t mFreeBlocksByLocation; + + FILE *mDataFP; + FILE *mIndexFP; + + std::deque mIndexHoles; + + char *mIndexFilename; + char *mDataFilename; + BOOL mReadOnly; + + EVFSValid mValid; + + S32 mLockCounts[VFSLOCK_COUNT]; + BOOL mRemoveAfterCrash; +}; + +extern LLVFS *gVFS; + +#endif diff --git a/indra/llvfs/llvfsthread.cpp b/indra/llvfs/llvfsthread.cpp new file mode 100644 index 0000000000..8ea98ab462 --- /dev/null +++ b/indra/llvfs/llvfsthread.cpp @@ -0,0 +1,294 @@ +/** + * @file llvfsthread.cpp + * @brief LLVFSThread implementation + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llmath.h" +#include "llvfsthread.h" +#include "llstl.h" + +//============================================================================ + +/*static*/ std::string LLVFSThread::sDataPath = ""; + +/*static*/ LLVFSThread* LLVFSThread::sLocal = NULL; + +//============================================================================ +// Run on MAIN thread +//static +void LLVFSThread::initClass(bool local_is_threaded, bool local_run_always) +{ + llassert(sLocal == NULL); + sLocal = new LLVFSThread(local_is_threaded, local_run_always); +} + +//static +S32 LLVFSThread::updateClass(U32 ms_elapsed) +{ + sLocal->update(ms_elapsed); + return sLocal->getPending(); +} + +//static +void LLVFSThread::cleanupClass() +{ + sLocal->setQuitting(); + while (sLocal->getPending()) + { + sLocal->update(0); + } + delete sLocal; + sLocal = 0; +} + +//---------------------------------------------------------------------------- + +LLVFSThread::LLVFSThread(bool threaded, bool runalways) : + LLQueuedThread("VFS", threaded, runalways) +{ +} + +LLVFSThread::~LLVFSThread() +{ + // ~LLQueuedThread() will be called here +} + +//---------------------------------------------------------------------------- + +LLVFSThread::handle_t LLVFSThread::read(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes, U32 priority, U32 flags) +{ + handle_t handle = generateHandle(); + + priority = llmax(priority, (U32)PRIORITY_LOW); // All reads are at least PRIORITY_LOW + Request* req = new Request(handle, priority, flags, FILE_READ, vfs, file_id, file_type, + buffer, offset, numbytes); + + bool res = addRequest(req); + if (!res) + { + llerrs << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +S32 LLVFSThread::readImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, PRIORITY_IMMEDIATE, 0, FILE_READ, vfs, file_id, file_type, + buffer, offset, numbytes); + + S32 res = addRequest(req) ? 1 : 0; + if (res == 0) + { + llerrs << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + } + else + { + llverify(waitForResult(handle, false) == true); + res = req->getBytesRead(); + completeRequest(handle); + } + return res; +} + +LLVFSThread::handle_t LLVFSThread::write(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes, U32 flags) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, 0, flags, FILE_WRITE, vfs, file_id, file_type, + buffer, offset, numbytes); + + bool res = addRequest(req); + if (!res) + { + llerrs << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +S32 LLVFSThread::writeImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, PRIORITY_IMMEDIATE, 0, FILE_WRITE, vfs, file_id, file_type, + buffer, offset, numbytes); + + S32 res = addRequest(req) ? 1 : 0; + if (res == 0) + { + llerrs << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + } + else + { + llverify(waitForResult(handle, false) == true); + res = req->getBytesRead(); + completeRequest(handle); + } + return res; +} + + +LLVFSThread::handle_t LLVFSThread::rename(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + const LLUUID &new_id, const LLAssetType::EType new_type, U32 flags) +{ + handle_t handle = generateHandle(); + + LLUUID* new_idp = new LLUUID(new_id); // deleted with Request + // new_type is passed as "numbytes" + Request* req = new Request(handle, 0, flags, FILE_RENAME, vfs, file_id, file_type, + (U8*)new_idp, 0, (S32)new_type); + + bool res = addRequest(req); + if (!res) + { + llerrs << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +//============================================================================ +// Runs on its OWN thread + +bool LLVFSThread::processRequest(QueuedRequest* qreq) +{ + Request *req = (Request*)qreq; + + bool complete = req->processIO(); + + return complete; +} + +//============================================================================ + +LLVFSThread::Request::Request(handle_t handle, U32 priority, U32 flags, + operation_t op, LLVFS* vfs, + const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes) : + QueuedRequest(handle, priority, flags), + mOperation(op), + mVFS(vfs), + mFileID(file_id), + mFileType(file_type), + mBuffer(buffer), + mOffset(offset), + mBytes(numbytes), + mBytesRead(0) +{ + llassert(mBuffer); + + if (numbytes <= 0 && mOperation != FILE_RENAME) + { + llwarns << "LLVFSThread: Request with numbytes = " << numbytes + << " operation = " << op + << " offset " << offset + << " file_type " << file_type << llendl; + } + if (mOperation == FILE_WRITE) + { + S32 blocksize = mVFS->getMaxSize(mFileID, mFileType); + if (blocksize < 0) + { + llwarns << "VFS write to temporary block (shouldn't happen)" << llendl; + } + mVFS->incLock(mFileID, mFileType, VFSLOCK_APPEND); + } + else if (mOperation == FILE_RENAME) + { + mVFS->incLock(mFileID, mFileType, VFSLOCK_APPEND); + } + else // if (mOperation == FILE_READ) + { + mVFS->incLock(mFileID, mFileType, VFSLOCK_READ); + } +} + +// dec locks as soon as a request finishes +void LLVFSThread::Request::finishRequest() +{ + if (mOperation == FILE_WRITE) + { + mVFS->decLock(mFileID, mFileType, VFSLOCK_APPEND); + } + else if (mOperation == FILE_RENAME) + { + mVFS->decLock(mFileID, mFileType, VFSLOCK_APPEND); + } + else // if (mOperation == FILE_READ) + { + mVFS->decLock(mFileID, mFileType, VFSLOCK_READ); + } +} + +void LLVFSThread::Request::deleteRequest() +{ + if (getStatus() == STATUS_QUEUED || getStatus() == STATUS_ABORT) + { + llerrs << "Attempt to delete a queued LLVFSThread::Request!" << llendl; + } + if (mOperation == FILE_WRITE) + { + if (mFlags & AUTO_DELETE) + { + delete [] mBuffer; + } + } + else if (mOperation == FILE_RENAME) + { + LLUUID* new_idp = (LLUUID*)mBuffer; + delete new_idp; + } + LLQueuedThread::QueuedRequest::deleteRequest(); +} + +bool LLVFSThread::Request::processIO() +{ + bool complete = false; + if (mOperation == FILE_READ) + { + llassert(mOffset >= 0); + mBytesRead = mVFS->getData(mFileID, mFileType, mBuffer, mOffset, mBytes); + complete = true; + //llinfos << llformat("LLVFSThread::READ '%s': %d bytes arg:%d",getFilename(),mBytesRead) << llendl; + } + else if (mOperation == FILE_WRITE) + { + mBytesRead = mVFS->storeData(mFileID, mFileType, mBuffer, mOffset, mBytes); + complete = true; + //llinfos << llformat("LLVFSThread::WRITE '%s': %d bytes arg:%d",getFilename(),mBytesRead) << llendl; + } + else if (mOperation == FILE_RENAME) + { + LLUUID* new_idp = (LLUUID*)mBuffer; + LLAssetType::EType new_type = (LLAssetType::EType)mBytes; + mVFS->renameFile(mFileID, mFileType, *new_idp, new_type); + complete = true; + //llinfos << llformat("LLVFSThread::WRITE '%s': %d bytes arg:%d",getFilename(),mBytesRead) << llendl; + } + else + { + llerrs << llformat("LLVFSThread::unknown operation: %d", mOperation) << llendl; + } + return complete; +} + +//============================================================================ diff --git a/indra/llvfs/llvfsthread.h b/indra/llvfs/llvfsthread.h new file mode 100644 index 0000000000..14a2fe0ba7 --- /dev/null +++ b/indra/llvfs/llvfsthread.h @@ -0,0 +1,125 @@ +/** + * @file llvfsthread.h + * @brief LLVFSThread definition + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLVFSTHREAD_H +#define LL_LLVFSTHREAD_H + +#include +#include +#include +#include + +#include "llapr.h" + +#include "llqueuedthread.h" + +#include "llvfs.h" + +//============================================================================ + +class LLVFSThread : public LLQueuedThread +{ + //------------------------------------------------------------------------ +public: + enum operation_t { + FILE_READ, + FILE_WRITE, + FILE_RENAME + }; + + //------------------------------------------------------------------------ +public: + + class Request : public QueuedRequest + { + protected: + ~Request() {}; // use deleteRequest() + + public: + Request(handle_t handle, U32 priority, U32 flags, + operation_t op, LLVFS* vfs, + const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes); + + S32 getBytesRead() + { + return mBytesRead; + } + S32 getOperation() + { + return mOperation; + } + U8* getBuffer() + { + return mBuffer; + } + LLVFS* getVFS() + { + return mVFS; + } + std::string getFilename() + { + char tbuf[40]; + mFileID.toString(tbuf); + return std::string(tbuf); + } + + /*virtual*/ void finishRequest(); + /*virtual*/ void deleteRequest(); + + bool processIO(); + + private: + operation_t mOperation; + + LLVFS* mVFS; + LLUUID mFileID; + LLAssetType::EType mFileType; + + U8* mBuffer; // dest for reads, source for writes, new UUID for rename + S32 mOffset; // offset into file, -1 = append (WRITE only) + S32 mBytes; // bytes to read from file, -1 = all (new mFileType for rename) + S32 mBytesRead; // bytes read from file + }; + + //------------------------------------------------------------------------ +public: + static std::string sDataPath; + static void setDataPath(const std::string& path) { sDataPath = path; } + +public: + LLVFSThread(bool threaded = TRUE, bool runalways = TRUE); + ~LLVFSThread(); + + // Return a Request handle + handle_t read(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes, U32 pri=PRIORITY_NORMAL, U32 flags = 0); + handle_t write(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes, U32 flags); + handle_t rename(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + const LLUUID &new_id, const LLAssetType::EType new_type, U32 flags); + // Return number of bytes read + S32 readImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes); + S32 writeImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes); + + /*virtual*/ bool processRequest(QueuedRequest* req); + + static void initClass(bool local_is_threaded = TRUE, bool run_always = TRUE); // Setup sLocal + static S32 updateClass(U32 ms_elapsed); + static void cleanupClass(); // Delete sLocal + +public: + static LLVFSThread* sLocal; // Default worker thread +}; + +//============================================================================ + + +#endif // LL_LLVFSTHREAD_H diff --git a/indra/llwindow/lldxhardware.cpp b/indra/llwindow/lldxhardware.cpp new file mode 100644 index 0000000000..a972a29aa4 --- /dev/null +++ b/indra/llwindow/lldxhardware.cpp @@ -0,0 +1,508 @@ +/** + * @file lldxhardware.cpp + * @brief LLDXHardware implementation + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifdef LL_WINDOWS + +// Culled from some Microsoft sample code + +#include "linden_common.h" + +#include +#include + +#include + +#include "lldxhardware.h" +#include "llerror.h" + +#include "llstring.h" +#include "llstl.h" + +void (*gWriteDebug)(const char* msg) = NULL; +LLDXHardware gDXHardware; + +//----------------------------------------------------------------------------- +// Defines, and constants +//----------------------------------------------------------------------------- +#define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } } +#define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p); (p)=NULL; } } +#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } + +std::string get_string(IDxDiagContainer *containerp, WCHAR *wszPropName) +{ + HRESULT hr; + VARIANT var; + WCHAR wszPropValue[256]; + + VariantInit( &var ); + hr = containerp->GetProp(wszPropName, &var ); + if( SUCCEEDED(hr) ) + { + // Switch off the type. There's 4 different types: + switch( var.vt ) + { + case VT_UI4: + swprintf( wszPropValue, L"%d", var.ulVal ); + break; + case VT_I4: + swprintf( wszPropValue, L"%d", var.lVal ); + break; + case VT_BOOL: + wcscpy( wszPropValue, (var.boolVal) ? L"true" : L"false" ); + break; + case VT_BSTR: + wcsncpy( wszPropValue, var.bstrVal, 255 ); + wszPropValue[255] = 0; + break; + } + } + // Clear the variant (this is needed to free BSTR memory) + VariantClear( &var ); + + return utf16str_to_utf8str(wszPropValue); +} + + +LLVersion::LLVersion() +{ + mValid = FALSE; + S32 i; + for (i = 0; i < 4; i++) + { + mFields[i] = 0; + } +} + +BOOL LLVersion::set(const std::string &version_string) +{ + S32 i; + for (i = 0; i < 4; i++) + { + mFields[i] = 0; + } + // Split the version string. + std::string str(version_string); + typedef boost::tokenizer > tokenizer; + boost::char_separator sep(".", "", boost::keep_empty_tokens); + tokenizer tokens(str, sep); + + tokenizer::iterator iter = tokens.begin(); + S32 count = 0; + for (;(iter != tokens.end()) && (count < 4);++iter) + { + mFields[count] = atoi(iter->c_str()); + count++; + } + if (count < 4) + { + //llwarns << "Potentially bogus version string!" << version_string << llendl; + for (i = 0; i < 4; i++) + { + mFields[i] = 0; + } + mValid = FALSE; + } + else + { + mValid = TRUE; + } + return mValid; +} + +S32 LLVersion::getField(const S32 field_num) +{ + if (!mValid) + { + return -1; + } + else + { + return mFields[field_num]; + } +} + +LLString LLDXDriverFile::dump() +{ + if (gWriteDebug) + { + gWriteDebug("Filename:"); + gWriteDebug(mName.c_str()); + gWriteDebug("\n"); + gWriteDebug("Ver:"); + gWriteDebug(mVersionString.c_str()); + gWriteDebug("\n"); + gWriteDebug("Date:"); + gWriteDebug(mDateString.c_str()); + gWriteDebug("\n"); + } + llinfos << mFilepath << llendl; + llinfos << mName << llendl; + llinfos << mVersionString << llendl; + llinfos << mDateString << llendl; + + return ""; +} + +LLDXDevice::~LLDXDevice() +{ + for_each(mDriverFiles.begin(), mDriverFiles.end(), DeletePairedPointer()); +} + +std::string LLDXDevice::dump() +{ + if (gWriteDebug) + { + gWriteDebug("StartDevice\n"); + gWriteDebug("DeviceName:"); + gWriteDebug(mName.c_str()); + gWriteDebug("\n"); + gWriteDebug("PCIString:"); + gWriteDebug(mPCIString.c_str()); + gWriteDebug("\n"); + } + llinfos << llendl; + llinfos << "DeviceName:" << mName << llendl; + llinfos << "PCIString:" << mPCIString << llendl; + llinfos << "Drivers" << llendl; + llinfos << "-------" << llendl; + for (driver_file_map_t::iterator iter = mDriverFiles.begin(), + end = mDriverFiles.end(); + iter != end; iter++) + { + LLDXDriverFile *filep = iter->second; + filep->dump(); + } + if (gWriteDebug) + { + gWriteDebug("EndDevice\n"); + } + + return ""; +} + +LLDXDriverFile *LLDXDevice::findDriver(const std::string &driver) +{ + for (driver_file_map_t::iterator iter = mDriverFiles.begin(), + end = mDriverFiles.end(); + iter != end; iter++) + { + LLDXDriverFile *filep = iter->second; + if (!utf8str_compare_insensitive(filep->mName,driver)) + { + return filep; + } + } + + return NULL; +} + +LLDXHardware::LLDXHardware() +{ + mVRAM = 0; + gWriteDebug = NULL; +} + +void LLDXHardware::cleanup() +{ + for_each(mDevices.begin(), mDevices.end(), DeletePairedPointer()); +} + +LLString LLDXHardware::dumpDevices() +{ + if (gWriteDebug) + { + gWriteDebug("\n"); + gWriteDebug("StartAllDevices\n"); + } + for (device_map_t::iterator iter = mDevices.begin(), + end = mDevices.end(); + iter != end; iter++) + { + LLDXDevice *devicep = iter->second; + devicep->dump(); + } + if (gWriteDebug) + { + gWriteDebug("EndAllDevices\n\n"); + } + return ""; +} + +LLDXDevice *LLDXHardware::findDevice(const std::string &vendor, const std::string &devices) +{ + // Iterate through different devices tokenized in devices string + std::string str(devices); + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("|", "", boost::keep_empty_tokens); + tokenizer tokens(str, sep); + + tokenizer::iterator iter = tokens.begin(); + for (;iter != tokens.end();++iter) + { + std::string dev_str = *iter; + for (device_map_t::iterator iter = mDevices.begin(), + end = mDevices.end(); + iter != end; iter++) + { + LLDXDevice *devicep = iter->second; + if ((devicep->mVendorID == vendor) + && (devicep->mDeviceID == dev_str)) + { + return devicep; + } + } + } + + return NULL; +} + +BOOL LLDXHardware::getInfo(BOOL vram_only) +{ + LLTimer hw_timer; + BOOL ok = FALSE; + HRESULT hr; + + CoInitialize(NULL); + + IDxDiagProvider *dx_diag_providerp = NULL; + IDxDiagContainer *dx_diag_rootp = NULL; + IDxDiagContainer *devices_containerp = NULL; + IDxDiagContainer *system_device_containerp= NULL; + IDxDiagContainer *device_containerp = NULL; + IDxDiagContainer *file_containerp = NULL; + IDxDiagContainer *driver_containerp = NULL; + + // CoCreate a IDxDiagProvider* + llinfos << "CoCreateInstance IID_IDxDiagProvider" << llendl; + hr = CoCreateInstance(CLSID_DxDiagProvider, + NULL, + CLSCTX_INPROC_SERVER, + IID_IDxDiagProvider, + (LPVOID*) &dx_diag_providerp); + + if (FAILED(hr)) + { + llwarns << "No DXDiag provider found! DirectX 9 not installed!" << llendl; + gWriteDebug("No DXDiag provider found! DirectX 9 not installed!\n"); + goto LCleanup; + } + if (SUCCEEDED(hr)) // if FAILED(hr) then dx9 is not installed + { + // Fill out a DXDIAG_INIT_PARAMS struct and pass it to IDxDiagContainer::Initialize + // Passing in TRUE for bAllowWHQLChecks, allows dxdiag to check if drivers are + // digital signed as logo'd by WHQL which may connect via internet to update + // WHQL certificates. + DXDIAG_INIT_PARAMS dx_diag_init_params; + ZeroMemory(&dx_diag_init_params, sizeof(DXDIAG_INIT_PARAMS)); + + dx_diag_init_params.dwSize = sizeof(DXDIAG_INIT_PARAMS); + dx_diag_init_params.dwDxDiagHeaderVersion = DXDIAG_DX9_SDK_VERSION; + dx_diag_init_params.bAllowWHQLChecks = TRUE; + dx_diag_init_params.pReserved = NULL; + + llinfos << "dx_diag_providerp->Initialize" << llendl; + hr = dx_diag_providerp->Initialize(&dx_diag_init_params); + if(FAILED(hr)) + { + goto LCleanup; + } + + llinfos << "dx_diag_providerp->GetRootContainer" << llendl; + hr = dx_diag_providerp->GetRootContainer( &dx_diag_rootp ); + if(FAILED(hr) || !dx_diag_rootp) + { + goto LCleanup; + } + + HRESULT hr; + + // Get display driver information + llinfos << "dx_diag_rootp->GetChildContainer" << llendl; + hr = dx_diag_rootp->GetChildContainer(L"DxDiag_DisplayDevices", &devices_containerp); + if(FAILED(hr) || !devices_containerp) + { + goto LCleanup; + } + + // Get device 0 + llinfos << "devices_containerp->GetChildContainer" << llendl; + hr = devices_containerp->GetChildContainer(L"0", &device_containerp); + if(FAILED(hr) || !device_containerp) + { + goto LCleanup; + } + + // Get the English VRAM string + std::string ram_str = get_string(device_containerp, L"szDisplayMemoryEnglish"); + + // We don't need the device any more + SAFE_RELEASE(device_containerp); + + // Dump the string as an int into the structure + char *stopstring; + mVRAM = strtol(ram_str.c_str(), &stopstring, 10); + llinfos << "VRAM Detected: " << mVRAM << " DX9 string: " << ram_str << llendl; + + if (vram_only) + { + ok = TRUE; + goto LCleanup; + } + + // Now let's get device and driver information + // Get the IDxDiagContainer object called "DxDiag_SystemDevices". + // This call may take some time while dxdiag gathers the info. + DWORD num_devices = 0; + WCHAR wszContainer[256]; + llinfos << "dx_diag_rootp->GetChildContainer DxDiag_SystemDevices" << llendl; + hr = dx_diag_rootp->GetChildContainer(L"DxDiag_SystemDevices", &system_device_containerp); + if (FAILED(hr)) + { + goto LCleanup; + } + + hr = system_device_containerp->GetNumberOfChildContainers(&num_devices); + if (FAILED(hr)) + { + goto LCleanup; + } + + llinfos << "DX9 iterating over devices" << llendl; + S32 device_num = 0; + for (device_num = 0; device_num < (S32)num_devices; device_num++) + { + hr = system_device_containerp->EnumChildContainerNames(device_num, wszContainer, 256); + if (FAILED(hr)) + { + goto LCleanup; + } + + hr = system_device_containerp->GetChildContainer(wszContainer, &device_containerp); + if (FAILED(hr) || device_containerp == NULL) + { + goto LCleanup; + } + + std::string device_name = get_string(device_containerp, L"szDescription"); + std::string device_id = get_string(device_containerp, L"szDeviceID"); + + LLDXDevice *dxdevicep = new LLDXDevice; + dxdevicep->mName = device_name; + dxdevicep->mPCIString = device_id; + mDevices[dxdevicep->mPCIString] = dxdevicep; + + // Split the PCI string based on vendor, device, subsys, rev. + std::string str(device_id); + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("&\\", "", boost::keep_empty_tokens); + tokenizer tokens(str, sep); + + tokenizer::iterator iter = tokens.begin(); + S32 count = 0; + BOOL valid = TRUE; + for (;(iter != tokens.end()) && (count < 3);++iter) + { + switch (count) + { + case 0: + if (strcmp(iter->c_str(), "PCI")) + { + valid = FALSE; + } + break; + case 1: + dxdevicep->mVendorID = iter->c_str(); + break; + case 2: + dxdevicep->mDeviceID = iter->c_str(); + break; + default: + // Ignore it + break; + } + count++; + } + + + // Now, iterate through the related drivers + hr = device_containerp->GetChildContainer(L"Drivers", &driver_containerp); + if (FAILED(hr) || !driver_containerp) + { + goto LCleanup; + } + + DWORD num_files = 0; + hr = driver_containerp->GetNumberOfChildContainers(&num_files); + if (FAILED(hr)) + { + goto LCleanup; + } + + S32 file_num = 0; + for (file_num = 0; file_num < (S32)num_files; file_num++ ) + { + hr = driver_containerp->EnumChildContainerNames(file_num, wszContainer, 256); + if (FAILED(hr)) + { + goto LCleanup; + } + + hr = driver_containerp->GetChildContainer(wszContainer, &file_containerp); + if (FAILED(hr) || file_containerp == NULL) + { + goto LCleanup; + } + + std::string driver_path = get_string(file_containerp, L"szPath"); + std::string driver_name = get_string(file_containerp, L"szName"); + std::string driver_version = get_string(file_containerp, L"szVersion"); + std::string driver_date = get_string(file_containerp, L"szDatestampEnglish"); + + LLDXDriverFile *dxdriverfilep = new LLDXDriverFile; + dxdriverfilep->mName = driver_name; + dxdriverfilep->mFilepath= driver_path; + dxdriverfilep->mVersionString = driver_version; + dxdriverfilep->mVersion.set(driver_version); + dxdriverfilep->mDateString = driver_date; + + dxdevicep->mDriverFiles[driver_name] = dxdriverfilep; + + SAFE_RELEASE(file_containerp); + } + SAFE_RELEASE(device_containerp); + } + } + + dumpDevices(); + ok = TRUE; + +LCleanup: + if (!ok) + { + llwarns << "DX9 probe failed" << llendl; + gWriteDebug("DX9 probe failed\n"); + } + + SAFE_RELEASE(file_containerp); + SAFE_RELEASE(driver_containerp); + SAFE_RELEASE(device_containerp); + SAFE_RELEASE(devices_containerp); + SAFE_RELEASE(dx_diag_rootp); + SAFE_RELEASE(dx_diag_providerp); + + CoUninitialize(); + + return ok; +} + +void LLDXHardware::setWriteDebugFunc(void (*func)(const char*)) +{ + gWriteDebug = func; +} + +#endif diff --git a/indra/llwindow/lldxhardware.h b/indra/llwindow/lldxhardware.h new file mode 100644 index 0000000000..00ae525372 --- /dev/null +++ b/indra/llwindow/lldxhardware.h @@ -0,0 +1,90 @@ +/** + * @file lldxhardware.h + * @brief LLDXHardware definition + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLDXHARDWARE_H +#define LL_LLDXHARDWARE_H + +#include + +#include "stdtypes.h" +#include "llstring.h" + +class LLVersion +{ +public: + LLVersion(); + BOOL set(const std::string &version_string); + S32 getField(const S32 field_num); +protected: + std::string mVersionString; + S32 mFields[4]; + BOOL mValid; +}; + +class LLDXDriverFile +{ +public: + LLString dump(); + +public: + std::string mFilepath; + std::string mName; + std::string mVersionString; + LLVersion mVersion; + std::string mDateString; +}; + +class LLDXDevice +{ +public: + ~LLDXDevice(); + std::string dump(); + + LLDXDriverFile *findDriver(const std::string &driver); +public: + std::string mName; + std::string mPCIString; + std::string mVendorID; + std::string mDeviceID; + + typedef std::map driver_file_map_t; + driver_file_map_t mDriverFiles; +}; + + +class LLDXHardware +{ +public: + LLDXHardware(); + void setWriteDebugFunc(void (*func)(const char*)); + void cleanup(); + + // Returns TRUE on success. + // vram_only TRUE does a "light" probe. + BOOL getInfo(BOOL vram_only); + + S32 getVRAM() const { return mVRAM; } + + // Find a particular device that matches the following specs. + // Empty strings indicate that you don't care. + // You can separate multiple devices with '|' chars to indicate you want + // ANY of them to match and return. + LLDXDevice *findDevice(const std::string &vendor, const std::string &devices); + + LLString dumpDevices(); +public: + typedef std::map device_map_t; + device_map_t mDevices; +protected: + S32 mVRAM; +}; + +extern void (*gWriteDebug)(const char* msg); +extern LLDXHardware gDXHardware; + +#endif // LL_LLDXHARDWARE_H diff --git a/indra/llwindow/llkeyboard.cpp b/indra/llwindow/llkeyboard.cpp new file mode 100644 index 0000000000..ee42f53571 --- /dev/null +++ b/indra/llwindow/llkeyboard.cpp @@ -0,0 +1,387 @@ +/** + * @file llkeyboard.cpp + * @brief Handler for assignable key bindings + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "indra_constants.h" +#include "llkeyboard.h" + +#include "llwindow.h" + + +// +// Globals +// + +LLKeyboard *gKeyboard = NULL; + +//static +std::map LLKeyboard::sKeysToNames; +std::map LLKeyboard::sNamesToKeys; + +// +// Class Implementation +// + +LLKeyboard::LLKeyboard() : mCallbacks(NULL), mNumpadDistinct(ND_NUMLOCK_OFF) +{ + S32 i; + + // Constructor for LLTimer inits each timer. We want them to + // be constructed without being initialized, so we shut them down here. + for (i = 0; i < KEY_COUNT; i++) + { + mKeyLevelFrameCount[i] = 0; + mKeyLevel[i] = FALSE; + mKeyUp[i] = FALSE; + mKeyDown[i] = FALSE; + mKeyRepeated[i] = FALSE; + } + + mInsertMode = LL_KIM_INSERT; + mCurTranslatedKey = KEY_NONE; + + addKeyName(' ', "Space" ); + addKeyName(KEY_RETURN, "Enter" ); + addKeyName(KEY_LEFT, "Left" ); + addKeyName(KEY_RIGHT, "Right" ); + addKeyName(KEY_UP, "Up" ); + addKeyName(KEY_DOWN, "Down" ); + addKeyName(KEY_ESCAPE, "Esc" ); + addKeyName(KEY_HOME, "Home" ); + addKeyName(KEY_END, "End" ); + addKeyName(KEY_PAGE_UP, "PgUp" ); + addKeyName(KEY_PAGE_DOWN, "PgDn" ); + addKeyName(KEY_F1, "F1" ); + addKeyName(KEY_F2, "F2" ); + addKeyName(KEY_F3, "F3" ); + addKeyName(KEY_F4, "F4" ); + addKeyName(KEY_F5, "F5" ); + addKeyName(KEY_F6, "F6" ); + addKeyName(KEY_F7, "F7" ); + addKeyName(KEY_F8, "F8" ); + addKeyName(KEY_F9, "F9" ); + addKeyName(KEY_F10, "F10" ); + addKeyName(KEY_F11, "F11" ); + addKeyName(KEY_F12, "F12" ); + addKeyName(KEY_TAB, "Tab" ); + addKeyName(KEY_ADD, "Add" ); + addKeyName(KEY_SUBTRACT, "Subtract" ); + addKeyName(KEY_MULTIPLY, "Multiply" ); + addKeyName(KEY_DIVIDE, "Divide" ); + addKeyName(KEY_PAD_LEFT, "PAD_LEFT" ); + addKeyName(KEY_PAD_RIGHT, "PAD_RIGHT" ); + addKeyName(KEY_PAD_DOWN, "PAD_DOWN" ); + addKeyName(KEY_PAD_UP, "PAD_UP" ); + addKeyName(KEY_PAD_HOME, "PAD_HOME" ); + addKeyName(KEY_PAD_END, "PAD_END" ); + addKeyName(KEY_PAD_PGUP, "PAD_PGUP" ); + addKeyName(KEY_PAD_PGDN, "PAD_PGDN" ); + addKeyName(KEY_PAD_CENTER, "PAD_CENTER" ); + addKeyName(KEY_PAD_INS, "PAD_INS" ); + addKeyName(KEY_PAD_DEL, "PAD_DEL" ); + addKeyName(KEY_PAD_RETURN, "PAD_Enter" ); + addKeyName(KEY_BUTTON0, "PAD_BUTTON0" ); + addKeyName(KEY_BUTTON1, "PAD_BUTTON1" ); + addKeyName(KEY_BUTTON2, "PAD_BUTTON2" ); + addKeyName(KEY_BUTTON3, "PAD_BUTTON3" ); + addKeyName(KEY_BUTTON4, "PAD_BUTTON4" ); + addKeyName(KEY_BUTTON5, "PAD_BUTTON5" ); + addKeyName(KEY_BUTTON6, "PAD_BUTTON6" ); + addKeyName(KEY_BUTTON7, "PAD_BUTTON7" ); + addKeyName(KEY_BUTTON8, "PAD_BUTTON8" ); + addKeyName(KEY_BUTTON9, "PAD_BUTTON9" ); + addKeyName(KEY_BUTTON10, "PAD_BUTTON10" ); + addKeyName(KEY_BUTTON11, "PAD_BUTTON11" ); + addKeyName(KEY_BUTTON12, "PAD_BUTTON12" ); + addKeyName(KEY_BUTTON13, "PAD_BUTTON13" ); + addKeyName(KEY_BUTTON14, "PAD_BUTTON14" ); + addKeyName(KEY_BUTTON15, "PAD_BUTTON15" ); + + addKeyName(KEY_BACKSPACE, "Backsp" ); + addKeyName(KEY_DELETE, "Del" ); + addKeyName(KEY_SHIFT, "Shift" ); + addKeyName(KEY_CONTROL, "Ctrl" ); + addKeyName(KEY_ALT, "Alt" ); + addKeyName(KEY_HYPHEN, "-" ); + addKeyName(KEY_EQUALS, "=" ); + addKeyName(KEY_INSERT, "Ins" ); + addKeyName(KEY_CAPSLOCK, "CapsLock" ); +} + + +LLKeyboard::~LLKeyboard() +{ + // nothing +} + +void LLKeyboard::addKeyName(KEY key, const LLString& name) +{ + sKeysToNames[key] = name; + LLString nameuc = name; + LLString::toUpper(nameuc); + sNamesToKeys[nameuc] = key; +} + +// BUG this has to be called when an OS dialog is shown, otherwise modifier key state +// is wrong because the keyup event is never received by the main window. JC +void LLKeyboard::resetKeys() +{ + S32 i; + + for (i = 0; i < KEY_COUNT; i++) + { + if( mKeyLevel[i] ) + { + mKeyLevel[i] = FALSE; + mKeyLevelFrameCount[i] = 0; + } + } + + for (i = 0; i < KEY_COUNT; i++) + { + mKeyUp[i] = FALSE; + } + + for (i = 0; i < KEY_COUNT; i++) + { + mKeyDown[i] = FALSE; + } + + for (i = 0; i < KEY_COUNT; i++) + { + mKeyRepeated[i] = FALSE; + } +} + + +BOOL LLKeyboard::translateKey(const U16 os_key, KEY *out_key) +{ + std::map::iterator iter; + + // Only translate keys in the map, ignore all other keys for now + iter = mTranslateKeyMap.find(os_key); + if (iter == mTranslateKeyMap.end()) + { + //llwarns << "Unknown virtual key " << os_key << llendl; + *out_key = 0; + return FALSE; + } + else + { + *out_key = iter->second; + return TRUE; + } +} + + +U16 LLKeyboard::inverseTranslateKey(const KEY translated_key) +{ + std::map::iterator iter; + iter = mInvTranslateKeyMap.find(translated_key); + if (iter == mInvTranslateKeyMap.end()) + { + return 0; + } + else + { + return iter->second; + } +} + + +BOOL LLKeyboard::handleTranslatedKeyDown(KEY translated_key, U32 translated_mask) +{ + BOOL handled = FALSE; + BOOL repeated = FALSE; + + // is this the first time the key went down? + // if so, generate "character" message + if( !mKeyLevel[translated_key] ) + { + mKeyLevel[translated_key] = TRUE; + mKeyLevelTimer[translated_key].reset(); + } + else + { + // Level is already down, assume it's repeated. + repeated = TRUE; + mKeyRepeated[translated_key] = TRUE; + } + + mKeyDown[translated_key] = TRUE; + mCurTranslatedKey = (KEY)translated_key; + handled = mCallbacks->handleTranslatedKeyDown(translated_key, translated_mask, repeated); + return handled; +} + + +BOOL LLKeyboard::handleTranslatedKeyUp(KEY translated_key, U32 translated_mask) +{ + BOOL handled = FALSE; + if( mKeyLevel[translated_key] ) + { + mKeyLevel[translated_key] = FALSE; + mKeyLevelFrameCount[translated_key] = 0; + + // Only generate key up events if the key is thought to + // be down. This allows you to call resetKeys() in the + // middle of a frame and ignore subsequent KEY_UP + // messages in the same frame. This was causing the + // sequence W in chat to move agents forward. JC + mKeyUp[translated_key] = TRUE; + mKeyRepeated[translated_key] = FALSE; + handled = mCallbacks->handleTranslatedKeyUp(translated_key, translated_mask); + } + + lldebugst(LLERR_USER_INPUT) << "keyup -" << translated_key << "-" << llendl; + + return handled; +} + + +void LLKeyboard::toggleInsertMode() +{ + if (LL_KIM_INSERT == mInsertMode) + { + mInsertMode = LL_KIM_OVERWRITE; + } + else + { + mInsertMode = LL_KIM_INSERT; + } +} + + +// Returns time in seconds since key was pressed. +F32 LLKeyboard::getKeyElapsedTime(KEY key) +{ + if( mKeyLevel[key] ) + { + return mKeyLevelTimer[key].getElapsedTimeF32(); + } + else + { + return 0.f; + } +} + +// Returns time in frames since key was pressed. +S32 LLKeyboard::getKeyElapsedFrameCount(KEY key) +{ + if( mKeyLevel[key] ) + { + return mKeyLevelFrameCount[key]; + } + else + { + return 0; + } +} + +// static +BOOL LLKeyboard::keyFromString(const LLString& str, KEY *key) +{ + LLString instring(str); + size_t length = instring.size(); + + if (length < 1) + { + return FALSE; + } + if (length == 1) + { + char ch = toupper(instring[0]); + if (('0' <= ch && ch <= '9') || + ('A' <= ch && ch <= 'Z') || + ('!' <= ch && ch <= '/') || // !"#$%&'()*+,-./ + (':' <= ch && ch <= '@') || // :;<=>?@ + ('[' <= ch && ch <= '`') || // [\]^_` + ('{' <= ch && ch <= '~')) // {|}~ + { + *key = ch; + return TRUE; + } + } + + LLString::toUpper(instring); + KEY res = get_if_there(sNamesToKeys, instring, (KEY)0); + if (res != 0) + { + *key = res; + return TRUE; + } + llwarns << "keyFromString failed: " << str << llendl; + return FALSE; +} + + +// static +LLString LLKeyboard::stringFromKey(KEY key) +{ + LLString res = get_if_there(sKeysToNames, key, LLString::null); + if (res.empty()) + { + char buffer[2]; + buffer[0] = key; + buffer[1] = '\0'; + res = LLString(buffer); + } + return res; +} + + + +//static +BOOL LLKeyboard::maskFromString(const LLString& str, MASK *mask) +{ + LLString instring(str); + if (instring == "NONE") + { + *mask = MASK_NONE; + return TRUE; + } + else if (instring == "SHIFT") + { + *mask = MASK_SHIFT; + return TRUE; + } + else if (instring == "CTL") + { + *mask = MASK_CONTROL; + return TRUE; + } + else if (instring == "ALT") + { + *mask = MASK_ALT; + return TRUE; + } + else if (instring == "CTL_SHIFT") + { + *mask = MASK_CONTROL | MASK_SHIFT; + return TRUE; + } + else if (instring == "ALT_SHIFT") + { + *mask = MASK_ALT | MASK_SHIFT; + return TRUE; + } + else if (instring == "CTL_ALT") + { + *mask = MASK_CONTROL | MASK_ALT; + return TRUE; + } + else if (instring == "CTL_ALT_SHIFT") + { + *mask = MASK_CONTROL | MASK_ALT | MASK_SHIFT; + return TRUE; + } + else + { + return FALSE; + } +} diff --git a/indra/llwindow/llkeyboard.h b/indra/llwindow/llkeyboard.h new file mode 100644 index 0000000000..0c47d117d0 --- /dev/null +++ b/indra/llwindow/llkeyboard.h @@ -0,0 +1,124 @@ +/** + * @file llkeyboard.h + * @brief Handler for assignable key bindings + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLKEYBOARD_H +#define LL_LLKEYBOARD_H + +#include + +#include "string_table.h" +#include "lltimer.h" +#include "indra_constants.h" + +enum EKeystate +{ + KEYSTATE_DOWN, + KEYSTATE_LEVEL, + KEYSTATE_UP +}; + +typedef void (*LLKeyFunc)(EKeystate keystate); + +enum EKeyboardInsertMode +{ + LL_KIM_INSERT, + LL_KIM_OVERWRITE +}; + +class LLKeyBinding +{ +public: + KEY mKey; + MASK mMask; +// const char *mName; // unused + LLKeyFunc mFunction; +}; + +class LLWindowCallbacks; + +class LLKeyboard +{ +public: + typedef enum e_numpad_distinct + { + ND_NEVER, + ND_NUMLOCK_OFF, + ND_NUMLOCK_ON + } ENumpadDistinct; + +public: + LLKeyboard(); + virtual ~LLKeyboard(); + + void resetKeys(); + + + F32 getCurKeyElapsedTime() { return getKeyElapsedTime( mCurScanKey ); } + F32 getCurKeyElapsedFrameCount() { return (F32)getKeyElapsedFrameCount( mCurScanKey ); } + BOOL getKeyDown(const KEY key) { return mKeyLevel[key]; } + BOOL getKeyRepeated(const KEY key) { return mKeyRepeated[key]; } + + BOOL translateKey(const U16 os_key, KEY *translated_key); + U16 inverseTranslateKey(const KEY translated_key); + BOOL handleTranslatedKeyUp(KEY translated_key, U32 translated_mask); // Translated into "Linden" keycodes + BOOL handleTranslatedKeyDown(KEY translated_key, U32 translated_mask); // Translated into "Linden" keycodes + + + virtual BOOL handleKeyUp(const U16 key, MASK mask) = 0; + virtual BOOL handleKeyDown(const U16 key, MASK mask) = 0; + + // Asynchronously poll the control, alt, and shift keys and set the + // appropriate internal key masks. + virtual void resetMaskKeys() = 0; + virtual void scanKeyboard() = 0; // scans keyboard, calls functions as necessary + // Mac must differentiate between Command = Control for keyboard events + // and Command != Control for mouse events. + virtual MASK currentMask(BOOL for_mouse_event) = 0; + virtual KEY currentKey() { return mCurTranslatedKey; } + + EKeyboardInsertMode getInsertMode() { return mInsertMode; } + void toggleInsertMode(); + + static BOOL maskFromString(const LLString& str, MASK *mask); // False on failure + static BOOL keyFromString(const LLString& str, KEY *key); // False on failure + static LLString stringFromKey(KEY key); + + e_numpad_distinct getNumpadDistinct() { return mNumpadDistinct; } + void setNumpadDistinct(e_numpad_distinct val) { mNumpadDistinct = val; } + + void setCallbacks(LLWindowCallbacks *cbs) { mCallbacks = cbs; } +protected: + F32 getKeyElapsedTime( KEY key ); // Returns time in seconds since key was pressed. + S32 getKeyElapsedFrameCount( KEY key ); // Returns time in frames since key was pressed. + void addKeyName(KEY key, const LLString& name); + +protected: + std::map mTranslateKeyMap; // Map of translations from OS keys to Linden KEYs + std::map mInvTranslateKeyMap; // Map of translations from Linden KEYs to OS keys + LLWindowCallbacks *mCallbacks; + + LLTimer mKeyLevelTimer[KEY_COUNT]; // Time since level was set + S32 mKeyLevelFrameCount[KEY_COUNT]; // Frames since level was set + BOOL mKeyLevel[KEY_COUNT]; // Levels + BOOL mKeyRepeated[KEY_COUNT]; // Key was repeated + BOOL mKeyUp[KEY_COUNT]; // Up edge + BOOL mKeyDown[KEY_COUNT]; // Down edge + KEY mCurTranslatedKey; + KEY mCurScanKey; // Used during the scanKeyboard() + + e_numpad_distinct mNumpadDistinct; + + EKeyboardInsertMode mInsertMode; + + static std::map sKeysToNames; + static std::map sNamesToKeys; +}; + +extern LLKeyboard *gKeyboard; + +#endif diff --git a/indra/llwindow/llkeyboardmacosx.cpp b/indra/llwindow/llkeyboardmacosx.cpp new file mode 100644 index 0000000000..8ce3b1fb2d --- /dev/null +++ b/indra/llwindow/llkeyboardmacosx.cpp @@ -0,0 +1,309 @@ +/** + * @file llkeyboardmacosx.cpp + * @brief Handler for assignable key bindings + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#if LL_DARWIN + +#include "linden_common.h" +#include "llkeyboardmacosx.h" +#include "llwindow.h" + +#include + +LLKeyboardMacOSX::LLKeyboardMacOSX() +{ + // Virtual keycode mapping table. Yes, this was as annoying to generate as it looks. + mTranslateKeyMap[0x00] = 'A'; + mTranslateKeyMap[0x01] = 'S'; + mTranslateKeyMap[0x02] = 'D'; + mTranslateKeyMap[0x03] = 'F'; + mTranslateKeyMap[0x04] = 'H'; + mTranslateKeyMap[0x05] = 'G'; + mTranslateKeyMap[0x06] = 'Z'; + mTranslateKeyMap[0x07] = 'X'; + mTranslateKeyMap[0x08] = 'C'; + mTranslateKeyMap[0x09] = 'V'; + mTranslateKeyMap[0x0b] = 'B'; + mTranslateKeyMap[0x0c] = 'Q'; + mTranslateKeyMap[0x0d] = 'W'; + mTranslateKeyMap[0x0e] = 'E'; + mTranslateKeyMap[0x0f] = 'R'; + mTranslateKeyMap[0x10] = 'Y'; + mTranslateKeyMap[0x11] = 'T'; + mTranslateKeyMap[0x12] = '1'; + mTranslateKeyMap[0x13] = '2'; + mTranslateKeyMap[0x14] = '3'; + mTranslateKeyMap[0x15] = '4'; + mTranslateKeyMap[0x16] = '6'; + mTranslateKeyMap[0x17] = '5'; + mTranslateKeyMap[0x18] = '='; // KEY_EQUALS + mTranslateKeyMap[0x19] = '9'; + mTranslateKeyMap[0x1a] = '7'; + mTranslateKeyMap[0x1b] = '-'; // KEY_HYPHEN + mTranslateKeyMap[0x1c] = '8'; + mTranslateKeyMap[0x1d] = '0'; + mTranslateKeyMap[0x1e] = ']'; + mTranslateKeyMap[0x1f] = 'O'; + mTranslateKeyMap[0x20] = 'U'; + mTranslateKeyMap[0x21] = '['; + mTranslateKeyMap[0x22] = 'I'; + mTranslateKeyMap[0x23] = 'P'; + mTranslateKeyMap[0x24] = KEY_RETURN, + mTranslateKeyMap[0x25] = 'L'; + mTranslateKeyMap[0x26] = 'J'; + mTranslateKeyMap[0x27] = '\''; + mTranslateKeyMap[0x28] = 'K'; + mTranslateKeyMap[0x29] = ';'; + mTranslateKeyMap[0x2a] = '\\'; + mTranslateKeyMap[0x2b] = ','; + mTranslateKeyMap[0x2c] = '/'; + mTranslateKeyMap[0x2d] = 'N'; + mTranslateKeyMap[0x2e] = 'M'; + mTranslateKeyMap[0x2f] = '.'; + mTranslateKeyMap[0x30] = KEY_TAB; + mTranslateKeyMap[0x31] = ' '; // space! + mTranslateKeyMap[0x32] = '`'; + mTranslateKeyMap[0x33] = KEY_BACKSPACE; + mTranslateKeyMap[0x35] = KEY_ESCAPE; + //mTranslateKeyMap[0x37] = 0; // Command key. (not used yet) + mTranslateKeyMap[0x38] = KEY_SHIFT; + mTranslateKeyMap[0x39] = KEY_CAPSLOCK; + mTranslateKeyMap[0x3a] = KEY_ALT; + mTranslateKeyMap[0x3b] = KEY_CONTROL; + mTranslateKeyMap[0x41] = '.'; // keypad + mTranslateKeyMap[0x43] = '*'; // keypad + mTranslateKeyMap[0x45] = '+'; // keypad + mTranslateKeyMap[0x4b] = '/'; // keypad + mTranslateKeyMap[0x4c] = KEY_RETURN; // keypad enter + mTranslateKeyMap[0x4e] = '-'; // keypad + mTranslateKeyMap[0x51] = '='; // keypad + mTranslateKeyMap[0x52] = '0'; // keypad + mTranslateKeyMap[0x53] = '1'; // keypad + mTranslateKeyMap[0x54] = '2'; // keypad + mTranslateKeyMap[0x55] = '3'; // keypad + mTranslateKeyMap[0x56] = '4'; // keypad + mTranslateKeyMap[0x57] = '5'; // keypad + mTranslateKeyMap[0x58] = '6'; // keypad + mTranslateKeyMap[0x59] = '7'; // keypad + mTranslateKeyMap[0x5b] = '8'; // keypad + mTranslateKeyMap[0x5c] = '9'; // keypad + mTranslateKeyMap[0x60] = KEY_F5; + mTranslateKeyMap[0x61] = KEY_F6; + mTranslateKeyMap[0x62] = KEY_F7; + mTranslateKeyMap[0x63] = KEY_F3; + mTranslateKeyMap[0x64] = KEY_F8; + mTranslateKeyMap[0x65] = KEY_F9; + mTranslateKeyMap[0x67] = KEY_F11; + mTranslateKeyMap[0x6d] = KEY_F10; + mTranslateKeyMap[0x6f] = KEY_F12; + mTranslateKeyMap[0x72] = KEY_INSERT; + mTranslateKeyMap[0x73] = KEY_HOME; + mTranslateKeyMap[0x74] = KEY_PAGE_UP; + mTranslateKeyMap[0x75] = KEY_DELETE; + mTranslateKeyMap[0x76] = KEY_F4; + mTranslateKeyMap[0x77] = KEY_END; + mTranslateKeyMap[0x78] = KEY_F2; + mTranslateKeyMap[0x79] = KEY_PAGE_DOWN; + mTranslateKeyMap[0x7a] = KEY_F1; + mTranslateKeyMap[0x7b] = KEY_LEFT; + mTranslateKeyMap[0x7c] = KEY_RIGHT; + mTranslateKeyMap[0x7d] = KEY_DOWN; + mTranslateKeyMap[0x7e] = KEY_UP; + + // Build inverse map + std::map::iterator iter; + for (iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) + { + mInvTranslateKeyMap[iter->second] = iter->first; + } + + // build numpad maps + mTranslateNumpadMap[0x52] = KEY_PAD_INS; // keypad 0 + mTranslateNumpadMap[0x53] = KEY_PAD_END; // keypad 1 + mTranslateNumpadMap[0x54] = KEY_PAD_DOWN; // keypad 2 + mTranslateNumpadMap[0x55] = KEY_PAD_PGDN; // keypad 3 + mTranslateNumpadMap[0x56] = KEY_PAD_LEFT; // keypad 4 + mTranslateNumpadMap[0x57] = KEY_PAD_CENTER; // keypad 5 + mTranslateNumpadMap[0x58] = KEY_PAD_RIGHT; // keypad 6 + mTranslateNumpadMap[0x59] = KEY_PAD_HOME; // keypad 7 + mTranslateNumpadMap[0x5b] = KEY_PAD_UP; // keypad 8 + mTranslateNumpadMap[0x5c] = KEY_PAD_PGUP; // keypad 9 + mTranslateNumpadMap[0x41] = KEY_PAD_DEL; // keypad . + mTranslateNumpadMap[0x4c] = KEY_PAD_RETURN; // keypad enter + + // Build inverse numpad map + for (iter = mTranslateNumpadMap.begin(); iter != mTranslateNumpadMap.end(); iter++) + { + mInvTranslateNumpadMap[iter->second] = iter->first; + } +} + +void LLKeyboardMacOSX::resetMaskKeys() +{ + U32 mask = GetCurrentEventKeyModifiers(); + + // MBW -- XXX -- This mirrors the operation of the Windows version of resetMaskKeys(). + // It looks a bit suspicious, as it won't correct for keys that have been released. + // Is this the way it's supposed to work? + + if(mask & shiftKey) + { + mKeyLevel[KEY_SHIFT] = TRUE; + } + + if(mask & (controlKey)) + { + mKeyLevel[KEY_CONTROL] = TRUE; + } + + if(mask & optionKey) + { + mKeyLevel[KEY_ALT] = TRUE; + } +} + +/* +static BOOL translateKeyMac(const U16 key, const U32 mask, KEY &outKey, U32 &outMask) +{ + // Translate the virtual keycode into the keycodes the keyboard system expects. + U16 virtualKey = (mask >> 24) & 0x0000007F; + outKey = macKeyTransArray[virtualKey]; + + + return(outKey != 0); +} +*/ + +MASK LLKeyboardMacOSX::updateModifiers(const U32 mask) +{ + // translate the mask + MASK out_mask = 0; + + if(mask & shiftKey) + { + out_mask |= MASK_SHIFT; + } + + if(mask & (controlKey | cmdKey)) + { + out_mask |= MASK_CONTROL; + } + + if(mask & optionKey) + { + out_mask |= MASK_ALT; + } + + return out_mask; +} + +BOOL LLKeyboardMacOSX::handleKeyDown(const U16 key, const U32 mask) +{ + KEY translated_key = 0; + U32 translated_mask = 0; + BOOL handled = FALSE; + + translated_mask = updateModifiers(mask); + + if(translateNumpadKey(key, &translated_key)) + { + handled = handleTranslatedKeyDown(translated_key, translated_mask); + } + + return handled; +} + + +BOOL LLKeyboardMacOSX::handleKeyUp(const U16 key, const U32 mask) +{ + KEY translated_key = 0; + U32 translated_mask = 0; + BOOL handled = FALSE; + + translated_mask = updateModifiers(mask); + + if(translateNumpadKey(key, &translated_key)) + { + handled = handleTranslatedKeyUp(translated_key, translated_mask); + } + + return handled; +} + +MASK LLKeyboardMacOSX::currentMask(BOOL for_mouse_event) +{ + MASK result = MASK_NONE; + U32 mask = GetCurrentEventKeyModifiers(); + + if (mask & shiftKey) result |= MASK_SHIFT; + if (mask & controlKey) result |= MASK_CONTROL; + if (mask & optionKey) result |= MASK_ALT; + + // For keyboard events, consider Command equivalent to Control + if (!for_mouse_event) + { + if (mask & cmdKey) result |= MASK_CONTROL; + } + + return result; +} + +void LLKeyboardMacOSX::scanKeyboard() +{ + S32 key; + for (key = 0; key < KEY_COUNT; key++) + { + // Generate callback if any event has occurred on this key this frame. + // Can't just test mKeyLevel, because this could be a slow frame and + // key might have gone down then up. JC + if (mKeyLevel[key] || mKeyDown[key] || mKeyUp[key]) + { + mCurScanKey = key; + mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]); + } + } + + // Reset edges for next frame + for (key = 0; key < KEY_COUNT; key++) + { + mKeyUp[key] = FALSE; + mKeyDown[key] = FALSE; + if (mKeyLevel[key]) + { + mKeyLevelFrameCount[key]++; + } + } +} + +BOOL LLKeyboardMacOSX::translateNumpadKey( const U16 os_key, KEY *translated_key ) +{ + if(mNumpadDistinct == ND_NUMLOCK_ON) + { + std::map::iterator iter= mTranslateNumpadMap.find(os_key); + if(iter != mTranslateNumpadMap.end()) + { + *translated_key = iter->second; + return TRUE; + } + } + return translateKey(os_key, translated_key); +} + +U16 LLKeyboardMacOSX::inverseTranslateNumpadKey(const KEY translated_key) +{ + if(mNumpadDistinct == ND_NUMLOCK_ON) + { + std::map::iterator iter= mInvTranslateNumpadMap.find(translated_key); + if(iter != mInvTranslateNumpadMap.end()) + { + return iter->second; + } + } + return inverseTranslateKey(translated_key); +} + +#endif // LL_DARWIN diff --git a/indra/llwindow/llkeyboardmacosx.h b/indra/llwindow/llkeyboardmacosx.h new file mode 100644 index 0000000000..d9b7de4049 --- /dev/null +++ b/indra/llwindow/llkeyboardmacosx.h @@ -0,0 +1,36 @@ +/** + * @file llkeyboardmacosx.h + * @brief Handler for assignable key bindings + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLKEYBOARDMACOSX_H +#define LL_LLKEYBOARDMACOSX_H + +#include "llkeyboard.h" + +class LLKeyboardMacOSX : public LLKeyboard +{ +public: + LLKeyboardMacOSX(); + /*virtual*/ ~LLKeyboardMacOSX() {}; + + /*virtual*/ BOOL handleKeyUp(const U16 key, MASK mask); + /*virtual*/ BOOL handleKeyDown(const U16 key, MASK mask); + /*virtual*/ void resetMaskKeys(); + /*virtual*/ MASK currentMask(BOOL for_mouse_event); + /*virtual*/ void scanKeyboard(); + +protected: + MASK updateModifiers(const U32 mask); + void setModifierKeyLevel( KEY key, BOOL new_state ); + BOOL translateNumpadKey( const U16 os_key, KEY *translated_key ); + U16 inverseTranslateNumpadKey(const KEY translated_key); +private: + std::map mTranslateNumpadMap; // special map for translating OS keys to numpad keys + std::map mInvTranslateNumpadMap; // inverse of the above +}; + +#endif diff --git a/indra/llwindow/llkeyboardsdl.cpp b/indra/llwindow/llkeyboardsdl.cpp new file mode 100644 index 0000000000..436a31b5cd --- /dev/null +++ b/indra/llwindow/llkeyboardsdl.cpp @@ -0,0 +1,324 @@ +/** + * @file llkeyboardsdl.cpp + * @brief Handler for assignable key bindings + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#if LL_SDL + +#include "linden_common.h" +#include "llkeyboardsdl.h" +#include "llwindow.h" +#include "SDL/SDL.h" + +LLKeyboardSDL::LLKeyboardSDL() +{ + // Set up key mapping for SDL - eventually can read this from a file? + // Anything not in the key map gets dropped + // Add default A-Z + + // Virtual key mappings from SDL_keysym.h ... + + // SDL maps the letter keys to the ASCII you'd expect, but it's lowercase... + U16 cur_char; + for (cur_char = 'A'; cur_char <= 'Z'; cur_char++) + { + mTranslateKeyMap[cur_char] = cur_char; + } + for (cur_char = 'a'; cur_char <= 'z'; cur_char++) + { + mTranslateKeyMap[cur_char] = (cur_char - 'a') + 'A'; + } + + for (cur_char = '0'; cur_char <= '9'; cur_char++) + { + mTranslateKeyMap[cur_char] = cur_char; + } + + // These ones are translated manually upon keydown/keyup because + // SDL doesn't handle their numlock transition. + //mTranslateKeyMap[SDLK_KP4] = KEY_PAD_LEFT; + //mTranslateKeyMap[SDLK_KP6] = KEY_PAD_RIGHT; + //mTranslateKeyMap[SDLK_KP8] = KEY_PAD_UP; + //mTranslateKeyMap[SDLK_KP2] = KEY_PAD_DOWN; + //mTranslateKeyMap[SDLK_KP_PERIOD] = KEY_DELETE; + //mTranslateKeyMap[SDLK_KP7] = KEY_HOME; + //mTranslateKeyMap[SDLK_KP1] = KEY_END; + //mTranslateKeyMap[SDLK_KP9] = KEY_PAGE_UP; + //mTranslateKeyMap[SDLK_KP3] = KEY_PAGE_DOWN; + //mTranslateKeyMap[SDLK_KP0] = KEY_INSERT; + + mTranslateKeyMap[SDLK_SPACE] = ' '; + mTranslateKeyMap[SDLK_RETURN] = KEY_RETURN; + mTranslateKeyMap[SDLK_LEFT] = KEY_LEFT; + mTranslateKeyMap[SDLK_RIGHT] = KEY_RIGHT; + mTranslateKeyMap[SDLK_UP] = KEY_UP; + mTranslateKeyMap[SDLK_DOWN] = KEY_DOWN; + mTranslateKeyMap[SDLK_ESCAPE] = KEY_ESCAPE; + mTranslateKeyMap[SDLK_KP_ENTER] = KEY_RETURN; + mTranslateKeyMap[SDLK_ESCAPE] = KEY_ESCAPE; + mTranslateKeyMap[SDLK_BACKSPACE] = KEY_BACKSPACE; + mTranslateKeyMap[SDLK_DELETE] = KEY_DELETE; + mTranslateKeyMap[SDLK_LSHIFT] = KEY_SHIFT; + mTranslateKeyMap[SDLK_RSHIFT] = KEY_SHIFT; + mTranslateKeyMap[SDLK_LCTRL] = KEY_CONTROL; + mTranslateKeyMap[SDLK_RCTRL] = KEY_CONTROL; + mTranslateKeyMap[SDLK_LALT] = KEY_ALT; + mTranslateKeyMap[SDLK_RALT] = KEY_ALT; + mTranslateKeyMap[SDLK_HOME] = KEY_HOME; + mTranslateKeyMap[SDLK_END] = KEY_END; + mTranslateKeyMap[SDLK_PAGEUP] = KEY_PAGE_UP; + mTranslateKeyMap[SDLK_PAGEDOWN] = KEY_PAGE_DOWN; + mTranslateKeyMap[SDLK_MINUS] = KEY_HYPHEN; + mTranslateKeyMap[SDLK_EQUALS] = KEY_EQUALS; + mTranslateKeyMap[SDLK_KP_EQUALS] = KEY_EQUALS; + mTranslateKeyMap[SDLK_INSERT] = KEY_INSERT; + mTranslateKeyMap[SDLK_CAPSLOCK] = KEY_CAPSLOCK; + mTranslateKeyMap[SDLK_TAB] = KEY_TAB; + mTranslateKeyMap[SDLK_KP_PLUS] = KEY_ADD; + mTranslateKeyMap[SDLK_KP_MINUS] = KEY_SUBTRACT; + mTranslateKeyMap[SDLK_KP_MULTIPLY] = KEY_MULTIPLY; + mTranslateKeyMap[SDLK_KP_DIVIDE] = KEY_DIVIDE; + mTranslateKeyMap[SDLK_F1] = KEY_F1; + mTranslateKeyMap[SDLK_F2] = KEY_F2; + mTranslateKeyMap[SDLK_F3] = KEY_F3; + mTranslateKeyMap[SDLK_F4] = KEY_F4; + mTranslateKeyMap[SDLK_F5] = KEY_F5; + mTranslateKeyMap[SDLK_F6] = KEY_F6; + mTranslateKeyMap[SDLK_F7] = KEY_F7; + mTranslateKeyMap[SDLK_F8] = KEY_F8; + mTranslateKeyMap[SDLK_F9] = KEY_F9; + mTranslateKeyMap[SDLK_F10] = KEY_F10; + mTranslateKeyMap[SDLK_F11] = KEY_F11; + mTranslateKeyMap[SDLK_F12] = KEY_F12; + mTranslateKeyMap[SDLK_PLUS] = '='; + mTranslateKeyMap[SDLK_COMMA] = ','; + mTranslateKeyMap[SDLK_MINUS] = '-'; + mTranslateKeyMap[SDLK_PERIOD] = '.'; + mTranslateKeyMap[SDLK_BACKQUOTE] = '`'; + mTranslateKeyMap[SDLK_SLASH] = '/'; + mTranslateKeyMap[SDLK_SEMICOLON] = ';'; + mTranslateKeyMap[SDLK_LEFTBRACKET] = '['; + mTranslateKeyMap[SDLK_BACKSLASH] = '\\'; + mTranslateKeyMap[SDLK_RIGHTBRACKET] = ']'; + mTranslateKeyMap[SDLK_QUOTE] = '\''; + + // Build inverse map + std::map::iterator iter; + for (iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) + { + mInvTranslateKeyMap[iter->second] = iter->first; + } + + // numpad map + mTranslateNumpadMap[SDLK_KP0] = KEY_PAD_INS; + mTranslateNumpadMap[SDLK_KP1] = KEY_PAD_END; + mTranslateNumpadMap[SDLK_KP2] = KEY_PAD_DOWN; + mTranslateNumpadMap[SDLK_KP3] = KEY_PAD_PGDN; + mTranslateNumpadMap[SDLK_KP4] = KEY_PAD_LEFT; + mTranslateNumpadMap[SDLK_KP5] = KEY_PAD_CENTER; + mTranslateNumpadMap[SDLK_KP6] = KEY_PAD_RIGHT; + mTranslateNumpadMap[SDLK_KP7] = KEY_PAD_HOME; + mTranslateNumpadMap[SDLK_KP8] = KEY_PAD_UP; + mTranslateNumpadMap[SDLK_KP9] = KEY_PAD_PGUP; + mTranslateNumpadMap[SDLK_KP_PERIOD] = KEY_PAD_DEL; + + // build inverse numpad map + for (iter = mTranslateNumpadMap.begin(); + iter != mTranslateNumpadMap.end(); + iter++) + { + mInvTranslateNumpadMap[iter->second] = iter->first; + } +} + +void LLKeyboardSDL::resetMaskKeys() +{ + SDLMod mask = SDL_GetModState(); + + // MBW -- XXX -- This mirrors the operation of the Windows version of resetMaskKeys(). + // It looks a bit suspicious, as it won't correct for keys that have been released. + // Is this the way it's supposed to work? + + if(mask & KMOD_SHIFT) + { + mKeyLevel[KEY_SHIFT] = TRUE; + } + + if(mask & KMOD_CTRL) + { + mKeyLevel[KEY_CONTROL] = TRUE; + } + + if(mask & KMOD_ALT) + { + mKeyLevel[KEY_ALT] = TRUE; + } +} + + +MASK LLKeyboardSDL::updateModifiers(const U32 mask) +{ + // translate the mask + MASK out_mask = MASK_NONE; + + if(mask & KMOD_SHIFT) + { + out_mask |= MASK_SHIFT; + } + + if(mask & KMOD_CTRL) + { + out_mask |= MASK_CONTROL; + } + + if(mask & KMOD_ALT) + { + out_mask |= MASK_ALT; + } + + return out_mask; +} + + +static U16 adjustNativekeyFromUnhandledMask(const U16 key, const U32 mask) +{ + // SDL doesn't automatically adjust the keysym according to + // whether NUMLOCK is engaged, so we massage the keysym manually. + U16 rtn = key; + if (!(mask & KMOD_NUM)) + { + switch (key) + { + case SDLK_KP_PERIOD: rtn = SDLK_DELETE; break; + case SDLK_KP0: rtn = SDLK_INSERT; break; + case SDLK_KP1: rtn = SDLK_END; break; + case SDLK_KP2: rtn = SDLK_DOWN; break; + case SDLK_KP3: rtn = SDLK_PAGEDOWN; break; + case SDLK_KP4: rtn = SDLK_LEFT; break; + case SDLK_KP6: rtn = SDLK_RIGHT; break; + case SDLK_KP7: rtn = SDLK_HOME; break; + case SDLK_KP8: rtn = SDLK_UP; break; + case SDLK_KP9: rtn = SDLK_PAGEUP; break; + } + } + return rtn; +} + + +BOOL LLKeyboardSDL::handleKeyDown(const U16 key, const U32 mask) +{ + U16 adjusted_nativekey; + KEY translated_key = 0; + U32 translated_mask = MASK_NONE; + BOOL handled = FALSE; + + adjusted_nativekey = adjustNativekeyFromUnhandledMask(key, mask); + + translated_mask = updateModifiers(mask); + + if(translateNumpadKey(adjusted_nativekey, &translated_key)) + { + handled = handleTranslatedKeyDown(translated_key, translated_mask); + } + + return handled; +} + + +BOOL LLKeyboardSDL::handleKeyUp(const U16 key, const U32 mask) +{ + U16 adjusted_nativekey; + KEY translated_key = 0; + U32 translated_mask = MASK_NONE; + BOOL handled = FALSE; + + adjusted_nativekey = adjustNativekeyFromUnhandledMask(key, mask); + + translated_mask = updateModifiers(mask); + + if(translateNumpadKey(adjusted_nativekey, &translated_key)) + { + handled = handleTranslatedKeyUp(translated_key, translated_mask); + } + + return handled; +} + +MASK LLKeyboardSDL::currentMask(BOOL for_mouse_event) +{ + MASK result = MASK_NONE; + SDLMod mask = SDL_GetModState(); + + if (mask & KMOD_SHIFT) result |= MASK_SHIFT; + if (mask & KMOD_CTRL) result |= MASK_CONTROL; + if (mask & KMOD_ALT) result |= MASK_ALT; + + // For keyboard events, consider Meta keys equivalent to Control + if (!for_mouse_event) + { + if (mask & KMOD_META) result |= MASK_CONTROL; + } + + return result; +} + +void LLKeyboardSDL::scanKeyboard() +{ + for (S32 key = 0; key < KEY_COUNT; key++) + { + // Generate callback if any event has occurred on this key this frame. + // Can't just test mKeyLevel, because this could be a slow frame and + // key might have gone down then up. JC + if (mKeyLevel[key] || mKeyDown[key] || mKeyUp[key]) + { + mCurScanKey = key; + mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]); + } + } + + // Reset edges for next frame + for (S32 key = 0; key < KEY_COUNT; key++) + { + mKeyUp[key] = FALSE; + mKeyDown[key] = FALSE; + if (mKeyLevel[key]) + { + mKeyLevelFrameCount[key]++; + } + } +} + + +BOOL LLKeyboardSDL::translateNumpadKey( const U16 os_key, KEY *translated_key) +{ + if(mNumpadDistinct == ND_NUMLOCK_ON) + { + std::map::iterator iter= mTranslateNumpadMap.find(os_key); + if(iter != mTranslateNumpadMap.end()) + { + *translated_key = iter->second; + return TRUE; + } + } + BOOL success = translateKey(os_key, translated_key); + return success; +} + +U16 LLKeyboardSDL::inverseTranslateNumpadKey(const KEY translated_key) +{ + if(mNumpadDistinct == ND_NUMLOCK_ON) + { + std::map::iterator iter= mInvTranslateNumpadMap.find(translated_key); + if(iter != mInvTranslateNumpadMap.end()) + { + return iter->second; + } + } + return inverseTranslateKey(translated_key); +} + +#endif + diff --git a/indra/llwindow/llkeyboardsdl.h b/indra/llwindow/llkeyboardsdl.h new file mode 100644 index 0000000000..83701d37bf --- /dev/null +++ b/indra/llwindow/llkeyboardsdl.h @@ -0,0 +1,37 @@ +/** + * @file llkeyboardsdl.h + * @brief Handler for assignable key bindings + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLKEYBOARDSDL_H +#define LL_LLKEYBOARDSDL_H + +#include "llkeyboard.h" +#include "SDL/SDL.h" + +class LLKeyboardSDL : public LLKeyboard +{ +public: + LLKeyboardSDL(); + /*virtual*/ ~LLKeyboardSDL() {}; + + /*virtual*/ BOOL handleKeyUp(const U16 key, MASK mask); + /*virtual*/ BOOL handleKeyDown(const U16 key, MASK mask); + /*virtual*/ void resetMaskKeys(); + /*virtual*/ MASK currentMask(BOOL for_mouse_event); + /*virtual*/ void scanKeyboard(); + +protected: + MASK updateModifiers(const U32 mask); + void setModifierKeyLevel( KEY key, BOOL new_state ); + BOOL translateNumpadKey( const U16 os_key, KEY *translated_key ); + U16 inverseTranslateNumpadKey(const KEY translated_key); +private: + std::map mTranslateNumpadMap; // special map for translating OS keys to numpad keys + std::map mInvTranslateNumpadMap; // inverse of the above +}; + +#endif diff --git a/indra/llwindow/llkeyboardwin32.cpp b/indra/llwindow/llkeyboardwin32.cpp new file mode 100644 index 0000000000..37eb967e27 --- /dev/null +++ b/indra/llwindow/llkeyboardwin32.cpp @@ -0,0 +1,372 @@ +/** + * @file llkeyboardwin32.cpp + * @brief Handler for assignable key bindings + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#if LL_WINDOWS + +#include "linden_common.h" +#include "llkeyboardwin32.h" +#include "llwindow.h" + +LLKeyboardWin32::LLKeyboardWin32() +{ + // Set up key mapping for windows - eventually can read this from a file? + // Anything not in the key map gets dropped + // Add default A-Z + + // Virtual key mappings from WinUser.h + + KEY cur_char; + for (cur_char = 'A'; cur_char <= 'Z'; cur_char++) + { + mTranslateKeyMap[cur_char] = (KEY)cur_char; + } + + for (cur_char = '0'; cur_char <= '9'; cur_char++) + { + mTranslateKeyMap[cur_char] = (KEY)cur_char; + } + // numpad number keys + for (cur_char = 0x60; cur_char <= 0x69; cur_char++) + { + mTranslateKeyMap[cur_char] = (KEY)('0' + (0x60 - cur_char)); + } + + + mTranslateKeyMap[VK_SPACE] = ' '; + mTranslateKeyMap[VK_OEM_1] = ';'; + // When the user hits, for example, Ctrl-= as a keyboard shortcut, + // Windows generates VK_OEM_PLUS. This is true on both QWERTY and DVORAK + // keyboards in the US. Numeric keypad '+' generates VK_ADD below. + // Thus we translate it as '='. + // Potential bug: This may not be true on international keyboards. JC + mTranslateKeyMap[VK_OEM_PLUS] = '='; + mTranslateKeyMap[VK_OEM_COMMA] = ','; + mTranslateKeyMap[VK_OEM_MINUS] = '-'; + mTranslateKeyMap[VK_OEM_PERIOD] = '.'; + mTranslateKeyMap[VK_OEM_2] = '/'; + mTranslateKeyMap[VK_OEM_3] = '`'; + mTranslateKeyMap[VK_OEM_4] = '['; + mTranslateKeyMap[VK_OEM_5] = '\\'; + mTranslateKeyMap[VK_OEM_6] = ']'; + mTranslateKeyMap[VK_OEM_7] = '\''; + mTranslateKeyMap[VK_ESCAPE] = KEY_ESCAPE; + mTranslateKeyMap[VK_RETURN] = KEY_RETURN; + mTranslateKeyMap[VK_LEFT] = KEY_LEFT; + mTranslateKeyMap[VK_RIGHT] = KEY_RIGHT; + mTranslateKeyMap[VK_UP] = KEY_UP; + mTranslateKeyMap[VK_DOWN] = KEY_DOWN; + mTranslateKeyMap[VK_BACK] = KEY_BACKSPACE; + mTranslateKeyMap[VK_INSERT] = KEY_INSERT; + mTranslateKeyMap[VK_DELETE] = KEY_DELETE; + mTranslateKeyMap[VK_SHIFT] = KEY_SHIFT; + mTranslateKeyMap[VK_CONTROL] = KEY_CONTROL; + mTranslateKeyMap[VK_MENU] = KEY_ALT; + mTranslateKeyMap[VK_CAPITAL] = KEY_CAPSLOCK; + mTranslateKeyMap[VK_HOME] = KEY_HOME; + mTranslateKeyMap[VK_END] = KEY_END; + mTranslateKeyMap[VK_PRIOR] = KEY_PAGE_UP; + mTranslateKeyMap[VK_NEXT] = KEY_PAGE_DOWN; + mTranslateKeyMap[VK_TAB] = KEY_TAB; + mTranslateKeyMap[VK_ADD] = KEY_ADD; + mTranslateKeyMap[VK_SUBTRACT] = KEY_SUBTRACT; + mTranslateKeyMap[VK_MULTIPLY] = KEY_MULTIPLY; + mTranslateKeyMap[VK_DIVIDE] = KEY_DIVIDE; + mTranslateKeyMap[VK_F1] = KEY_F1; + mTranslateKeyMap[VK_F2] = KEY_F2; + mTranslateKeyMap[VK_F3] = KEY_F3; + mTranslateKeyMap[VK_F4] = KEY_F4; + mTranslateKeyMap[VK_F5] = KEY_F5; + mTranslateKeyMap[VK_F6] = KEY_F6; + mTranslateKeyMap[VK_F7] = KEY_F7; + mTranslateKeyMap[VK_F8] = KEY_F8; + mTranslateKeyMap[VK_F9] = KEY_F9; + mTranslateKeyMap[VK_F10] = KEY_F10; + mTranslateKeyMap[VK_F11] = KEY_F11; + mTranslateKeyMap[VK_F12] = KEY_F12; + mTranslateKeyMap[VK_CLEAR] = KEY_PAD_CENTER; + + // Build inverse map + std::map::iterator iter; + for (iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) + { + mInvTranslateKeyMap[iter->second] = iter->first; + } + + // numpad map + mTranslateNumpadMap[0x60] = KEY_PAD_INS; // keypad 0 + mTranslateNumpadMap[0x61] = KEY_PAD_END; // keypad 1 + mTranslateNumpadMap[0x62] = KEY_PAD_DOWN; // keypad 2 + mTranslateNumpadMap[0x63] = KEY_PAD_PGDN; // keypad 3 + mTranslateNumpadMap[0x64] = KEY_PAD_LEFT; // keypad 4 + mTranslateNumpadMap[0x65] = KEY_PAD_CENTER; // keypad 5 + mTranslateNumpadMap[0x66] = KEY_PAD_RIGHT; // keypad 6 + mTranslateNumpadMap[0x67] = KEY_PAD_HOME; // keypad 7 + mTranslateNumpadMap[0x68] = KEY_PAD_UP; // keypad 8 + mTranslateNumpadMap[0x69] = KEY_PAD_PGUP; // keypad 9 + mTranslateNumpadMap[0x6E] = KEY_PAD_DEL; // keypad . + + for (iter = mTranslateNumpadMap.begin(); iter != mTranslateNumpadMap.end(); iter++) + { + mInvTranslateNumpadMap[iter->second] = iter->first; + } +} + +// Asynchronously poll the control, alt and shift keys and set the +// appropriate states. +// Note: this does not generate edges. +void LLKeyboardWin32::resetMaskKeys() +{ + // GetAsyncKeyState returns a short and uses the most significant + // bit to indicate that the key is down. + if (GetAsyncKeyState(VK_SHIFT) & 0x8000) + { + mKeyLevel[KEY_SHIFT] = TRUE; + } + + if (GetAsyncKeyState(VK_CONTROL) & 0x8000) + { + mKeyLevel[KEY_CONTROL] = TRUE; + } + + if (GetAsyncKeyState(VK_MENU) & 0x8000) + { + mKeyLevel[KEY_ALT] = TRUE; + } +} + + +void LLKeyboardWin32::setModifierKeyLevel( KEY key, BOOL new_state ) +{ + if( mKeyLevel[key] != new_state ) + { + mKeyLevelFrameCount[key] = 0; + + if( new_state ) + { + mKeyLevelTimer[key].reset(); + } + mKeyLevel[key] = new_state; + } +} + + +MASK LLKeyboardWin32::updateModifiers() +{ + // Scan the modifier keys as of the last Windows key message + // (keydown encoded in high order bit of short) + setModifierKeyLevel( KEY_SHIFT, GetKeyState(VK_SHIFT) & 0x8000 ); + setModifierKeyLevel( KEY_CONTROL, GetKeyState(VK_CONTROL) & 0x8000 ); + setModifierKeyLevel( KEY_ALT, GetKeyState(VK_MENU) & 0x8000 ); + setModifierKeyLevel( KEY_CAPSLOCK, GetKeyState(VK_CAPITAL) & 0x0001); // Low order bit carries the toggle state. + // Get mask for keyboard events + MASK mask = currentMask(FALSE); + return mask; +} + + +// mask is ignored, except for extended flag -- we poll the modifier keys for the other flags +BOOL LLKeyboardWin32::handleKeyDown(const U16 key, MASK mask) +{ + KEY translated_key; + U32 translated_mask; + BOOL handled = FALSE; + + translated_mask = updateModifiers(); + + if (translateExtendedKey(key, mask, &translated_key)) + { + handled = handleTranslatedKeyDown(translated_key, translated_mask); + } + + return handled; +} + + +// mask is ignored, except for extended flag -- we poll the modifier keys for the other flags +BOOL LLKeyboardWin32::handleKeyUp(const U16 key, MASK mask) +{ + KEY translated_key; + U32 translated_mask; + BOOL handled = FALSE; + + translated_mask = updateModifiers(); + + if (translateExtendedKey(key, mask, &translated_key)) + { + handled = handleTranslatedKeyUp(translated_key, translated_mask); + } + + return handled; +} + + +MASK LLKeyboardWin32::currentMask(BOOL) +{ + MASK mask = MASK_NONE; + + if (mKeyLevel[KEY_SHIFT]) mask |= MASK_SHIFT; + if (mKeyLevel[KEY_CONTROL]) mask |= MASK_CONTROL; + if (mKeyLevel[KEY_ALT]) mask |= MASK_ALT; + + return mask; +} + + +void LLKeyboardWin32::scanKeyboard() +{ + S32 key; + for (key = 0; key < KEY_COUNT; key++) + { + // On Windows, verify key down state. JC + if (mKeyLevel[key]) + { + // FIXME: I KNOW there must be a better way of interrogating the key state than this, using async + // key state can cause ALL kinds of bugs - Doug + if (key < KEY_BUTTON0) + { + // ...under windows make sure the key actually still is down. + // ...translate back to windows key + U16 virtual_key = inverseTranslateExtendedKey(key); + // keydown in highest bit + if (!(GetAsyncKeyState(virtual_key) & 0x8000)) + { + //llinfos << "Key up event missed, resetting" << llendl; + mKeyLevel[key] = FALSE; + mKeyLevelFrameCount[key] = 0; + } + } + } + + // Generate callback if any event has occurred on this key this frame. + // Can't just test mKeyLevel, because this could be a slow frame and + // key might have gone down then up. JC + if (mKeyLevel[key] || mKeyDown[key] || mKeyUp[key]) + { + mCurScanKey = key; + mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]); + } + } + + // Reset edges for next frame + for (key = 0; key < KEY_COUNT; key++) + { + mKeyUp[key] = FALSE; + mKeyDown[key] = FALSE; + if (mKeyLevel[key]) + { + mKeyLevelFrameCount[key]++; + } + } +} + +BOOL LLKeyboardWin32::translateExtendedKey(const U16 os_key, const MASK mask, KEY *translated_key) +{ + if(mNumpadDistinct == ND_NUMLOCK_ON) + { + std::map::iterator iter = mTranslateNumpadMap.find(os_key); + if (iter != mTranslateNumpadMap.end()) + { + *translated_key = iter->second; + return TRUE; + } + } + + BOOL success = translateKey(os_key, translated_key); + if(mNumpadDistinct != ND_NEVER) { + if(!success) return success; + if(mask & MASK_EXTENDED) + { + // this is where we'd create new keycodes for extended keys + // the set of extended keys includes the 'normal' arrow keys and + // the pgup/dn/insert/home/end/delete cluster above the arrow keys + // see http://windowssdk.msdn.microsoft.com/en-us/library/ms646280.aspx + + // only process the return key if numlock is off + if(((mNumpadDistinct == ND_NUMLOCK_OFF && + !(GetKeyState(VK_NUMLOCK) & 1)) + || mNumpadDistinct == ND_NUMLOCK_ON) && + *translated_key == KEY_RETURN) { + *translated_key = KEY_PAD_RETURN; + } + } + else + { + // the non-extended keys, those are in the numpad + switch (*translated_key) + { + case KEY_LEFT: + *translated_key = KEY_PAD_LEFT; break; + case KEY_RIGHT: + *translated_key = KEY_PAD_RIGHT; break; + case KEY_UP: + *translated_key = KEY_PAD_UP; break; + case KEY_DOWN: + *translated_key = KEY_PAD_DOWN; break; + case KEY_HOME: + *translated_key = KEY_PAD_HOME; break; + case KEY_END: + *translated_key = KEY_PAD_END; break; + case KEY_PAGE_UP: + *translated_key = KEY_PAD_PGUP; break; + case KEY_PAGE_DOWN: + *translated_key = KEY_PAD_PGDN; break; + case KEY_INSERT: + *translated_key = KEY_PAD_INS; break; + case KEY_DELETE: + *translated_key = KEY_PAD_DEL; break; + } + } + } + return success; +} + +U16 LLKeyboardWin32::inverseTranslateExtendedKey(const KEY translated_key) +{ + // if numlock is on, then we need to translate KEY_PAD_FOO to the corresponding number pad number + if((mNumpadDistinct == ND_NUMLOCK_ON) && (GetKeyState(VK_NUMLOCK) & 1)) + { + std::map::iterator iter = mInvTranslateNumpadMap.find(translated_key); + if (iter != mInvTranslateNumpadMap.end()) + { + return iter->second; + } + } + + // if numlock is off or we're not converting numbers to arrows, we map our keypad arrows + // to regular arrows since Windows doesn't distinguish between them + KEY converted_key = translated_key; + switch (converted_key) + { + case KEY_PAD_LEFT: + converted_key = KEY_LEFT; break; + case KEY_PAD_RIGHT: + converted_key = KEY_RIGHT; break; + case KEY_PAD_UP: + converted_key = KEY_UP; break; + case KEY_PAD_DOWN: + converted_key = KEY_DOWN; break; + case KEY_PAD_HOME: + converted_key = KEY_HOME; break; + case KEY_PAD_END: + converted_key = KEY_END; break; + case KEY_PAD_PGUP: + converted_key = KEY_PAGE_UP; break; + case KEY_PAD_PGDN: + converted_key = KEY_PAGE_DOWN; break; + case KEY_PAD_INS: + converted_key = KEY_INSERT; break; + case KEY_PAD_DEL: + converted_key = KEY_DELETE; break; + case KEY_PAD_RETURN: + converted_key = KEY_RETURN; break; + } + // convert our virtual keys to OS keys + return inverseTranslateKey(converted_key); +} + +#endif diff --git a/indra/llwindow/llkeyboardwin32.h b/indra/llwindow/llkeyboardwin32.h new file mode 100644 index 0000000000..e7eb4b9c1e --- /dev/null +++ b/indra/llwindow/llkeyboardwin32.h @@ -0,0 +1,40 @@ +/** + * @file llkeyboardwin32.h + * @brief Handler for assignable key bindings + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLKEYBOARDWIN32_H +#define LL_LLKEYBOARDWIN32_H + +#include "llkeyboard.h" + +// this mask distinguishes extended keys, which include non-numpad arrow keys +// (and, curiously, the num lock and numpad '/') +const MASK MASK_EXTENDED = 0x0100; + +class LLKeyboardWin32 : public LLKeyboard +{ +public: + LLKeyboardWin32(); + /*virtual*/ ~LLKeyboardWin32() {}; + + /*virtual*/ BOOL handleKeyUp(const U16 key, MASK mask); + /*virtual*/ BOOL handleKeyDown(const U16 key, MASK mask); + /*virtual*/ void resetMaskKeys(); + /*virtual*/ MASK currentMask(BOOL for_mouse_event); + /*virtual*/ void scanKeyboard(); + BOOL translateExtendedKey(const U16 os_key, const MASK mask, KEY *translated_key); + U16 inverseTranslateExtendedKey(const KEY translated_key); + +protected: + MASK updateModifiers(); + void setModifierKeyLevel( KEY key, BOOL new_state ); +private: + std::map mTranslateNumpadMap; + std::map mInvTranslateNumpadMap; +}; + +#endif diff --git a/indra/llwindow/llmousehandler.h b/indra/llwindow/llmousehandler.h new file mode 100644 index 0000000000..bd163535a0 --- /dev/null +++ b/indra/llwindow/llmousehandler.h @@ -0,0 +1,39 @@ +/** + * @file llmousehandler.h + * @brief LLMouseHandler class definition + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_MOUSEHANDLER_H +#define LL_MOUSEHANDLER_H + +// Abstract interface. +// Intended for use via multiple inheritance. +// A class may have as many interfaces as it likes, but never needs to inherit one more than once. + +class LLMouseHandler +{ +public: + LLMouseHandler() {} + virtual ~LLMouseHandler() {} + + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask) = 0; + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask) = 0; + virtual BOOL handleHover(S32 x, S32 y, MASK mask) = 0; + virtual BOOL handleScrollWheel(S32 x, S32 y, S32 clicks) = 0; + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask) = 0; + virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask) = 0; + virtual BOOL handleRightMouseUp(S32 x, S32 y, MASK mask) = 0; + virtual BOOL handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen) = 0; + virtual const LLString& getName() const = 0; + + // Hack to support LLFocusMgr + virtual BOOL isView() = 0; + + virtual void screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const = 0; + virtual void localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const = 0; +}; + +#endif diff --git a/indra/llwindow/llwindow.cpp b/indra/llwindow/llwindow.cpp new file mode 100644 index 0000000000..435ac9d0cc --- /dev/null +++ b/indra/llwindow/llwindow.cpp @@ -0,0 +1,399 @@ +/** + * @file llwindow.cpp + * @brief Basic graphical window class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llwindowheadless.h" + +#if LL_MESA_HEADLESS +#include "llwindowmesaheadless.h" +#elif LL_SDL +#include "llwindowsdl.h" +#elif LL_WINDOWS +#include "llwindowwin32.h" +#elif LL_DARWIN +#include "llwindowmacosx.h" +#elif LL_LINUX +#include "llwindowlinux.h" // currently just a dummy wrapper +#endif + +#include "llerror.h" +#include "llkeyboard.h" + +//static instance for default callbacks +LLWindowCallbacks LLWindow::sDefaultCallbacks; + +// +// LLWindowCallbacks +// + +LLSplashScreen *gSplashScreenp = NULL; +BOOL gDebugClicks = FALSE; +BOOL gDebugWindowProc = FALSE; + +const S32 gURLProtocolWhitelistCount = 3; +const char* gURLProtocolWhitelist[] = { "file", "http", "https" }; + +// CP: added a handler list - this is what's used to open the protocol and is based on registry entry +// only meaningful difference currently is that file: protocols are opened using http: +// since no protocol handler exists in registry for file: +// Important - these lists should match - protocol to handler +const char* gURLProtocolWhitelistHandler[] = { "http", "http", "https" }; + +BOOL LLWindowCallbacks::handleTranslatedKeyDown(const KEY key, const MASK mask, BOOL repeated) +{ + return FALSE; +} + + +BOOL LLWindowCallbacks::handleTranslatedKeyUp(const KEY key, const MASK mask) +{ + return FALSE; +} + +void LLWindowCallbacks::handleScanKey(KEY key, BOOL key_down, BOOL key_up, BOOL key_level) +{ +} + +BOOL LLWindowCallbacks::handleUnicodeChar(llwchar uni_char, MASK mask) +{ + return FALSE; +} + + +BOOL LLWindowCallbacks::handleMouseDown(LLWindow *window, const LLCoordGL pos, MASK mask) +{ + return FALSE; +} + +BOOL LLWindowCallbacks::handleMouseUp(LLWindow *window, const LLCoordGL pos, MASK mask) +{ + return FALSE; +} + +void LLWindowCallbacks::handleMouseLeave(LLWindow *window) +{ + return; +} + +BOOL LLWindowCallbacks::handleCloseRequest(LLWindow *window) +{ + //allow the window to close + return TRUE; +} + +void LLWindowCallbacks::handleQuit(LLWindow *window) +{ + if(LLWindowManager::destroyWindow(window) == FALSE) + { + llerrs << "LLWindowCallbacks::handleQuit() : Couldn't destroy window" << llendl; + } +} + +BOOL LLWindowCallbacks::handleRightMouseDown(LLWindow *window, const LLCoordGL pos, MASK mask) +{ + return FALSE; +} + +BOOL LLWindowCallbacks::handleRightMouseUp(LLWindow *window, const LLCoordGL pos, MASK mask) +{ + return FALSE; +} + +BOOL LLWindowCallbacks::handleActivate(LLWindow *window, BOOL activated) +{ + return FALSE; +} + +void LLWindowCallbacks::handleMouseMove(LLWindow *window, const LLCoordGL pos, MASK mask) +{ +} + +void LLWindowCallbacks::handleScrollWheel(LLWindow *window, S32 clicks) +{ +} + +void LLWindowCallbacks::handleResize(LLWindow *window, const S32 width, const S32 height) +{ +} + +void LLWindowCallbacks::handleFocus(LLWindow *window) +{ +} + +void LLWindowCallbacks::handleFocusLost(LLWindow *window) +{ +} + +void LLWindowCallbacks::handleMenuSelect(LLWindow *window, const S32 menu_item) +{ +} + +BOOL LLWindowCallbacks::handlePaint(LLWindow *window, const S32 x, const S32 y, + const S32 width, const S32 height) +{ + return FALSE; +} + +BOOL LLWindowCallbacks::handleDoubleClick(LLWindow *window, const LLCoordGL pos, MASK mask) +{ + return FALSE; +} + +void LLWindowCallbacks::handleWindowBlock(LLWindow *window) +{ +} + +void LLWindowCallbacks::handleWindowUnblock(LLWindow *window) +{ +} + +void LLWindowCallbacks::handleDataCopy(LLWindow *window, S32 data_type, void *data) +{ +} + + +S32 OSMessageBox(const char* text, const char* caption, U32 type) +{ + // Properly hide the splash screen when displaying the message box + BOOL was_visible = FALSE; + if (LLSplashScreen::isVisible()) + { + was_visible = TRUE; + LLSplashScreen::hide(); + } + + S32 result = 0; +#if LL_MESA_HEADLESS // !!! FIXME + llwarns << "OSMessageBox: " << text << llendl; + return OSBTN_OK; +#elif LL_WINDOWS + result = OSMessageBoxWin32(text, caption, type); +#elif LL_DARWIN + result = OSMessageBoxMacOSX(text, caption, type); +#elif LL_SDL + result = OSMessageBoxSDL(text, caption, type); +#else +#error("OSMessageBox not implemented for this platform!") +#endif + + if (was_visible) + { + LLSplashScreen::show(); + } + + return result; +} + + +// +// LLWindow +// + +LLWindow::LLWindow(BOOL fullscreen, U32 flags) + : mCallbacks(&sDefaultCallbacks), + mPostQuit(TRUE), + mFullscreen(fullscreen), + mFullscreenWidth(0), + mFullscreenHeight(0), + mFullscreenBits(0), + mFullscreenRefresh(0), + mSupportedResolutions(NULL), + mNumSupportedResolutions(0), + mCurrentCursor(UI_CURSOR_ARROW), + mCursorHidden(FALSE), + mBusyCount(0), + mIsMouseClipping(FALSE), + mSwapMethod(SWAP_METHOD_UNDEFINED), + mHideCursorPermanent(FALSE), + mFlags(flags) +{ +} + +// virtual +void LLWindow::incBusyCount() +{ + ++mBusyCount; +} + +// virtual +void LLWindow::decBusyCount() +{ + if (mBusyCount > 0) + { + --mBusyCount; + } +} + +void LLWindow::setCallbacks(LLWindowCallbacks *callbacks) +{ + mCallbacks = callbacks; + if (gKeyboard) + { + gKeyboard->setCallbacks(callbacks); + } +} + +// +// LLSplashScreen +// + +// static +bool LLSplashScreen::isVisible() +{ + return gSplashScreenp ? true: false; +} + +// static +LLSplashScreen *LLSplashScreen::create() +{ +#if LL_MESA_HEADLESS || LL_SDL // !!! FIXME + return 0; +#elif LL_WINDOWS + return new LLSplashScreenWin32; +#elif LL_DARWIN + return new LLSplashScreenMacOSX; +#else +#error("LLSplashScreen not implemented on this platform!") +#endif +} + + +//static +void LLSplashScreen::show() +{ + if (!gSplashScreenp) + { +#if LL_WINDOWS && !LL_MESA_HEADLESS + gSplashScreenp = new LLSplashScreenWin32; +#elif LL_DARWIN + gSplashScreenp = new LLSplashScreenMacOSX; +#endif + if (gSplashScreenp) + { + gSplashScreenp->showImpl(); + } + } +} + +//static +void LLSplashScreen::update(const char* str) +{ + LLSplashScreen::show(); + if (gSplashScreenp) + { + gSplashScreenp->updateImpl(str); + } +} + +//static +void LLSplashScreen::hide() +{ + if (gSplashScreenp) + { + gSplashScreenp->hideImpl(); + } + delete gSplashScreenp; + gSplashScreenp = NULL; +} + +// +// LLWindowManager +// + +LLLinkedList LLWindowManager::sWindowList; + +LLWindow* LLWindowManager::createWindow( + char *title, + char *name, + LLCoordScreen upper_left, + LLCoordScreen size, + U32 flags, + BOOL fullscreen, + BOOL clearBg, + BOOL disable_vsync, + BOOL use_gl, + BOOL ignore_pixel_depth) +{ + return createWindow( + title, name, upper_left.mX, upper_left.mY, size.mX, size.mY, flags, + fullscreen, clearBg, disable_vsync, use_gl, ignore_pixel_depth); +} + +LLWindow* LLWindowManager::createWindow( + char *title, char *name, S32 x, S32 y, S32 width, S32 height, U32 flags, + BOOL fullscreen, + BOOL clearBg, + BOOL disable_vsync, + BOOL use_gl, + BOOL ignore_pixel_depth) +{ + LLWindow* new_window; + + if (use_gl) + { +#if LL_MESA_HEADLESS + new_window = new LLWindowMesaHeadless( + title, name, x, y, width, height, flags, + fullscreen, clearBg, disable_vsync, use_gl, ignore_pixel_depth); +#elif LL_SDL + new_window = new LLWindowSDL( + title, x, y, width, height, flags, + fullscreen, clearBg, disable_vsync, use_gl, ignore_pixel_depth); +#elif LL_WINDOWS + new_window = new LLWindowWin32( + title, name, x, y, width, height, flags, + fullscreen, clearBg, disable_vsync, use_gl, ignore_pixel_depth); +#elif LL_DARWIN + new_window = new LLWindowMacOSX( + title, name, x, y, width, height, flags, + fullscreen, clearBg, disable_vsync, use_gl, ignore_pixel_depth); +#elif LL_LINUX + new_window = new LLWindowLinux( + title, name, x, y, width, height, flags, + fullscreen, clearBg, disable_vsync, use_gl, ignore_pixel_depth); +#endif + } + else + { + new_window = new LLWindowHeadless( + title, name, x, y, width, height, flags, + fullscreen, clearBg, disable_vsync, use_gl, ignore_pixel_depth); + } + + if (FALSE == new_window->isValid()) + { + delete new_window; + llwarns << "LLWindowManager::create() : Error creating window." << llendl; + return NULL; + } + sWindowList.addDataAtEnd(new_window); + return new_window; +} + +BOOL LLWindowManager::destroyWindow(LLWindow* window) +{ + if (!sWindowList.checkData(window)) + { + llerrs << "LLWindowManager::destroyWindow() : Window pointer not valid, this window doesn't exist!" + << llendl; + return FALSE; + } + + window->close(); + + sWindowList.removeData(window); + + delete window; + + return TRUE; +} + +BOOL LLWindowManager::isWindowValid(LLWindow *window) +{ + return sWindowList.checkData(window); +} diff --git a/indra/llwindow/llwindow.h b/indra/llwindow/llwindow.h new file mode 100644 index 0000000000..703eee32d0 --- /dev/null +++ b/indra/llwindow/llwindow.h @@ -0,0 +1,328 @@ +/** + * @file llwindow.h + * @brief Basic graphical window class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLWINDOW_H +#define LL_LLWINDOW_H + +#include + +#include "llrect.h" +#include "linked_lists.h" +#include "llcoord.h" +#include "llstring.h" + + +enum ECursorType { + UI_CURSOR_ARROW, + UI_CURSOR_WAIT, + UI_CURSOR_HAND, + UI_CURSOR_IBEAM, + UI_CURSOR_CROSS, + UI_CURSOR_SIZENWSE, + UI_CURSOR_SIZENESW, + UI_CURSOR_SIZEWE, + UI_CURSOR_SIZENS, + UI_CURSOR_NO, + UI_CURSOR_WORKING, + UI_CURSOR_TOOLGRAB, + UI_CURSOR_TOOLLAND, + UI_CURSOR_TOOLFOCUS, + UI_CURSOR_TOOLCREATE, + UI_CURSOR_ARROWDRAG, + UI_CURSOR_ARROWCOPY, // drag with copy + UI_CURSOR_ARROWDRAGMULTI, + UI_CURSOR_ARROWCOPYMULTI, // drag with copy + UI_CURSOR_NOLOCKED, + UI_CURSOR_ARROWLOCKED, + UI_CURSOR_GRABLOCKED, + UI_CURSOR_TOOLTRANSLATE, + UI_CURSOR_TOOLROTATE, + UI_CURSOR_TOOLSCALE, + UI_CURSOR_TOOLCAMERA, + UI_CURSOR_TOOLPAN, + UI_CURSOR_TOOLZOOMIN, + UI_CURSOR_TOOLPICKOBJECT3, + UI_CURSOR_TOOLSIT, + UI_CURSOR_TOOLBUY, + UI_CURSOR_TOOLPAY, + UI_CURSOR_TOOLOPEN, + UI_CURSOR_PIPETTE, + UI_CURSOR_COUNT // Number of elements in this enum (NOT a cursor) +}; + +class LLSplashScreen; + +class LLWindow; + +class LLWindowCallbacks +{ +public: + virtual ~LLWindowCallbacks() {} + virtual BOOL handleTranslatedKeyDown(KEY key, MASK mask, BOOL repeated); + virtual BOOL handleTranslatedKeyUp(KEY key, MASK mask); + virtual void handleScanKey(KEY key, BOOL key_down, BOOL key_up, BOOL key_level); + virtual BOOL handleUnicodeChar(llwchar uni_char, MASK mask); + + virtual BOOL handleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); + virtual BOOL handleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); + virtual void handleMouseLeave(LLWindow *window); + // return TRUE to allow window to close, which will then cause handleQuit to be called + virtual BOOL handleCloseRequest(LLWindow *window); + // window is about to be destroyed, clean up your business + virtual void handleQuit(LLWindow *window); + virtual BOOL handleRightMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); + virtual BOOL handleRightMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); + virtual BOOL handleActivate(LLWindow *window, BOOL activated); + virtual void handleMouseMove(LLWindow *window, LLCoordGL pos, MASK mask); + virtual void handleScrollWheel(LLWindow *window, S32 clicks); + virtual void handleResize(LLWindow *window, S32 width, S32 height); + virtual void handleFocus(LLWindow *window); + virtual void handleFocusLost(LLWindow *window); + virtual void handleMenuSelect(LLWindow *window, S32 menu_item); + virtual BOOL handlePaint(LLWindow *window, S32 x, S32 y, S32 width, S32 height); + virtual BOOL handleDoubleClick(LLWindow *window, LLCoordGL pos, MASK mask); // double-click of left mouse button + virtual void handleWindowBlock(LLWindow *window); // window is taking over CPU for a while + virtual void handleWindowUnblock(LLWindow *window); // window coming back after taking over CPU for a while + virtual void handleDataCopy(LLWindow *window, S32 data_type, void *data); +}; + +// Refer to llwindow_test in test/common/llwindow for usage example + +class LLWindow +{ +public: + struct LLWindowResolution + { + S32 mWidth; + S32 mHeight; + }; + enum ESwapMethod + { + SWAP_METHOD_UNDEFINED, + SWAP_METHOD_EXCHANGE, + SWAP_METHOD_COPY + }; + enum EFlags + { + // currently unused + }; +public: + virtual void show() = 0; + virtual void hide() = 0; + virtual void close() = 0; + virtual BOOL getVisible() = 0; + virtual BOOL getMinimized() = 0; + virtual BOOL getMaximized() = 0; + virtual BOOL maximize() = 0; + BOOL getFullscreen() { return mFullscreen; }; + virtual BOOL getPosition(LLCoordScreen *position) = 0; + virtual BOOL getSize(LLCoordScreen *size) = 0; + virtual BOOL getSize(LLCoordWindow *size) = 0; + virtual BOOL setPosition(LLCoordScreen position) = 0; + virtual BOOL setSize(LLCoordScreen size) = 0; + virtual BOOL switchContext(BOOL fullscreen, LLCoordScreen size, BOOL disable_vsync) = 0; + virtual BOOL setCursorPosition(LLCoordWindow position) = 0; + virtual BOOL getCursorPosition(LLCoordWindow *position) = 0; + virtual void showCursor() = 0; + virtual void hideCursor() = 0; + virtual BOOL isCursorHidden() = 0; + virtual void showCursorFromMouseMove() = 0; + virtual void hideCursorUntilMouseMove() = 0; + + // These two functions create a way to make a busy cursor instead + // of an arrow when someone's busy doing something. Draw an + // arrow/hour if busycount > 0. + virtual void incBusyCount(); + virtual void decBusyCount(); + virtual void resetBusyCount() { mBusyCount = 0; } + virtual S32 getBusyCount() const { return mBusyCount; } + + // Sets cursor, may set to arrow+hourglass + virtual void setCursor(ECursorType cursor) = 0; + virtual ECursorType getCursor() const { return mCurrentCursor; } + + virtual void captureMouse() = 0; + virtual void releaseMouse() = 0; + virtual void setMouseClipping( BOOL b ) = 0; + virtual BOOL isClipboardTextAvailable() = 0; + virtual BOOL pasteTextFromClipboard(LLWString &dst) = 0; + virtual BOOL copyTextToClipboard(const LLWString &src) = 0; + virtual void flashIcon(F32 seconds) = 0; + virtual F32 getGamma() = 0; + virtual BOOL setGamma(const F32 gamma) = 0; // Set the gamma + virtual BOOL restoreGamma() = 0; // Restore original gamma table (before updating gamma) + virtual ESwapMethod getSwapMethod() { return mSwapMethod; } + virtual void gatherInput() = 0; + virtual void delayInputProcessing() = 0; + virtual void swapBuffers() = 0; + virtual void bringToFront() = 0; + virtual void focusClient() { }; // this may not have meaning or be required on other platforms, therefore, it's not abstract + + virtual S32 stat( const char* file_name, struct stat* stat_info ) = 0; + virtual BOOL sendEmail(const char* address,const char* subject,const char* body_text, const char* attachment=NULL, const char* attachment_displayed_name=NULL ) = 0; + + + // handy coordinate space conversion routines + // NB: screen to window and vice verse won't work on width/height coordinate pairs, + // as the conversion must take into account left AND right border widths, etc. + virtual BOOL convertCoords( LLCoordScreen from, LLCoordWindow *to) = 0; + virtual BOOL convertCoords( LLCoordWindow from, LLCoordScreen *to) = 0; + virtual BOOL convertCoords( LLCoordWindow from, LLCoordGL *to) = 0; + virtual BOOL convertCoords( LLCoordGL from, LLCoordWindow *to) = 0; + virtual BOOL convertCoords( LLCoordScreen from, LLCoordGL *to) = 0; + virtual BOOL convertCoords( LLCoordGL from, LLCoordScreen *to) = 0; + + // query supported resolutions + virtual LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) = 0; + virtual F32 getNativeAspectRatio() = 0; + virtual F32 getPixelAspectRatio() = 0; + virtual void setNativeAspectRatio(F32 aspect) = 0; + + void setCallbacks(LLWindowCallbacks *callbacks); + + virtual void beforeDialog() {}; // prepare to put up an OS dialog (if special measures are required, such as in fullscreen mode) + virtual void afterDialog() {}; // undo whatever was done in beforeDialog() + +// opens system default color picker + virtual BOOL dialog_color_picker (F32 *r, F32 *g, F32 *b) { return FALSE; }; + +// return a platform-specific window reference (HWND on Windows, WindowRef on the Mac) + virtual void *getPlatformWindow() = 0; + +protected: + LLWindow(BOOL fullscreen, U32 flags); + virtual ~LLWindow() {} + virtual BOOL isValid() {return TRUE;} + virtual BOOL canDelete() {return TRUE;} +protected: + static LLWindowCallbacks sDefaultCallbacks; + +protected: + LLWindowCallbacks* mCallbacks; + + BOOL mPostQuit; // should this window post a quit message when destroyed? + BOOL mFullscreen; + S32 mFullscreenWidth; + S32 mFullscreenHeight; + S32 mFullscreenBits; + S32 mFullscreenRefresh; + LLWindowResolution* mSupportedResolutions; + S32 mNumSupportedResolutions; + ECursorType mCurrentCursor; + BOOL mCursorHidden; + S32 mBusyCount; // how deep is the "cursor busy" stack? + BOOL mIsMouseClipping; // Is this window currently clipping the mouse + ESwapMethod mSwapMethod; + BOOL mHideCursorPermanent; + U32 mFlags; + + friend class LLWindowManager; +}; + + +// LLSplashScreen +// A simple, OS-specific splash screen that we can display +// while initializing the application and before creating a GL +// window + + +class LLSplashScreen +{ +public: + LLSplashScreen() { }; + virtual ~LLSplashScreen() { }; + + + // Call to display the window. + static LLSplashScreen * create(); + static void show(); + static void hide(); + static void update(const char* string); + + static bool isVisible(); +protected: + // These are overridden by the platform implementation + virtual void showImpl() = 0; + virtual void updateImpl(const char* string) = 0; + virtual void hideImpl() = 0; + + static BOOL sVisible; + +}; + +// Platform-neutral for accessing the platform specific message box +S32 OSMessageBox(const char* text, const char* caption, U32 type); +const U32 OSMB_OK = 0; +const U32 OSMB_OKCANCEL = 1; +const U32 OSMB_YESNO = 2; + +const S32 OSBTN_YES = 0; +const S32 OSBTN_NO = 1; +const S32 OSBTN_OK = 2; +const S32 OSBTN_CANCEL = 3; + +// +// LLWindowManager +// Manages window creation and error checking + +class LLWindowManager +{ +private: + static LLLinkedList sWindowList; + +public: + static LLWindow* createWindow( + char *title, + char *name, + LLCoordScreen upper_left = LLCoordScreen(10, 10), + LLCoordScreen size = LLCoordScreen(320, 240), + U32 flags = 0, + BOOL fullscreen = FALSE, + BOOL clearBg = FALSE, + BOOL disable_vsync = TRUE, + BOOL use_gl = TRUE, + BOOL ignore_pixel_depth = FALSE); + static LLWindow *createWindow( + char* title, char* name, S32 x, S32 y, S32 width, S32 height, + U32 flags = 0, + BOOL fullscreen = FALSE, + BOOL clearBg = FALSE, + BOOL disable_vsync = TRUE, + BOOL use_gl = TRUE, + BOOL ignore_pixel_depth = FALSE); + static BOOL destroyWindow(LLWindow* window); + static BOOL isWindowValid(LLWindow *window); +}; + +// +// helper funcs +// + +// Protocols, like "http" and "https" we support in URLs +extern const S32 gURLProtocolWhitelistCount; +extern const char* gURLProtocolWhitelist[]; +extern const char* gURLProtocolWhitelistHandler[]; + +// Loads a URL with the user's default browser +void spawn_web_browser(const char* escaped_url); + +// Opens a file with ShellExecute. Security risk! +void shell_open(const char* file_path); + +void simpleEscapeString ( std::string& stringIn ); + + +#if LL_WINDOWS + // return Win32 specific window handle + HWND llwindow_get_hwnd(LLWindow *window); + + // backdoor for special case handling of Win32 messages + void llwindow_install_wndproc(LLWindow *window, WNDPROC wnd_proc); +#endif + +#endif // _LL_window_h_ diff --git a/indra/llwindow/llwindowheadless.cpp b/indra/llwindow/llwindowheadless.cpp new file mode 100644 index 0000000000..1251ed1bb7 --- /dev/null +++ b/indra/llwindow/llwindowheadless.cpp @@ -0,0 +1,32 @@ +/** + * @file llwindowheadless.cpp + * @brief Headless implementation of LLWindow class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "indra_constants.h" + +#include "llwindowheadless.h" + +// +// LLWindowHeadless +// +LLWindowHeadless::LLWindowHeadless(char *title, char *name, S32 x, S32 y, S32 width, S32 height, + U32 flags, BOOL fullscreen, BOOL clearBg, + BOOL disable_vsync, BOOL use_gl, BOOL ignore_pixel_depth) + : LLWindow(fullscreen, flags) +{ +} + + +LLWindowHeadless::~LLWindowHeadless() +{ +} + +void LLWindowHeadless::swapBuffers() +{ +} + diff --git a/indra/llwindow/llwindowheadless.h b/indra/llwindow/llwindowheadless.h new file mode 100644 index 0000000000..7c9b2e2d77 --- /dev/null +++ b/indra/llwindow/llwindowheadless.h @@ -0,0 +1,98 @@ +/** + * @file llwindowheadless.h + * @brief Headless definition of LLWindow class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLWINDOWHEADLESS_H +#define LL_LLWINDOWHEADLESS_H + +#include "llwindow.h" + +class LLWindowHeadless : public LLWindow +{ +public: + /*virtual*/ void show() {}; + /*virtual*/ void hide() {}; + /*virtual*/ void close() {}; + /*virtual*/ BOOL getVisible() {return FALSE;}; + /*virtual*/ BOOL getMinimized() {return FALSE;}; + /*virtual*/ BOOL getMaximized() {return FALSE;}; + /*virtual*/ BOOL maximize() {return FALSE;}; + /*virtual*/ BOOL getFullscreen() {return FALSE;}; + /*virtual*/ BOOL getPosition(LLCoordScreen *position) {return FALSE;}; + /*virtual*/ BOOL getSize(LLCoordScreen *size) {return FALSE;}; + /*virtual*/ BOOL getSize(LLCoordWindow *size) {return FALSE;}; + /*virtual*/ BOOL setPosition(LLCoordScreen position) {return FALSE;}; + /*virtual*/ BOOL setSize(LLCoordScreen size) {return FALSE;}; + /*virtual*/ BOOL switchContext(BOOL fullscreen, LLCoordScreen size, BOOL disable_vsync) {return FALSE;}; + /*virtual*/ BOOL setCursorPosition(LLCoordWindow position) {return FALSE;}; + /*virtual*/ BOOL getCursorPosition(LLCoordWindow *position) {return FALSE;}; + /*virtual*/ void showCursor() {}; + /*virtual*/ void hideCursor() {}; + /*virtual*/ void showCursorFromMouseMove() {}; + /*virtual*/ void hideCursorUntilMouseMove() {}; + /*virtual*/ BOOL isCursorHidden() {return FALSE;}; + /*virtual*/ void setCursor(ECursorType cursor) {}; + //virtual ECursorType getCursor() { return mCurrentCursor; }; + /*virtual*/ void captureMouse() {}; + /*virtual*/ void releaseMouse() {}; + /*virtual*/ void setMouseClipping( BOOL b ) {}; + /*virtual*/ BOOL isClipboardTextAvailable() {return FALSE; }; + /*virtual*/ BOOL pasteTextFromClipboard(LLWString &dst) {return FALSE; }; + /*virtual*/ BOOL copyTextToClipboard(const LLWString &src) {return FALSE; }; + /*virtual*/ void flashIcon(F32 seconds) {}; + /*virtual*/ F32 getGamma() {return 1.0f; }; + /*virtual*/ BOOL setGamma(const F32 gamma) {return FALSE; }; // Set the gamma + /*virtual*/ BOOL restoreGamma() {return FALSE; }; // Restore original gamma table (before updating gamma) + //virtual ESwapMethod getSwapMethod() { return mSwapMethod; } + /*virtual*/ void gatherInput() {}; + /*virtual*/ void delayInputProcessing() {}; + /*virtual*/ void swapBuffers(); + + /*virtual*/ LLString getTempFileName() {return LLString(""); }; + /*virtual*/ void deleteFile( const char* file_name ) {}; + /*virtual*/ S32 stat( const char* file_name, struct stat* stat_info ) {return 0; }; + /*virtual*/ BOOL sendEmail(const char* address,const char* subject,const char* body_text,const char* attachment=NULL, const char* attachment_displayed_name=NULL) { return FALSE; }; + + + // handy coordinate space conversion routines + /*virtual*/ BOOL convertCoords(LLCoordScreen from, LLCoordWindow *to) { return FALSE; }; + /*virtual*/ BOOL convertCoords(LLCoordWindow from, LLCoordScreen *to) { return FALSE; }; + /*virtual*/ BOOL convertCoords(LLCoordWindow from, LLCoordGL *to) { return FALSE; }; + /*virtual*/ BOOL convertCoords(LLCoordGL from, LLCoordWindow *to) { return FALSE; }; + /*virtual*/ BOOL convertCoords(LLCoordScreen from, LLCoordGL *to) { return FALSE; }; + /*virtual*/ BOOL convertCoords(LLCoordGL from, LLCoordScreen *to) { return FALSE; }; + + /*virtual*/ LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) { return NULL; }; + /*virtual*/ F32 getNativeAspectRatio() { return 1.0f; }; + /*virtual*/ F32 getPixelAspectRatio() { return 1.0f; }; + /*virtual*/ void setNativeAspectRatio(F32 ratio) {} + + /*virtual*/ void *getPlatformWindow() { return 0; }; + /*virtual*/ void bringToFront() {}; + + LLWindowHeadless(char *title, char *name, S32 x, S32 y, S32 width, S32 height, + U32 flags, BOOL fullscreen, BOOL clearBg, + BOOL disable_vsync, BOOL use_gl, BOOL ignore_pixel_depth); + virtual ~LLWindowHeadless(); + +private: +}; + +class LLSplashScreenHeadless : public LLSplashScreen +{ +public: + LLSplashScreenHeadless() {}; + virtual ~LLSplashScreenHeadless() {}; + + /*virtual*/ void showImpl() {}; + /*virtual*/ void updateImpl(const char* mesg) {}; + /*virtual*/ void hideImpl() {}; + +}; + +#endif //LL_LLWINDOWHEADLESS_H + diff --git a/indra/llwindow/llwindowmacosx-objc.h b/indra/llwindow/llwindowmacosx-objc.h new file mode 100644 index 0000000000..86ac74ff1f --- /dev/null +++ b/indra/llwindow/llwindowmacosx-objc.h @@ -0,0 +1,19 @@ +/** + * @file llwindowmacosx-objc.h + * @brief Prototypes for functions shared between llwindowmacosx.cpp + * and llwindowmacosx-objc.mm. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + + +// This will actually hold an NSCursor*, but that type is only available in objective C. +typedef void *CursorRef; + +/* Defined in llwindowmacosx-objc.mm: */ +void setupCocoa(); +CursorRef createImageCursor(const char *fullpath, int hotspotX, int hotspotY); +OSErr releaseImageCursor(CursorRef ref); +OSErr setImageCursor(CursorRef ref); + diff --git a/indra/llwindow/llwindowmacosx-objc.mm b/indra/llwindow/llwindowmacosx-objc.mm new file mode 100644 index 0000000000..82d2babab5 --- /dev/null +++ b/indra/llwindow/llwindowmacosx-objc.mm @@ -0,0 +1,87 @@ +/** + * @file llwindowmacosx-objc.mm + * @brief Definition of functions shared between llwindowmacosx.cpp + * and llwindowmacosx-objc.mm. + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include + +/* + * These functions are broken out into a separate file because the + * objective-C typedef for 'BOOL' conflicts with the one in + * llcommon/stdtypes.h. This makes it impossible to use the standard + * linden headers with any objective-C++ source. + */ + +#include "llwindowmacosx-objc.h" + +void setupCocoa() +{ + static bool inited = false; + + if(!inited) + { + // This is a bit of voodoo taken from the Apple sample code "CarbonCocoa_PictureCursor": + // http://developer.apple.com/samplecode/CarbonCocoa_PictureCursor/index.html + + // Needed for Carbon based applications which call into Cocoa + NSApplicationLoad(); + + // Must first call [[[NSWindow alloc] init] release] to get the NSWindow machinery set up so that NSCursor can use a window to cache the cursor image + [[[NSWindow alloc] init] release]; + } +} + +CursorRef createImageCursor(const char *fullpath, int hotspotX, int hotspotY) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + // extra retain on the NSCursor since we want it to live for the lifetime of the app. + NSCursor *cursor = + [[[NSCursor alloc] + initWithImage: + [[[NSImage alloc] initWithContentsOfFile: + [NSString stringWithFormat:@"%s", fullpath] + ]autorelease] + hotSpot:NSMakePoint(hotspotX, hotspotY) + ]retain]; + + [pool release]; + + return (CursorRef)cursor; +} + +// This is currently unused, since we want all our cursors to persist for the life of the app, but I've included it for completeness. +OSErr releaseImageCursor(CursorRef ref) +{ + if( ref != NULL ) + { + NSCursor *cursor = (NSCursor*)ref; + [cursor release]; + } + else + { + return paramErr; + } + + return noErr; +} + +OSErr setImageCursor(CursorRef ref) +{ + if( ref != NULL ) + { + NSCursor *cursor = (NSCursor*)ref; + [cursor set]; + } + else + { + return paramErr; + } + + return noErr; +} + diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp new file mode 100644 index 0000000000..4d75a30a8e --- /dev/null +++ b/indra/llwindow/llwindowmacosx.cpp @@ -0,0 +1,2904 @@ +/** + * @file llwindowmacosx.cpp + * @brief Platform-dependent implementation of llwindow + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#if LL_DARWIN + +#include "linden_common.h" + +#include + +#include "llwindowmacosx.h" +#include "llkeyboardmacosx.h" +#include "llerror.h" +#include "llgl.h" +#include "llstring.h" +#include "lldir.h" + +#include "llglheaders.h" + +#include "indra_constants.h" + +#include "llwindowmacosx-objc.h" + +extern BOOL gDebugWindowProc; + +// culled from winuser.h +//const S32 WHEEL_DELTA = 120; /* Value for rolling one detent */ +// On the Mac, the scroll wheel reports a delta of 1 for each detent. +// There's also acceleration for faster scrolling, based on a slider in the system preferences. +const S32 WHEEL_DELTA = 1; /* Value for rolling one detent */ +const S32 BITS_PER_PIXEL = 32; +const S32 MAX_NUM_RESOLUTIONS = 32; + + +// +// LLWindowMacOSX +// + +// Cross-platform bits: + +void show_window_creation_error(const char* title) +{ + llwarns << title << llendl; + shell_open( "help/window_creation_error.html"); + /* + OSMessageBox( + "Second Life is unable to run because it can't set up your display.\n" + "We need to be able to make a 32-bit color window at 1024x768, with\n" + "an 8 bit alpha channel.\n" + "\n" + "First, be sure your monitor is set to True Color (32-bit) in\n" + "Start -> Control Panels -> Display -> Settings.\n" + "\n" + "Otherwise, this may be due to video card driver issues.\n" + "Please make sure you have the latest video card drivers installed.\n" + "ATI drivers are available at http://www.ati.com/\n" + "nVidia drivers are available at http://www.nvidia.com/\n" + "\n" + "If you continue to receive this message, contact customer service.", + title, + OSMB_OK); + */ +} + +BOOL check_for_card(const char* RENDERER, const char* bad_card) +{ + if (!strnicmp(RENDERER, bad_card, strlen(bad_card))) + { + char buffer[1024]; + sprintf(buffer, + "Your video card appears to be a %s, which Second Life does not support.\n" + "\n" + "Second Life requires a video card with 32 Mb of memory or more, as well as\n" + "multitexture support. We explicitly support nVidia GeForce 2 or better, \n" + "and ATI Radeon 8500 or better.\n" + "\n" + "If you own a supported card and continue to receive this message, try \n" + "updating to the latest video card drivers. Otherwise look in the\n" + "secondlife.com support section or e-mail technical support\n" + "\n" + "You can try to run Second Life, but it will probably crash or run\n" + "very slowly. Try anyway?", + bad_card); + S32 button = OSMessageBox(buffer, "Unsupported video card", OSMB_YESNO); + if (OSBTN_YES == button) + { + return FALSE; + } + else + { + return TRUE; + } + } + + return FALSE; +} + + + +// Switch to determine whether we capture all displays, or just the main one. +// We may want to base this on the setting of _DEBUG... + +#define CAPTURE_ALL_DISPLAYS 0 +static double getDictDouble (CFDictionaryRef refDict, CFStringRef key); +static long getDictLong (CFDictionaryRef refDict, CFStringRef key); + + + + +// CarbonEvents we're interested in. +static EventTypeSpec WindowHandlerEventList[] = +{ + // Window-related events + // { kEventClassWindow, kEventWindowCollapsing }, + // { kEventClassWindow, kEventWindowCollapsed }, + // { kEventClassWindow, kEventWindowShown }, + { kEventClassWindow, kEventWindowActivated }, + { kEventClassWindow, kEventWindowDeactivated }, + { kEventClassWindow, kEventWindowShown }, + { kEventClassWindow, kEventWindowHidden }, + { kEventClassWindow, kEventWindowCollapsed }, + { kEventClassWindow, kEventWindowExpanded }, + { kEventClassWindow, kEventWindowGetClickActivation }, + { kEventClassWindow, kEventWindowClose }, + { kEventClassWindow, kEventWindowBoundsChanging }, + { kEventClassWindow, kEventWindowBoundsChanged }, + // { kEventClassWindow, kEventWindowZoomed }, + // { kEventClassWindow, kEventWindowDrawContent }, + + // Mouse events + { kEventClassMouse, kEventMouseDown }, + { kEventClassMouse, kEventMouseUp }, + { kEventClassMouse, kEventMouseDragged }, + { kEventClassMouse, kEventMouseWheelMoved }, + { kEventClassMouse, kEventMouseMoved }, + + // Keyboard events + // No longer handle raw key down events directly. + // When text input events come in, extract the raw key events from them and process at that point. + // This allows input methods to eat keystrokes the way they're supposed to. +// { kEventClassKeyboard, kEventRawKeyDown }, +// { kEventClassKeyboard, kEventRawKeyRepeat }, + { kEventClassKeyboard, kEventRawKeyUp }, + { kEventClassKeyboard, kEventRawKeyModifiersChanged }, + + // Text input events + { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent } + +}; + +static EventTypeSpec GlobalHandlerEventList[] = +{ + // Mouse events + { kEventClassMouse, kEventMouseDown }, + { kEventClassMouse, kEventMouseUp }, + { kEventClassMouse, kEventMouseDragged }, + { kEventClassMouse, kEventMouseWheelMoved }, + { kEventClassMouse, kEventMouseMoved }, + + // Keyboard events + // No longer handle raw key down events directly. + // When text input events come in, extract the raw key events from them and process at that point. + // This allows input methods to eat keystrokes the way they're supposed to. +// { kEventClassKeyboard, kEventRawKeyDown }, +// { kEventClassKeyboard, kEventRawKeyRepeat }, + { kEventClassKeyboard, kEventRawKeyUp }, + { kEventClassKeyboard, kEventRawKeyModifiersChanged }, + + // Text input events + { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent } +}; + +static EventTypeSpec CommandHandlerEventList[] = +{ + { kEventClassCommand, kEventCommandProcess } +}; + +// MBW -- HACK ALERT +// On the Mac, to put up an OS dialog in full screen mode, we must first switch OUT of full screen mode. +// The proper way to do this is to bracket the dialog with calls to beforeDialog() and afterDialog(), but these +// require a pointer to the LLWindowMacOSX object. Stash it here and maintain in the constructor and destructor. +// This assumes that there will be only one object of this class at any time. Hopefully this is true. +static LLWindowMacOSX *gWindowImplementation = NULL; + + + +LLWindowMacOSX::LLWindowMacOSX(char *title, char *name, S32 x, S32 y, S32 width, + S32 height, U32 flags, + BOOL fullscreen, BOOL clearBg, + BOOL disable_vsync, BOOL use_gl, + BOOL ignore_pixel_depth) + : LLWindow(fullscreen, flags) +{ + // Voodoo for calling cocoa from carbon (see llwindowmacosx-objc.mm). + setupCocoa(); + + // Initialize the keyboard + gKeyboard = new LLKeyboardMacOSX(); + + // Ignore use_gl for now, only used for drones on PC + mWindow = NULL; + mContext = NULL; + mPixelFormat = NULL; + mDisplay = CGMainDisplayID(); + mOldDisplayMode = NULL; + mTimer = NULL; + mSimulatedRightClick = FALSE; + mLastModifiers = 0; + mHandsOffEvents = FALSE; + mCursorDecoupled = FALSE; + mCursorLastEventDeltaX = 0; + mCursorLastEventDeltaY = 0; + mCursorIgnoreNextDelta = FALSE; + mNeedsResize = FALSE; + mOverrideAspectRatio = 0.f; + mMinimized = FALSE; + + // For reasons that aren't clear to me, LLTimers seem to be created in the "started" state. + // Since the started state of this one is used to track whether the NMRec has been installed, it wants to start out in the "stopped" state. + mBounceTimer.stop(); + + // Get the original aspect ratio of the main device. + mOriginalAspectRatio = (double)CGDisplayPixelsWide(mDisplay) / (double)CGDisplayPixelsHigh(mDisplay); + + // Stash the window title + strcpy((char*)mWindowTitle + 1, title); + mWindowTitle[0] = strlen(title); + + mEventHandlerUPP = NewEventHandlerUPP(staticEventHandler); + mGlobalHandlerRef = NULL; + mWindowHandlerRef = NULL; + + // We're not clipping yet + SetRect( &mOldMouseClip, 0, 0, 0, 0 ); + + // Set up global event handlers (the fullscreen case needs this) + InstallStandardEventHandler(GetApplicationEventTarget()); + + // Stash an object pointer for OSMessageBox() + gWindowImplementation = this; + + // Create the GL context and set it up for windowed or fullscreen, as appropriate. + if(createContext(x, y, width, height, 32, fullscreen, disable_vsync)) + { + if(mWindow != NULL) + { + // MBW -- XXX -- I think we can now do this here? + // Constrain the window to the screen it's mostly on, resizing if necessary. + ConstrainWindowToScreen( + mWindow, + kWindowStructureRgn, + kWindowConstrainMayResize | + // kWindowConstrainStandardOptions | + 0, + NULL, + NULL); + + MacShowWindow(mWindow); + BringToFront(mWindow); + } + + if (!gGLManager.initGL()) + { + setupFailure( + "Second Life is unable to run because your video card drivers\n" + "are out of date or unsupported. Please make sure you have\n" + "the latest video card drivers installed.\n" + "If you continue to receive this message, contact customer service.", + "Error", + OSMB_OK); + return; + } + + //start with arrow cursor + initCursors(); + setCursor( UI_CURSOR_ARROW ); + } + + stop_glerror(); +} + +BOOL LLWindowMacOSX::createContext(int x, int y, int width, int height, int bits, BOOL fullscreen, BOOL disable_vsync) +{ + OSStatus err; + BOOL glNeedsInit = FALSE; + + if(mGlobalHandlerRef == NULL) + { + InstallApplicationEventHandler(mEventHandlerUPP, GetEventTypeCount (CommandHandlerEventList), CommandHandlerEventList, (void*)this, &mGlobalHandlerRef); + } + + mFullscreen = fullscreen; + + if (mFullscreen && (mOldDisplayMode == NULL)) + { + llinfos << "createContext: setting up fullscreen " << width << "x" << height << llendl; + + // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays. Plan accordingly. + double refresh = getDictDouble (CGDisplayCurrentMode (mDisplay), kCGDisplayRefreshRate); + + // If the requested width or height is 0, find the best default for the monitor. + if((width == 0) || (height == 0)) + { + // Scan through the list of modes, looking for one which has: + // height between 700 and 800 + // aspect ratio closest to the user's original mode + S32 resolutionCount = 0; + LLWindowResolution *resolutionList = getSupportedResolutions(resolutionCount); + + if(resolutionList != NULL) + { + F32 closestAspect = 0; + U32 closestHeight = 0; + U32 closestWidth = 0; + int i; + + llinfos << "createContext: searching for a display mode, original aspect is " << mOriginalAspectRatio << llendl; + + for(i=0; i < resolutionCount; i++) + { + F32 aspect = (F32)resolutionList[i].mWidth / (F32)resolutionList[i].mHeight; + + llinfos << "createContext: width " << resolutionList[i].mWidth << " height " << resolutionList[i].mHeight << " aspect " << aspect << llendl; + + if( (resolutionList[i].mHeight >= 700) && (resolutionList[i].mHeight <= 800) && + (fabs(aspect - mOriginalAspectRatio) < fabs(closestAspect - mOriginalAspectRatio))) + { + llinfos << " (new closest mode) " << llendl; + + // This is the closest mode we've seen yet. + closestWidth = resolutionList[i].mWidth; + closestHeight = resolutionList[i].mHeight; + closestAspect = aspect; + } + } + + width = closestWidth; + height = closestHeight; + } + } + + if((width == 0) || (height == 0)) + { + // Mode search failed for some reason. Use the old-school default. + width = 1024; + height = 768; + } + + if (true) + { + // Fullscreen support + CFDictionaryRef refDisplayMode = 0; + boolean_t exactMatch = false; + +#if CAPTURE_ALL_DISPLAYS + // Capture all displays (may want to do this for final build) + CGCaptureAllDisplays (); +#else + // Capture only the main display (useful for debugging) + CGDisplayCapture (mDisplay); +#endif + + // Switch the display to the desired resolution and refresh + refDisplayMode = CGDisplayBestModeForParametersAndRefreshRate( + mDisplay, + BITS_PER_PIXEL, + width, + height, + refresh, + &exactMatch); + + if (refDisplayMode) + { + llinfos << "createContext: switching display resolution" << llendl; + mOldDisplayMode = CGDisplayCurrentMode (mDisplay); + CGDisplaySwitchToMode (mDisplay, refDisplayMode); + // CFRelease(refDisplayMode); + + AddEventTypesToHandler(mGlobalHandlerRef, GetEventTypeCount (GlobalHandlerEventList), GlobalHandlerEventList); + } + + + mFullscreen = TRUE; + mFullscreenWidth = CGDisplayPixelsWide(mDisplay); + mFullscreenHeight = CGDisplayPixelsHigh(mDisplay); + mFullscreenBits = CGDisplayBitsPerPixel(mDisplay); + mFullscreenRefresh = llround(getDictDouble (CGDisplayCurrentMode (mDisplay), kCGDisplayRefreshRate)); + + llinfos << "Running at " << mFullscreenWidth + << "x" << mFullscreenHeight + << "x" << mFullscreenBits + << " @ " << mFullscreenRefresh + << llendl; + } + else + { + // No fullscreen support + mFullscreen = FALSE; + mFullscreenWidth = -1; + mFullscreenHeight = -1; + mFullscreenBits = -1; + mFullscreenRefresh = -1; + + char error[256]; + sprintf(error, "Unable to run fullscreen at %d x %d.\nRunning in window.", width, height); + OSMessageBox(error, "Error", OSMB_OK); + } + } + + if(!mFullscreen && (mWindow == NULL)) + { + Rect window_rect; + //int displayWidth = CGDisplayPixelsWide(mDisplay); + //int displayHeight = CGDisplayPixelsHigh(mDisplay); + //const int menuBarPlusTitleBar = 44; // Ugly magic number. + + llinfos << "createContext: creating window" << llendl; + + window_rect.left = (long) x; + window_rect.right = (long) x + width; + window_rect.top = (long) y; + window_rect.bottom = (long) y + height; + + //----------------------------------------------------------------------- + // Create the window + //----------------------------------------------------------------------- + mWindow = NewCWindow( + NULL, + &window_rect, + mWindowTitle, + false, // Create the window invisible. Whoever calls createContext() should show it after any moving/resizing. + // noGrowDocProc, // Window with no grow box and no zoom box + zoomDocProc, // Window with a grow box and a zoom box + // zoomNoGrow, // Window with a zoom box but no grow box + kFirstWindowOfClass, + true, + (long)this); + + + if (!mWindow) + { + setupFailure("Window creation error", "Error", OSMB_OK); + return FALSE; + } + + // Turn on live resize. + // For this to work correctly, we need to be able to call LLViewerWindow::draw from + // the event handler for kEventWindowBoundsChanged. It's not clear that we have access from here. + // err = ChangeWindowAttributes(mWindow, kWindowLiveResizeAttribute, 0); + + // Set up window event handlers (some window-related events ONLY go to window handlers.) + InstallStandardEventHandler(GetWindowEventTarget(mWindow)); + InstallWindowEventHandler (mWindow, mEventHandlerUPP, GetEventTypeCount (WindowHandlerEventList), WindowHandlerEventList, (void*)this, &mWindowHandlerRef); // add event handler + + } + + if(mContext == NULL) + { + AGLRendererInfo rendererInfo = NULL; + + //----------------------------------------------------------------------- + // Create GL drawing context + //----------------------------------------------------------------------- + + if(mPixelFormat == NULL) + { + if(mFullscreen) + { + GLint fullscreenAttrib[] = + { + AGL_RGBA, + AGL_FULLSCREEN, + // AGL_NO_RECOVERY, // MBW -- XXX -- Not sure if we want this attribute + AGL_DOUBLEBUFFER, + AGL_CLOSEST_POLICY, + AGL_ACCELERATED, + AGL_RED_SIZE, 8, + AGL_GREEN_SIZE, 8, + AGL_BLUE_SIZE, 8, + AGL_ALPHA_SIZE, 8, + AGL_DEPTH_SIZE, 24, + AGL_STENCIL_SIZE, 8, + AGL_NONE + }; + + llinfos << "createContext: creating fullscreen pixelformat" << llendl; + + GDHandle gdhDisplay = NULL; + err = DMGetGDeviceByDisplayID ((DisplayIDType)mDisplay, &gdhDisplay, false); + + mPixelFormat = aglChoosePixelFormat(&gdhDisplay, 1, fullscreenAttrib); + rendererInfo = aglQueryRendererInfo(&gdhDisplay, 1); + } + else + { + GLint windowedAttrib[] = + { + AGL_RGBA, + AGL_DOUBLEBUFFER, + AGL_CLOSEST_POLICY, + AGL_ACCELERATED, + AGL_RED_SIZE, 8, + AGL_GREEN_SIZE, 8, + AGL_BLUE_SIZE, 8, + AGL_ALPHA_SIZE, 8, + AGL_DEPTH_SIZE, 24, + AGL_STENCIL_SIZE, 8, + AGL_NONE + }; + + llinfos << "createContext: creating windowed pixelformat" << llendl; + + mPixelFormat = aglChoosePixelFormat(NULL, 0, windowedAttrib); + + GDHandle gdhDisplay = GetMainDevice(); + rendererInfo = aglQueryRendererInfo(&gdhDisplay, 1); + } + + // May want to get the real error text like this: + // (char *) aglErrorString(aglGetError()); + + if(aglGetError() != AGL_NO_ERROR) + { + setupFailure("Can't find suitable pixel format", "Error", OSMB_OK); + return FALSE; + } + } + + if(mPixelFormat) + { + llinfos << "createContext: creating GL context" << llendl; + mContext = aglCreateContext(mPixelFormat, NULL); + } + + if(mContext == NULL) + { + setupFailure("Can't make GL context", "Error", OSMB_OK); + return FALSE; + } + + gGLManager.mVRAM = 0; + + if(rendererInfo != NULL) + { + GLint result; + + if(aglDescribeRenderer(rendererInfo, AGL_VIDEO_MEMORY, &result)) + { + // llinfos << "createContext: aglDescribeRenderer(AGL_VIDEO_MEMORY) returned " << result << llendl; + gGLManager.mVRAM = result / (1024 * 1024); + } + else + { + // llinfos << "createContext: aglDescribeRenderer(AGL_VIDEO_MEMORY) failed." << llendl; + } + + // This could be useful at some point, if it takes into account the memory already used by screen buffers, etc... + if(aglDescribeRenderer(rendererInfo, AGL_TEXTURE_MEMORY, &result)) + { + // llinfos << "createContext: aglDescribeRenderer(AGL_TEXTURE_MEMORY) returned " << result << llendl; + } + else + { + // llinfos << "createContext: aglDescribeRenderer(AGL_TEXTURE_MEMORY) failed." << llendl; + } + + aglDestroyRendererInfo(rendererInfo); + } + + // Since we just created the context, it needs to be set up. + glNeedsInit = TRUE; + } + + // Hook up the context to a drawable + if (mFullscreen && (mOldDisplayMode != NULL)) + { + // We successfully captured the display. Use a fullscreen drawable + + llinfos << "createContext: attaching fullscreen drawable" << llendl; + +#if CAPTURE_ALL_DISPLAYS + // Capture all displays (may want to do this for final build) + aglDisable (mContext, AGL_FS_CAPTURE_SINGLE); +#else + // Capture only the main display (useful for debugging) + aglEnable (mContext, AGL_FS_CAPTURE_SINGLE); +#endif + + if (!aglSetFullScreen (mContext, 0, 0, 0, 0)) + { + setupFailure("Can't set GL fullscreen", "Error", OSMB_OK); + return FALSE; + } + } + else if(!mFullscreen && (mWindow != NULL)) + { + llinfos << "createContext: attaching windowed drawable" << llendl; + + // We created a window. Use it as the drawable. + if(!aglSetDrawable(mContext, GetWindowPort (mWindow))) + { + setupFailure("Can't set GL drawable", "Error", OSMB_OK); + return FALSE; + } + } + else + { + setupFailure("Can't get fullscreen or windowed drawable.", "Error", OSMB_OK); + return FALSE; + } + + if(mContext != NULL) + { + llinfos << "createContext: setting current context" << llendl; + + if (!aglSetCurrentContext(mContext)) + { + setupFailure("Can't activate GL rendering context", "Error", OSMB_OK); + return FALSE; + } + } + + if(glNeedsInit) + { + // Check for some explicitly unsupported cards. + const char* RENDERER = (const char*) glGetString(GL_RENDERER); + + const char* CARD_LIST[] = + { "RAGE 128", + "RIVA TNT2", + "Intel 810", + "3Dfx/Voodoo3", + "Radeon 7000", + "Radeon 7200", + "Radeon 7500", + "Radeon DDR", + "Radeon VE", + "GDI Generic" }; + const S32 CARD_COUNT = sizeof(CARD_LIST)/sizeof(char*); + + // Future candidates: + // ProSavage/Twister + // SuperSavage + + S32 i; + for (i = 0; i < CARD_COUNT; i++) + { + if (check_for_card(RENDERER, CARD_LIST[i])) + { + close(); + shell_open( "help/unsupported_card.html" ); + return FALSE; + } + } + } + + GLint colorBits, alphaBits, depthBits, stencilBits; + + if( !aglDescribePixelFormat(mPixelFormat, AGL_BUFFER_SIZE, &colorBits) || + !aglDescribePixelFormat(mPixelFormat, AGL_ALPHA_SIZE, &alphaBits) || + !aglDescribePixelFormat(mPixelFormat, AGL_DEPTH_SIZE, &depthBits) || + !aglDescribePixelFormat(mPixelFormat, AGL_STENCIL_SIZE, &stencilBits)) + { + close(); + setupFailure("Can't get pixel format description", "Error", OSMB_OK); + return FALSE; + } + + llinfos << "GL buffer: Color Bits " << S32(colorBits) + << " Alpha Bits " << S32(alphaBits) + << " Depth Bits " << S32(depthBits) + << " Stencil Bits" << S32(stencilBits) + << llendl; + + if (colorBits < 32) + { + close(); + setupFailure( + "Second Life requires True Color (32-bit) to run in a window.\n" + "Please go to Control Panels -> Display -> Settings and\n" + "set the screen to 32-bit color.\n" + "Alternately, if you choose to run fullscreen, Second Life\n" + "will automatically adjust the screen each time it runs.", + "Error", + OSMB_OK); + return FALSE; + } + + if (alphaBits < 8) + { + close(); + setupFailure( + "Second Life is unable to run because it can't get an 8 bit alpha\n" + "channel. Usually this is due to video card driver issues.\n" + "Please make sure you have the latest video card drivers installed.\n" + "Also be sure your monitor is set to True Color (32-bit) in\n" + "Control Panels -> Display -> Settings.\n" + "If you continue to receive this message, contact customer service.", + "Error", + OSMB_OK); + return FALSE; + } + + // Disable vertical sync for swap + GLint frames_per_swap = 0; + if (disable_vsync) + { + llinfos << "Disabling vertical sync" << llendl; + frames_per_swap = 0; + } + else + { + llinfos << "Keeping vertical sync" << llendl; + frames_per_swap = 1; + } + aglSetInteger(mContext, AGL_SWAP_INTERVAL, &frames_per_swap); + + // Don't need to get the current gamma, since there's a call that restores it to the system defaults. + return TRUE; +} + + +// changing fullscreen resolution, or switching between windowed and fullscreen mode. +BOOL LLWindowMacOSX::switchContext(BOOL fullscreen, LLCoordScreen size, BOOL disable_vsync) +{ + BOOL needsRebuild = FALSE; + BOOL result = true; + + if(fullscreen) + { + if(mFullscreen) + { + // Switching resolutions in fullscreen mode. Don't need to rebuild for this. + // Fullscreen support + CFDictionaryRef refDisplayMode = 0; + boolean_t exactMatch = false; + + // Switch the display to the desired resolution and refresh + refDisplayMode = CGDisplayBestModeForParametersAndRefreshRate( + mDisplay, + BITS_PER_PIXEL, + size.mX, + size.mY, + getDictDouble (CGDisplayCurrentMode (mDisplay), kCGDisplayRefreshRate), + &exactMatch); + + if (refDisplayMode) + { + CGDisplaySwitchToMode (mDisplay, refDisplayMode); + // CFRelease(refDisplayMode); + } + + mFullscreenWidth = CGDisplayPixelsWide(mDisplay); + mFullscreenHeight = CGDisplayPixelsHigh(mDisplay); + mFullscreenBits = CGDisplayBitsPerPixel(mDisplay); + mFullscreenRefresh = llround(getDictDouble (CGDisplayCurrentMode (mDisplay), kCGDisplayRefreshRate)); + + llinfos << "Switched resolution to " << mFullscreenWidth + << "x" << mFullscreenHeight + << "x" << mFullscreenBits + << " @ " << mFullscreenRefresh + << llendl; + + // Update the GL context to the new screen size + if (!aglUpdateContext(mContext)) + { + setupFailure("Can't set GL fullscreen", "Error", OSMB_OK); + result = FALSE; + } + } + else + { + // Switching from windowed to fullscreen + needsRebuild = TRUE; + } + } + else + { + if(mFullscreen) + { + // Switching from fullscreen to windowed + needsRebuild = TRUE; + } + else + { + // Windowed to windowed -- not sure why we would be called like this. Just change the window size. + // The bounds changed event handler will do the rest. + if(mWindow != NULL) + { + ::SizeWindow(mWindow, size.mX, size.mY, true); + } + } + } + + stop_glerror(); + if(needsRebuild) + { + destroyContext(); + result = createContext(0, 0, size.mX, size.mY, 0, fullscreen, disable_vsync); + if (result) + { + if(mWindow != NULL) + { + MacShowWindow(mWindow); + BringToFront(mWindow); + } + + llverify(gGLManager.initGL()); + + //start with arrow cursor + initCursors(); + setCursor( UI_CURSOR_ARROW ); + } + } + + stop_glerror(); + + return result; +} + +void LLWindowMacOSX::destroyContext() +{ + if (!mContext) + { + // We don't have a context + return; + } + // Unhook the GL context from any drawable it may have + if(mContext != NULL) + { + llinfos << "destroyContext: unhooking drawable " << llendl; + + aglSetCurrentContext (NULL); + aglSetDrawable(mContext, NULL); + } + + // Make sure the display resolution gets restored + if(mOldDisplayMode != NULL) + { + llinfos << "destroyContext: restoring display resolution " << llendl; + + CGDisplaySwitchToMode (mDisplay, mOldDisplayMode); + +#if CAPTURE_ALL_DISPLAYS + // Uncapture all displays (may want to do this for final build) + CGReleaseAllDisplays (); +#else + // Uncapture only the main display (useful for debugging) + CGDisplayRelease (mDisplay); +#endif + + // CFRelease(mOldDisplayMode); + + mOldDisplayMode = NULL; + + // Remove the global event handlers the fullscreen case needed + RemoveEventTypesFromHandler(mGlobalHandlerRef, GetEventTypeCount (GlobalHandlerEventList), GlobalHandlerEventList); + } + + // Clean up remaining GL state before blowing away window + gGLManager.shutdownGL(); + + // Clean up the pixel format + if(mPixelFormat != NULL) + { + llinfos << "destroyContext: destroying pixel format " << llendl; + aglDestroyPixelFormat(mPixelFormat); + mPixelFormat = NULL; + } + + // Remove any Carbon Event handlers we installed + if(mGlobalHandlerRef != NULL) + { + llinfos << "destroyContext: removing global event handler" << llendl; + RemoveEventHandler(mGlobalHandlerRef); + mGlobalHandlerRef = NULL; + } + + if(mWindowHandlerRef != NULL) + { + llinfos << "destroyContext: removing window event handler" << llendl; + RemoveEventHandler(mWindowHandlerRef); + mWindowHandlerRef = NULL; + } + + // Close the window + if(mWindow != NULL) + { + llinfos << "destroyContext: disposing window" << llendl; + DisposeWindow(mWindow); + mWindow = NULL; + } + + // Clean up the GL context + if(mContext != NULL) + { + llinfos << "destroyContext: destroying GL context" << llendl; + aglDestroyContext(mContext); + mContext = NULL; + } + +} + +LLWindowMacOSX::~LLWindowMacOSX() +{ + destroyContext(); + + if(mSupportedResolutions != NULL) + { + delete []mSupportedResolutions; + } + + gWindowImplementation = NULL; + +} + + +void LLWindowMacOSX::show() +{ + if(IsWindowCollapsed(mWindow)) + CollapseWindow(mWindow, false); + + MacShowWindow(mWindow); + BringToFront(mWindow); +} + +void LLWindowMacOSX::hide() +{ + setMouseClipping(FALSE); + HideWindow(mWindow); +} + +void LLWindowMacOSX::minimize() +{ + setMouseClipping(FALSE); + showCursor(); + CollapseWindow(mWindow, true); +} + +void LLWindowMacOSX::restore() +{ + show(); +} + + +// close() destroys all OS-specific code associated with a window. +// Usually called from LLWindowManager::destroyWindow() +void LLWindowMacOSX::close() +{ + // Is window is already closed? + // if (!mWindow) + // { + // return; + // } + + // Make sure cursor is visible and we haven't mangled the clipping state. + setMouseClipping(FALSE); + showCursor(); + + destroyContext(); +} + +BOOL LLWindowMacOSX::isValid() +{ + if(mFullscreen) + { + return(TRUE); + } + + return (mWindow != NULL); +} + +BOOL LLWindowMacOSX::getVisible() +{ + BOOL result = FALSE; + + if(mFullscreen) + { + result = TRUE; + }if (mWindow) + { + if(MacIsWindowVisible(mWindow)) + result = TRUE; + } + + return(result); +} + +BOOL LLWindowMacOSX::getMinimized() +{ + BOOL result = FALSE; + + // Since the set of states where we want to act "minimized" is non-trivial, it's easier to + // track things locally than to try and retrieve the state from the window manager. + result = mMinimized; + + return(result); +} + +BOOL LLWindowMacOSX::getMaximized() +{ + BOOL result = FALSE; + + if (mWindow) + { + // TODO + } + + return(result); +} + +BOOL LLWindowMacOSX::maximize() +{ + // TODO + return FALSE; +} + +BOOL LLWindowMacOSX::getFullscreen() +{ + return mFullscreen; +} + +void LLWindowMacOSX::gatherInput() +{ + // stop bouncing icon after fixed period of time + if (mBounceTimer.getStarted() && mBounceTimer.getElapsedTimeF32() > mBounceTime) + { + stopDockTileBounce(); + } + + // Use the old-school version so we get AppleEvent handler dispatch and menuselect handling. + // Anything that has an event handler will get processed inside WaitNextEvent, so we only need to handle + // the odd stuff here. + EventRecord evt; + while(WaitNextEvent(everyEvent, &evt, 0, NULL)) + { + // printf("WaitNextEvent returned true, event is %d.\n", evt.what); + switch(evt.what) + { + case mouseDown: + { + short part; + WindowRef window; + long selectResult; + part = FindWindow(evt.where, &window); + switch ( part ) + { + case inMenuBar: + selectResult = MenuSelect(evt.where); + + HiliteMenu(0); + break; + } + } + break; + + case kHighLevelEvent: + AEProcessAppleEvent (&evt); + break; + + case updateEvt: + // We shouldn't be getting these regularly (since our window will be buffered), but we need to handle them correctly... + BeginUpdate((WindowRef)evt.message); + EndUpdate((WindowRef)evt.message); + break; + + } + } +} + +BOOL LLWindowMacOSX::getPosition(LLCoordScreen *position) +{ + Rect window_rect; + OSStatus err = -1; + + if(mFullscreen) + { + position->mX = 0; + position->mY = 0; + err = noErr; + } + else if(mWindow) + { + err = GetWindowBounds(mWindow, kWindowContentRgn, &window_rect); + + position->mX = window_rect.left; + position->mY = window_rect.top; + } + else + { + llerrs << "LLWindowMacOSX::getPosition(): no window and not fullscreen!" << llendl; + } + + return (err == noErr); +} + +BOOL LLWindowMacOSX::getSize(LLCoordScreen *size) +{ + Rect window_rect; + OSStatus err = -1; + + if(mFullscreen) + { + size->mX = mFullscreenWidth; + size->mY = mFullscreenHeight; + err = noErr; + } + else if(mWindow) + { + err = GetWindowBounds(mWindow, kWindowContentRgn, &window_rect); + + size->mX = window_rect.right - window_rect.left; + size->mY = window_rect.bottom - window_rect.top; + } + else + { + llerrs << "LLWindowMacOSX::getPosition(): no window and not fullscreen!" << llendl; + } + + return (err == noErr); +} + +BOOL LLWindowMacOSX::getSize(LLCoordWindow *size) +{ + Rect window_rect; + OSStatus err = -1; + + if(mFullscreen) + { + size->mX = mFullscreenWidth; + size->mY = mFullscreenHeight; + err = noErr; + } + else if(mWindow) + { + err = GetWindowBounds(mWindow, kWindowContentRgn, &window_rect); + + size->mX = window_rect.right - window_rect.left; + size->mY = window_rect.bottom - window_rect.top; + } + else + { + llerrs << "LLWindowMacOSX::getPosition(): no window and not fullscreen!" << llendl; + } + + return (err == noErr); +} + +BOOL LLWindowMacOSX::setPosition(const LLCoordScreen position) +{ + if(mWindow) + { + MacMoveWindow(mWindow, position.mX, position.mY, false); + } + + return TRUE; +} + +BOOL LLWindowMacOSX::setSize(const LLCoordScreen size) +{ + if(mWindow) + { + SizeWindow(mWindow, size.mX, size.mY, true); + } + + return TRUE; +} + +void LLWindowMacOSX::swapBuffers() +{ + aglSwapBuffers(mContext); +} + +F32 LLWindowMacOSX::getGamma() +{ + F32 result = 1.8; // Default to something sane + + CGGammaValue redMin; + CGGammaValue redMax; + CGGammaValue redGamma; + CGGammaValue greenMin; + CGGammaValue greenMax; + CGGammaValue greenGamma; + CGGammaValue blueMin; + CGGammaValue blueMax; + CGGammaValue blueGamma; + + if(CGGetDisplayTransferByFormula( + mDisplay, + &redMin, + &redMax, + &redGamma, + &greenMin, + &greenMax, + &greenGamma, + &blueMin, + &blueMax, + &blueGamma) == noErr) + { + // So many choices... + // Let's just return the green channel gamma for now. + result = greenGamma; + } + + return result; +} + +BOOL LLWindowMacOSX::restoreGamma() +{ + CGDisplayRestoreColorSyncSettings(); + return true; +} + +BOOL LLWindowMacOSX::setGamma(const F32 gamma) +{ + CGGammaValue redMin; + CGGammaValue redMax; + CGGammaValue redGamma; + CGGammaValue greenMin; + CGGammaValue greenMax; + CGGammaValue greenGamma; + CGGammaValue blueMin; + CGGammaValue blueMax; + CGGammaValue blueGamma; + + // MBW -- XXX -- Should we allow this in windowed mode? + + if(CGGetDisplayTransferByFormula( + mDisplay, + &redMin, + &redMax, + &redGamma, + &greenMin, + &greenMax, + &greenGamma, + &blueMin, + &blueMax, + &blueGamma) != noErr) + { + return false; + } + + if(CGSetDisplayTransferByFormula( + mDisplay, + redMin, + redMax, + gamma, + greenMin, + greenMax, + gamma, + blueMin, + blueMax, + gamma) != noErr) + { + return false; + } + + + return true; +} + +BOOL LLWindowMacOSX::isCursorHidden() +{ + return mCursorHidden; +} + + + +// Constrains the mouse to the window. +void LLWindowMacOSX::setMouseClipping( BOOL b ) +{ + // Just stash the requested state. We'll simulate this when the cursor is hidden by decoupling. + mIsMouseClipping = b; + + if(b) + { + // llinfos << "setMouseClipping(TRUE)" << llendl + } + else + { + // llinfos << "setMouseClipping(FALSE)" << llendl + } + + adjustCursorDecouple(); +} + +BOOL LLWindowMacOSX::setCursorPosition(const LLCoordWindow position) +{ + BOOL result = FALSE; + LLCoordScreen screen_pos; + + if (!convertCoords(position, &screen_pos)) + { + return FALSE; + } + + CGPoint newPosition; + + // llinfos << "setCursorPosition(" << screen_pos.mX << ", " << screen_pos.mY << ")" << llendl + + newPosition.x = screen_pos.mX; + newPosition.y = screen_pos.mY; + + CGSetLocalEventsSuppressionInterval(0.0); + if(CGWarpMouseCursorPosition(newPosition) == noErr) + { + result = TRUE; + } + + // Under certain circumstances, this will trigger us to decouple the cursor. + adjustCursorDecouple(true); + + return result; +} + +static void fixOrigin(void) +{ + GrafPtr port; + Rect portrect; + + ::GetPort(&port); + ::GetPortBounds(port, &portrect); + if((portrect.left != 0) || (portrect.top != 0)) + { + // Mozilla sometimes changes our port origin. Fuckers. + ::SetOrigin(0,0); + } +} + +BOOL LLWindowMacOSX::getCursorPosition(LLCoordWindow *position) +{ + Point cursor_point; + LLCoordScreen screen_pos; + GrafPtr save; + + if(mWindow == NULL) + return FALSE; + + ::GetPort(&save); + ::SetPort(GetWindowPort(mWindow)); + fixOrigin(); + + // gets the mouse location in local coordinates + ::GetMouse(&cursor_point); + +// lldebugs << "getCursorPosition(): cursor is at " << cursor_point.h << ", " << cursor_point.v << " port origin: " << portrect.left << ", " << portrect.top << llendl; + + ::SetPort(save); + + if(mCursorDecoupled) + { + // CGMouseDelta x, y; + + // If the cursor's decoupled, we need to read the latest movement delta as well. + // CGGetLastMouseDelta( &x, &y ); + // cursor_point.h += x; + // cursor_point.v += y; + + // CGGetLastMouseDelta may behave strangely when the cursor's first captured. + // Stash in the event handler instead. + cursor_point.h += mCursorLastEventDeltaX; + cursor_point.v += mCursorLastEventDeltaY; + } + + position->mX = cursor_point.h; + position->mY = cursor_point.v; + + return TRUE; +} + +void LLWindowMacOSX::adjustCursorDecouple(bool warpingMouse) +{ + if(mIsMouseClipping && mCursorHidden) + { + if(warpingMouse) + { + // The cursor should be decoupled. Make sure it is. + if(!mCursorDecoupled) + { + // llinfos << "adjustCursorDecouple: decoupling cursor" << llendl; + CGAssociateMouseAndMouseCursorPosition(false); + mCursorDecoupled = true; + mCursorIgnoreNextDelta = TRUE; + } + } + } + else + { + // The cursor should not be decoupled. Make sure it isn't. + if(mCursorDecoupled) + { + // llinfos << "adjustCursorDecouple: recoupling cursor" << llendl; + CGAssociateMouseAndMouseCursorPosition(true); + mCursorDecoupled = false; + } + } +} + +F32 LLWindowMacOSX::getNativeAspectRatio() +{ + if (mFullscreen) + { + return (F32)mFullscreenWidth / (F32)mFullscreenHeight; + } + else + { + // The constructor for this class grabs the aspect ratio of the monitor before doing any resolution + // switching, and stashes it in mOriginalAspectRatio. Here, we just return it. + + if (mOverrideAspectRatio > 0.f) + { + return mOverrideAspectRatio; + } + + return mOriginalAspectRatio; + } +} + +F32 LLWindowMacOSX::getPixelAspectRatio() +{ + //OS X always enforces a 1:1 pixel aspect ratio, regardless of video mode + return 1.f; +} + +//static SInt32 oldWindowLevel; + +// MBW -- XXX -- There's got to be a better way than this. Find it, please... + +void LLWindowMacOSX::beforeDialog() +{ + if(mFullscreen) + { + +#if CAPTURE_ALL_DISPLAYS + // Uncapture all displays (may want to do this for final build) + CGReleaseAllDisplays (); +#else + // Uncapture only the main display (useful for debugging) + CGDisplayRelease (mDisplay); +#endif + // kDocumentWindowClass + // kMovableModalWindowClass + // kAllWindowClasses + + // GLint order = 0; + // aglSetInteger(mContext, AGL_ORDER_CONTEXT_TO_FRONT, &order); + aglSetDrawable(mContext, NULL); + // GetWindowGroupLevel(GetWindowGroupOfClass(kAllWindowClasses), &oldWindowLevel); + // SetWindowGroupLevel(GetWindowGroupOfClass(kAllWindowClasses), CGShieldingWindowLevel()); + + mHandsOffEvents = TRUE; + + } +} + +void LLWindowMacOSX::afterDialog() +{ + if(mFullscreen) + { + mHandsOffEvents = FALSE; + + // SetWindowGroupLevel(GetWindowGroupOfClass(kAllWindowClasses), oldWindowLevel); + aglSetFullScreen(mContext, 0, 0, 0, 0); + // GLint order = 1; + // aglSetInteger(mContext, AGL_ORDER_CONTEXT_TO_FRONT, &order); + +#if CAPTURE_ALL_DISPLAYS + // Capture all displays (may want to do this for final build) + CGCaptureAllDisplays (); +#else + // Capture only the main display (useful for debugging) + CGDisplayCapture (mDisplay); +#endif + } +} + + +S32 LLWindowMacOSX::stat(const char* file_name, struct stat* stat_info) +{ + return ::stat( file_name, stat_info ); +} + +void LLWindowMacOSX::flashIcon(F32 seconds) +{ + // Don't do this if we're already started, since this would try to install the NMRec twice. + if(!mBounceTimer.getStarted()) + { + OSErr err; + + mBounceTime = seconds; + memset(&mBounceRec, sizeof(mBounceRec), 0); + mBounceRec.qType = nmType; + mBounceRec.nmMark = 1; + err = NMInstall(&mBounceRec); + if(err == noErr) + { + mBounceTimer.start(); + } + else + { + // This is very not-fatal (only problem is the icon will not bounce), but we'd like to find out about it somehow... + llinfos << "NMInstall failed with error code " << err << llendl; + } + } +} + +BOOL LLWindowMacOSX::isClipboardTextAvailable() +{ + OSStatus err; + ScrapRef scrap; + ScrapFlavorFlags flags; + BOOL result = false; + + err = GetCurrentScrap(&scrap); + + if(err == noErr) + { + err = GetScrapFlavorFlags(scrap, kScrapFlavorTypeUnicode, &flags); + } + + if(err == noErr) + result = true; + + return result; +} + +BOOL LLWindowMacOSX::pasteTextFromClipboard(LLWString &dst) +{ + OSStatus err; + ScrapRef scrap; + Size len; + BOOL result = false; + + err = GetCurrentScrap(&scrap); + + if(err == noErr) + { + err = GetScrapFlavorSize(scrap, kScrapFlavorTypeUnicode, &len); + } + + if((err == noErr) && (len > 0)) + { + int u16len = len / sizeof(U16); + U16 *temp = new U16[u16len + 1]; + if (temp) + { + memset(temp, 0, (u16len + 1) * sizeof(temp[0])); + err = GetScrapFlavorData(scrap, kScrapFlavorTypeUnicode, &len, temp); + if (err == noErr) + { + // convert \r\n to \n and \r to \n in the incoming text. + U16 *s, *d; + for(s = d = temp; s[0] != '\0'; s++, d++) + { + if(s[0] == '\r') + { + if(s[1] == '\n') + { + // CRLF, a.k.a. DOS newline. Collapse to a single '\n'. + s++; + } + + d[0] = '\n'; + } + else + { + d[0] = s[0]; + } + } + + d[0] = '\0'; + + dst = utf16str_to_wstring(temp); + + result = true; + } + delete[] temp; + } + } + + return result; +} + +BOOL LLWindowMacOSX::copyTextToClipboard(const LLWString &s) +{ + OSStatus err; + ScrapRef scrap; + //Size len; + //char *temp; + BOOL result = false; + + if (!s.empty()) + { + err = GetCurrentScrap(&scrap); + if (err == noErr) + err = ClearScrap(&scrap); + + if (err == noErr) + { + llutf16string utf16str = wstring_to_utf16str(s); + size_t u16len = utf16str.length() * sizeof(U16); + err = PutScrapFlavor(scrap, kScrapFlavorTypeUnicode, kScrapFlavorMaskNone, u16len, utf16str.data()); + if (err == noErr) + result = true; + } + } + + return result; +} + + +BOOL LLWindowMacOSX::sendEmail(const char* address, const char* subject, const char* body_text, + const char* attachment, const char* attachment_displayed_name ) +{ + // MBW -- XXX -- Um... yeah. I'll get to this later. + + return false; +} + + +// protected +BOOL LLWindowMacOSX::resetDisplayResolution() +{ + // This is only called from elsewhere in this class, and it's not used by the Mac implementation. + return true; +} + + +LLWindow::LLWindowResolution* LLWindowMacOSX::getSupportedResolutions(S32 &num_resolutions) +{ + if (!mSupportedResolutions) + { + CFArrayRef modes = CGDisplayAvailableModes(mDisplay); + + if(modes != NULL) + { + CFIndex index, cnt; + + mSupportedResolutions = new LLWindowResolution[MAX_NUM_RESOLUTIONS]; + mNumSupportedResolutions = 0; + + // Examine each mode + cnt = CFArrayGetCount( modes ); + + for ( index = 0; (index < cnt) && (mNumSupportedResolutions < MAX_NUM_RESOLUTIONS); index++ ) + { + // Pull the mode dictionary out of the CFArray + CFDictionaryRef mode = (CFDictionaryRef)CFArrayGetValueAtIndex( modes, index ); + long width = getDictLong(mode, kCGDisplayWidth); + long height = getDictLong(mode, kCGDisplayHeight); + long bits = getDictLong(mode, kCGDisplayBitsPerPixel); + + if(bits == BITS_PER_PIXEL && width >= 800 && height >= 600) + { + BOOL resolution_exists = FALSE; + for(S32 i = 0; i < mNumSupportedResolutions; i++) + { + if (mSupportedResolutions[i].mWidth == width && + mSupportedResolutions[i].mHeight == height) + { + resolution_exists = TRUE; + } + } + if (!resolution_exists) + { + mSupportedResolutions[mNumSupportedResolutions].mWidth = width; + mSupportedResolutions[mNumSupportedResolutions].mHeight = height; + mNumSupportedResolutions++; + } + } + } + } + } + + num_resolutions = mNumSupportedResolutions; + return mSupportedResolutions; +} + +BOOL LLWindowMacOSX::convertCoords(LLCoordGL from, LLCoordWindow *to) +{ + S32 client_height; + Rect client_rect; + + if(mFullscreen) + { + // In the fullscreen case, the "window" is the entire screen. + client_rect.left = 0; + client_rect.top = 0; + client_rect.right = mFullscreenWidth; + client_rect.bottom = mFullscreenHeight; + } + else if (!mWindow || + (GetWindowBounds(mWindow, kWindowContentRgn, &client_rect) != noErr) || + NULL == to) + { + return FALSE; + } + + to->mX = from.mX; + client_height = client_rect.bottom - client_rect.top; + to->mY = client_height - from.mY - 1; + + return TRUE; +} + +BOOL LLWindowMacOSX::convertCoords(LLCoordWindow from, LLCoordGL* to) +{ + S32 client_height; + Rect client_rect; + + if(mFullscreen) + { + // In the fullscreen case, the "window" is the entire screen. + client_rect.left = 0; + client_rect.top = 0; + client_rect.right = mFullscreenWidth; + client_rect.bottom = mFullscreenHeight; + } + else if (!mWindow || + (GetWindowBounds(mWindow, kWindowContentRgn, &client_rect) != noErr) || + NULL == to) + { + return FALSE; + } + + to->mX = from.mX; + client_height = client_rect.bottom - client_rect.top; + to->mY = client_height - from.mY - 1; + + return TRUE; +} + +BOOL LLWindowMacOSX::convertCoords(LLCoordScreen from, LLCoordWindow* to) +{ + if(mFullscreen) + { + // In the fullscreen case, window and screen coordinates are the same. + to->mX = from.mX; + to->mY = from.mY; + return TRUE; + } + else if(mWindow) + { + GrafPtr save; + Point mouse_point; + + mouse_point.h = from.mX; + mouse_point.v = from.mY; + + ::GetPort(&save); + ::SetPort(GetWindowPort(mWindow)); + fixOrigin(); + + ::GlobalToLocal(&mouse_point); + + to->mX = mouse_point.h; + to->mY = mouse_point.v; + + ::SetPort(save); + + return TRUE; + } + + return FALSE; +} + +BOOL LLWindowMacOSX::convertCoords(LLCoordWindow from, LLCoordScreen *to) +{ + if(mFullscreen) + { + // In the fullscreen case, window and screen coordinates are the same. + to->mX = from.mX; + to->mY = from.mY; + return TRUE; + } + else if(mWindow) + { + GrafPtr save; + Point mouse_point; + + mouse_point.h = from.mX; + mouse_point.v = from.mY; + ::GetPort(&save); + ::SetPort(GetWindowPort(mWindow)); + fixOrigin(); + + LocalToGlobal(&mouse_point); + + to->mX = mouse_point.h; + to->mY = mouse_point.v; + + ::SetPort(save); + + return TRUE; + } + + return FALSE; +} + +BOOL LLWindowMacOSX::convertCoords(LLCoordScreen from, LLCoordGL *to) +{ + LLCoordWindow window_coord; + + return(convertCoords(from, &window_coord) && convertCoords(window_coord, to)); +} + +BOOL LLWindowMacOSX::convertCoords(LLCoordGL from, LLCoordScreen *to) +{ + LLCoordWindow window_coord; + + return(convertCoords(from, &window_coord) && convertCoords(window_coord, to)); +} + + + + +void LLWindowMacOSX::setupFailure(const char* text, const char* caption, U32 type) +{ + destroyContext(); + + OSMessageBox(text, caption, type); +} + +pascal OSStatus LLWindowMacOSX::staticEventHandler(EventHandlerCallRef myHandler, EventRef event, void* userData) +{ + LLWindowMacOSX *self = (LLWindowMacOSX*)userData; + + return(self->eventHandler(myHandler, event)); +} + +OSStatus LLWindowMacOSX::eventHandler (EventHandlerCallRef myHandler, EventRef event) +{ + OSStatus result = eventNotHandledErr; + UInt32 evtClass = GetEventClass (event); + UInt32 evtKind = GetEventKind (event); + + // Always handle command events, even in hands-off mode. + if((evtClass == kEventClassCommand) && (evtKind == kEventCommandProcess)) + { + HICommand command; + GetEventParameter (event, kEventParamDirectObject, typeHICommand, NULL, sizeof(command), NULL, &command); + + switch(command.commandID) + { + case kHICommandQuit: + if(mCallbacks->handleCloseRequest(this)) + { + // Get the app to initiate cleanup. + mCallbacks->handleQuit(this); + // The app is responsible for calling destroyWindow when done with GL + } + result = noErr; + break; + + default: + // MBW -- XXX -- Should we handle other events here? + break; + } + } + + if(mHandsOffEvents) + { + return(result); + } + + switch (evtClass) + { + case kEventClassTextInput: + { + switch (evtKind) + { + case kEventTextInputUnicodeForKeyEvent: + { + UInt32 modifiers = 0; + + // First, process the raw event. + { + EventRef rawEvent; + + // Get the original event and extract the modifier keys, so we can ignore command-key events. + if (GetEventParameter(event, kEventParamTextInputSendKeyboardEvent, typeEventRef, NULL, sizeof(rawEvent), NULL, &rawEvent) == noErr) + { + // Grab the modifiers for later use in this function... + GetEventParameter (rawEvent, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(UInt32), NULL, &modifiers); + + // and call this function recursively to handle the raw key event. + eventHandler (myHandler, rawEvent); + } + } + + OSStatus err = noErr; + EventParamType actualType = typeUnicodeText; + UInt32 actualSize = 0; + size_t actualCount = 0; + U16 *buffer = NULL; + + // Get the size of the unicode data + err = GetEventParameter (event, kEventParamTextInputSendText, typeUnicodeText, &actualType, 0, &actualSize, NULL); + if(err == noErr) + { + // allocate a buffer and get the actual data. + actualCount = actualSize / sizeof(U16); + buffer = new U16[actualCount]; + err = GetEventParameter (event, kEventParamTextInputSendText, typeUnicodeText, &actualType, actualSize, &actualSize, buffer); + } + + if(err == noErr) + { + if(modifiers & (cmdKey | controlKey)) + { + // This was a menu key equivalent. Ignore it. + } + else + { + MASK mask = 0; + if(modifiers & shiftKey) { mask |= MASK_SHIFT; } + if(modifiers & (cmdKey | controlKey)) { mask |= MASK_CONTROL; } + if(modifiers & optionKey) { mask |= MASK_ALT; } + + llassert( actualType == typeUnicodeText ); + + // The result is a UTF16 buffer. Pass the characters in turn to handleUnicodeChar. + + // Convert to UTF32 and go character-by-character. + llutf16string utf16(buffer, actualCount); + LLWString utf32 = utf16str_to_wstring(utf16); + LLWString::iterator iter; + + for(iter = utf32.begin(); iter != utf32.end(); iter++) + { + mCallbacks->handleUnicodeChar(*iter, mask); + } + } + } + + if(buffer != NULL) + { + delete[] buffer; + } + + result = err; + } + break; + } + } + break; + + case kEventClassKeyboard: + { + UInt32 keyCode = 0; + char charCode = 0; + UInt32 modifiers = 0; + + // Some of these may fail for some event types. That's fine. + GetEventParameter (event, kEventParamKeyCode, typeUInt32, NULL, sizeof(UInt32), NULL, &keyCode); + GetEventParameter (event, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(UInt32), NULL, &modifiers); + + // printf("key event, key code = 0x%08x, char code = 0x%02x (%c), modifiers = 0x%08x\n", keyCode, charCode, (char)charCode, modifiers); + // fflush(stdout); + + switch (evtKind) + { + case kEventRawKeyDown: + case kEventRawKeyRepeat: + if (gDebugWindowProc) + { + printf("key down, key code = 0x%08x, char code = 0x%02x (%c), modifiers = 0x%08x\n", + (unsigned int)keyCode, charCode, (char)charCode, (unsigned int)modifiers); + fflush(stdout); + } + gKeyboard->handleKeyDown(keyCode, modifiers); + result = eventNotHandledErr; + break; + + case kEventRawKeyUp: + if (gDebugWindowProc) + { + printf("key up, key code = 0x%08x, char code = 0x%02x (%c), modifiers = 0x%08x\n", + (unsigned int)keyCode, charCode, (char)charCode, (unsigned int)modifiers); + fflush(stdout); + } + gKeyboard->handleKeyUp(keyCode, modifiers); + result = eventNotHandledErr; + break; + + case kEventRawKeyModifiersChanged: + // The keyboard input system wants key up/down events for modifier keys. + // Mac OS doesn't supply these directly, but can supply events when the collective modifier state changes. + // Use these events to generate up/down events for the modifiers. + + if((modifiers & shiftKey) && !(mLastModifiers & shiftKey)) + { + if (gDebugWindowProc) printf("Shift key down event\n"); + gKeyboard->handleKeyDown(0x38, (modifiers & 0x00FFFFFF) | ((0x38 << 24) & 0xFF000000)); + } + else if(!(modifiers & shiftKey) && (mLastModifiers & shiftKey)) + { + if (gDebugWindowProc) printf("Shift key up event\n"); + gKeyboard->handleKeyUp(0x38, (modifiers & 0x00FFFFFF) | ((0x38 << 24) & 0xFF000000)); + } + + if((modifiers & alphaLock) && !(mLastModifiers & alphaLock)) + { + if (gDebugWindowProc) printf("Caps lock down event\n"); + gKeyboard->handleKeyDown(0x39, (modifiers & 0x00FFFFFF) | ((0x39 << 24) & 0xFF000000)); + } + else if(!(modifiers & alphaLock) && (mLastModifiers & alphaLock)) + { + if (gDebugWindowProc) printf("Caps lock up event\n"); + gKeyboard->handleKeyUp(0x39, (modifiers & 0x00FFFFFF) | ((0x39 << 24) & 0xFF000000)); + } + + if((modifiers & controlKey) && !(mLastModifiers & controlKey)) + { + if (gDebugWindowProc) printf("Control key down event\n"); + gKeyboard->handleKeyDown(0x3b, (modifiers & 0x00FFFFFF) | ((0x3b << 24) & 0xFF000000)); + } + else if(!(modifiers & controlKey) && (mLastModifiers & controlKey)) + { + if (gDebugWindowProc) printf("Control key up event\n"); + gKeyboard->handleKeyUp(0x3b, (modifiers & 0x00FFFFFF) | ((0x3b << 24) & 0xFF000000)); + } + + if((modifiers & optionKey) && !(mLastModifiers & optionKey)) + { + if (gDebugWindowProc) printf("Option key down event\n"); + gKeyboard->handleKeyDown(0x3a, (modifiers & 0x00FFFFFF) | ((0x3a << 24) & 0xFF000000)); + } + else if(!(modifiers & optionKey) && (mLastModifiers & optionKey)) + { + if (gDebugWindowProc) printf("Option key up event\n"); + gKeyboard->handleKeyUp(0x3a, (modifiers & 0x00FFFFFF) | ((0x3a << 24) & 0xFF000000)); + } + + // When the state of the 'Fn' key (the one that changes some of the mappings on a powerbook/macbook keyboard + // to an embedded keypad) changes, it may subsequently cause a key up event to be lost, which may lead to + // a movement key getting "stuck" down. This is bad. + // This is an OS bug -- even the GetKeys() API doesn't tell you the key has been released. + // This workaround causes all held-down keys to be reset whenever the state of the Fn key changes. This isn't + // exactly what we want, but it does avoid the case where you get stuck running forward. + if((modifiers & kEventKeyModifierFnMask) != (mLastModifiers & kEventKeyModifierFnMask)) + { + if (gDebugWindowProc) printf("Fn key state change event\n"); + gKeyboard->resetKeys(); + } + + if (gDebugWindowProc) fflush(stdout); + + mLastModifiers = modifiers; + result = eventNotHandledErr; + break; + } + } + break; + + case kEventClassMouse: + { + result = CallNextEventHandler(myHandler, event); + if (eventNotHandledErr == result) + { // only handle events not already handled (prevents wierd resize interaction) + EventMouseButton button = kEventMouseButtonPrimary; + HIPoint location = {0.0f, 0.0f}; + UInt32 modifiers = 0; + UInt32 clickCount = 1; + long wheelDelta = 0; + LLCoordScreen inCoords; + LLCoordGL outCoords; + MASK mask = 0; + + GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(button), NULL, &button); + GetEventParameter(event, kEventParamMouseLocation, typeHIPoint, NULL, sizeof(location), NULL, &location); + GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(modifiers), NULL, &modifiers); + GetEventParameter(event, kEventParamMouseWheelDelta, typeLongInteger, NULL, sizeof(wheelDelta), NULL, &wheelDelta); + GetEventParameter(event, kEventParamClickCount, typeUInt32, NULL, sizeof(clickCount), NULL, &clickCount); + + inCoords.mX = llround(location.x); + inCoords.mY = llround(location.y); + + if(modifiers & shiftKey) { mask |= MASK_SHIFT; } + if(modifiers & controlKey) { mask |= MASK_CONTROL; } + if(modifiers & optionKey) { mask |= MASK_ALT; } + + if(mCursorDecoupled) + { + CGMouseDelta x, y; + + // If the cursor's decoupled, we need to read the latest movement delta as well. + CGGetLastMouseDelta( &x, &y ); + mCursorLastEventDeltaX = x; + mCursorLastEventDeltaY = y; + + if(mCursorIgnoreNextDelta) + { + mCursorLastEventDeltaX = 0; + mCursorLastEventDeltaY = 0; + mCursorIgnoreNextDelta = FALSE; + } + } + else + { + mCursorLastEventDeltaX = 0; + mCursorLastEventDeltaY = 0; + } + + inCoords.mX += mCursorLastEventDeltaX; + inCoords.mY += mCursorLastEventDeltaY; + + convertCoords(inCoords, &outCoords); + + // printf("coords in: %d, %d; coords out: %d, %d\n", inCoords.mX, inCoords.mY, outCoords.mX, outCoords.mY); + // fflush(stdout); + + + switch (evtKind) + { + case kEventMouseDown: + switch(button) + { + case kEventMouseButtonPrimary: + if(modifiers & cmdKey) + { + // Simulate a right click + mSimulatedRightClick = true; + mCallbacks->handleRightMouseDown(this, outCoords, mask); + } + else if(clickCount == 2) + { + // Windows double-click events replace the second mousedown event in a double-click. + mCallbacks->handleDoubleClick(this, outCoords, mask); + } + else + { + mCallbacks->handleMouseDown(this, outCoords, mask); + } + break; + case kEventMouseButtonSecondary: + mCallbacks->handleRightMouseDown(this, outCoords, mask); + break; + } + result = noErr; + break; + case kEventMouseUp: + + switch(button) + { + case kEventMouseButtonPrimary: + if(mSimulatedRightClick) + { + // End of simulated right click + mSimulatedRightClick = false; + mCallbacks->handleRightMouseUp(this, outCoords, mask); + } + else + { + mCallbacks->handleMouseUp(this, outCoords, mask); + } + break; + case kEventMouseButtonSecondary: + mCallbacks->handleRightMouseUp(this, outCoords, mask); + break; + } + result = noErr; + break; + + case kEventMouseWheelMoved: + { + static S32 z_delta = 0; + + z_delta += wheelDelta; + + if (z_delta <= -WHEEL_DELTA || WHEEL_DELTA <= z_delta) + { + mCallbacks->handleScrollWheel(this, -z_delta / WHEEL_DELTA); + z_delta = 0; + } + } + result = noErr; + break; + + case kEventMouseDragged: + case kEventMouseMoved: + mCallbacks->handleMouseMove(this, outCoords, mask); + result = noErr; + break; + + } + } + } + break; + + case kEventClassWindow: + switch(evtKind) + { + case kEventWindowBoundsChanging: + { + Rect currentBounds; + Rect previousBounds; + + GetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, ¤tBounds); + GetEventParameter(event, kEventParamPreviousBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, &previousBounds); + + // This is where we would constrain move/resize to a particular screen + if(0) + { + SetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, sizeof(Rect), ¤tBounds); + } + } + break; + + case kEventWindowBoundsChanged: + { + Rect newBounds; + + GetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, &newBounds); + aglUpdateContext(mContext); + mCallbacks->handleResize(this, newBounds.right - newBounds.left, newBounds.bottom - newBounds.top); + + + } + break; + + case kEventWindowClose: + if(mCallbacks->handleCloseRequest(this)) + { + // Get the app to initiate cleanup. + mCallbacks->handleQuit(this); + // The app is responsible for calling destroyWindow when done with GL + } + result = noErr; + break; + + case kEventWindowHidden: + // llinfos << "LLWindowMacOSX: Deactivating on hide" << llendl; + mMinimized = TRUE; + mCallbacks->handleActivate(this, false); + // result = noErr; + break; + + case kEventWindowShown: + // llinfos << "LLWindowMacOSX: Activating on show" << llendl; + mMinimized = FALSE; + mCallbacks->handleActivate(this, true); + // result = noErr; + break; + + case kEventWindowCollapsed: + // llinfos << "LLWindowMacOSX: Deactivating on collapse" << llendl; + mMinimized = TRUE; + mCallbacks->handleActivate(this, false); + // result = noErr; + break; + + case kEventWindowExpanded: + // llinfos << "LLWindowMacOSX: Activating on expand" << llendl; + mMinimized = FALSE; + mCallbacks->handleActivate(this, true); + // result = noErr; + break; + + case kEventWindowGetClickActivation: + // BringToFront(mWindow); + // result = noErr; + break; + } + break; + } + return result; +} + +const char* cursorIDToName(int id) +{ + switch (id) + { + case UI_CURSOR_ARROW: return "UI_CURSOR_ARROW"; + case UI_CURSOR_WAIT: return "UI_CURSOR_WAIT"; + case UI_CURSOR_HAND: return "UI_CURSOR_HAND"; + case UI_CURSOR_IBEAM: return "UI_CURSOR_IBEAM"; + case UI_CURSOR_CROSS: return "UI_CURSOR_CROSS"; + case UI_CURSOR_SIZENWSE: return "UI_CURSOR_SIZENWSE"; + case UI_CURSOR_SIZENESW: return "UI_CURSOR_SIZENESW"; + case UI_CURSOR_SIZEWE: return "UI_CURSOR_SIZEWE"; + case UI_CURSOR_SIZENS: return "UI_CURSOR_SIZENS"; + case UI_CURSOR_NO: return "UI_CURSOR_NO"; + case UI_CURSOR_WORKING: return "UI_CURSOR_WORKING"; + case UI_CURSOR_TOOLGRAB: return "UI_CURSOR_TOOLGRAB"; + case UI_CURSOR_TOOLLAND: return "UI_CURSOR_TOOLLAND"; + case UI_CURSOR_TOOLFOCUS: return "UI_CURSOR_TOOLFOCUS"; + case UI_CURSOR_TOOLCREATE: return "UI_CURSOR_TOOLCREATE"; + case UI_CURSOR_ARROWDRAG: return "UI_CURSOR_ARROWDRAG"; + case UI_CURSOR_ARROWCOPY: return "UI_CURSOR_ARROWCOPY"; + case UI_CURSOR_ARROWDRAGMULTI: return "UI_CURSOR_ARROWDRAGMULTI"; + case UI_CURSOR_ARROWCOPYMULTI: return "UI_CURSOR_ARROWCOPYMULTI"; + case UI_CURSOR_NOLOCKED: return "UI_CURSOR_NOLOCKED"; + case UI_CURSOR_ARROWLOCKED: return "UI_CURSOR_ARROWLOCKED"; + case UI_CURSOR_GRABLOCKED: return "UI_CURSOR_GRABLOCKED"; + case UI_CURSOR_TOOLTRANSLATE: return "UI_CURSOR_TOOLTRANSLATE"; + case UI_CURSOR_TOOLROTATE: return "UI_CURSOR_TOOLROTATE"; + case UI_CURSOR_TOOLSCALE: return "UI_CURSOR_TOOLSCALE"; + case UI_CURSOR_TOOLCAMERA: return "UI_CURSOR_TOOLCAMERA"; + case UI_CURSOR_TOOLPAN: return "UI_CURSOR_TOOLPAN"; + case UI_CURSOR_TOOLZOOMIN: return "UI_CURSOR_TOOLZOOMIN"; + case UI_CURSOR_TOOLPICKOBJECT3: return "UI_CURSOR_TOOLPICKOBJECT3"; + case UI_CURSOR_TOOLSIT: return "UI_CURSOR_TOOLSIT"; + case UI_CURSOR_TOOLBUY: return "UI_CURSOR_TOOLBUY"; + case UI_CURSOR_TOOLPAY: return "UI_CURSOR_TOOLPAY"; + case UI_CURSOR_TOOLOPEN: return "UI_CURSOR_TOOLOPEN"; + case UI_CURSOR_PIPETTE: return "UI_CURSOR_PIPETTE"; + } + + llerrs << "cursorIDToName: unknown cursor id" << id << llendl; + + return "UI_CURSOR_ARROW"; +} + +static CursorRef gCursors[UI_CURSOR_COUNT]; + + +static void initPixmapCursor(int cursorid, int hotspotX, int hotspotY) +{ + // cursors are in /Contents/Resources/cursors_mac/UI_CURSOR_FOO.tif + std::string fullpath = gDirUtilp->getAppRODataDir(); + fullpath += gDirUtilp->getDirDelimiter(); + fullpath += "cursors_mac"; + fullpath += gDirUtilp->getDirDelimiter(); + fullpath += cursorIDToName(cursorid); + fullpath += ".tif"; + + gCursors[cursorid] = createImageCursor(fullpath.c_str(), hotspotX, hotspotY); +} + +void LLWindowMacOSX::setCursor(ECursorType cursor) +{ + OSStatus result = noErr; + + if (cursor == UI_CURSOR_ARROW + && mBusyCount > 0) + { + cursor = UI_CURSOR_WORKING; + } + + if(mCurrentCursor == cursor) + return; + + // RN: replace multi-drag cursors with single versions + if (cursor == UI_CURSOR_ARROWDRAGMULTI) + { + cursor = UI_CURSOR_ARROWDRAG; + } + else if (cursor == UI_CURSOR_ARROWCOPYMULTI) + { + cursor = UI_CURSOR_ARROWCOPY; + } + + switch(cursor) + { + default: + case UI_CURSOR_ARROW: + InitCursor(); + if(mCursorHidden) + { + // Since InitCursor resets the hide level, correct for it here. + ::HideCursor(); + } + break; + + // MBW -- XXX -- Some of the standard Windows cursors have no standard Mac equivalents. + // Find out what they look like and replicate them. + + // These are essentially correct + case UI_CURSOR_WAIT: SetThemeCursor(kThemeWatchCursor); break; + case UI_CURSOR_IBEAM: SetThemeCursor(kThemeIBeamCursor); break; + case UI_CURSOR_CROSS: SetThemeCursor(kThemeCrossCursor); break; + case UI_CURSOR_HAND: SetThemeCursor(kThemePointingHandCursor); break; + // case UI_CURSOR_NO: SetThemeCursor(kThemeNotAllowedCursor); break; + case UI_CURSOR_ARROWCOPY: SetThemeCursor(kThemeCopyArrowCursor); break; + + // Double-check these + case UI_CURSOR_NO: + case UI_CURSOR_SIZEWE: + case UI_CURSOR_SIZENS: + case UI_CURSOR_SIZENWSE: + case UI_CURSOR_SIZENESW: + case UI_CURSOR_WORKING: + case UI_CURSOR_TOOLGRAB: + case UI_CURSOR_TOOLLAND: + case UI_CURSOR_TOOLFOCUS: + case UI_CURSOR_TOOLCREATE: + case UI_CURSOR_ARROWDRAG: + case UI_CURSOR_NOLOCKED: + case UI_CURSOR_ARROWLOCKED: + case UI_CURSOR_GRABLOCKED: + case UI_CURSOR_TOOLTRANSLATE: + case UI_CURSOR_TOOLROTATE: + case UI_CURSOR_TOOLSCALE: + case UI_CURSOR_TOOLCAMERA: + case UI_CURSOR_TOOLPAN: + case UI_CURSOR_TOOLZOOMIN: + case UI_CURSOR_TOOLPICKOBJECT3: + case UI_CURSOR_TOOLSIT: + case UI_CURSOR_TOOLBUY: + case UI_CURSOR_TOOLPAY: + case UI_CURSOR_TOOLOPEN: + result = setImageCursor(gCursors[cursor]); + break; + + } + + if(result != noErr) + { + InitCursor(); + } + + mCurrentCursor = cursor; +} + +ECursorType LLWindowMacOSX::getCursor() +{ + return mCurrentCursor; +} + +void LLWindowMacOSX::initCursors() +{ + initPixmapCursor(UI_CURSOR_NO, 8, 8); + initPixmapCursor(UI_CURSOR_WORKING, 1, 1); + initPixmapCursor(UI_CURSOR_TOOLGRAB, 2, 14); + initPixmapCursor(UI_CURSOR_TOOLLAND, 13, 8); + initPixmapCursor(UI_CURSOR_TOOLFOCUS, 7, 6); + initPixmapCursor(UI_CURSOR_TOOLCREATE, 7, 7); + initPixmapCursor(UI_CURSOR_ARROWDRAG, 1, 1); + initPixmapCursor(UI_CURSOR_ARROWCOPY, 1, 1); + initPixmapCursor(UI_CURSOR_NOLOCKED, 8, 8); + initPixmapCursor(UI_CURSOR_ARROWLOCKED, 1, 1); + initPixmapCursor(UI_CURSOR_GRABLOCKED, 2, 14); + initPixmapCursor(UI_CURSOR_TOOLTRANSLATE, 1, 1); + initPixmapCursor(UI_CURSOR_TOOLROTATE, 1, 1); + initPixmapCursor(UI_CURSOR_TOOLSCALE, 1, 1); + initPixmapCursor(UI_CURSOR_TOOLCAMERA, 7, 6); + initPixmapCursor(UI_CURSOR_TOOLPAN, 7, 6); + initPixmapCursor(UI_CURSOR_TOOLZOOMIN, 7, 6); + initPixmapCursor(UI_CURSOR_TOOLPICKOBJECT3, 1, 1); + initPixmapCursor(UI_CURSOR_TOOLSIT, 1, 1); + initPixmapCursor(UI_CURSOR_TOOLBUY, 1, 1); + initPixmapCursor(UI_CURSOR_TOOLPAY, 1, 1); + initPixmapCursor(UI_CURSOR_TOOLOPEN, 1, 1); + + initPixmapCursor(UI_CURSOR_SIZENWSE, 10, 10); + initPixmapCursor(UI_CURSOR_SIZENESW, 10, 10); + initPixmapCursor(UI_CURSOR_SIZEWE, 10, 10); + initPixmapCursor(UI_CURSOR_SIZENS, 10, 10); + +} + +void LLWindowMacOSX::captureMouse() +{ + // By registering a global CarbonEvent handler for mouse move events, we ensure that + // mouse events are always processed. Thus, capture and release are unnecessary. +} + +void LLWindowMacOSX::releaseMouse() +{ + // By registering a global CarbonEvent handler for mouse move events, we ensure that + // mouse events are always processed. Thus, capture and release are unnecessary. +} + +void LLWindowMacOSX::hideCursor() +{ + if(!mCursorHidden) + { + // llinfos << "hideCursor: hiding" << llendl; + mCursorHidden = TRUE; + mHideCursorPermanent = TRUE; + ::HideCursor(); + } + else + { + // llinfos << "hideCursor: already hidden" << llendl; + } + + adjustCursorDecouple(); +} + +void LLWindowMacOSX::showCursor() +{ + if(mCursorHidden) + { + // llinfos << "showCursor: showing" << llendl; + mCursorHidden = FALSE; + mHideCursorPermanent = FALSE; + ::ShowCursor(); + } + else + { + // llinfos << "showCursor: already visible" << llendl; + } + + adjustCursorDecouple(); +} + +void LLWindowMacOSX::showCursorFromMouseMove() +{ + if (!mHideCursorPermanent) + { + showCursor(); + } +} + +void LLWindowMacOSX::hideCursorUntilMouseMove() +{ + if (!mHideCursorPermanent) + { + hideCursor(); + mHideCursorPermanent = FALSE; + } +} + + + +// +// LLSplashScreenMacOSX +// +LLSplashScreenMacOSX::LLSplashScreenMacOSX() +{ + mWindow = NULL; +} + +LLSplashScreenMacOSX::~LLSplashScreenMacOSX() +{ +} + +void LLSplashScreenMacOSX::showImpl() +{ + // This code _could_ be used to display a spash screen... +#if 0 + IBNibRef nib = NULL; + OSStatus err; + + err = CreateNibReference(CFSTR("SecondLife"), &nib); + + if(err == noErr) + { + CreateWindowFromNib(nib, CFSTR("Splash Screen"), &mWindow); + + DisposeNibReference(nib); + } + + if(mWindow != NULL) + { + ShowWindow(mWindow); + } +#endif +} + +void LLSplashScreenMacOSX::updateImpl(const char* mesg) +{ + if(mWindow != NULL) + { + CFStringRef string = NULL; + + if(mesg != NULL) + { + string = CFStringCreateWithCString(NULL, mesg, kCFStringEncodingUTF8); + } + else + { + string = CFStringCreateWithCString(NULL, "", kCFStringEncodingUTF8); + } + + if(string != NULL) + { + ControlRef progressText = NULL; + ControlID id; + OSStatus err; + + id.signature = 'what'; + id.id = 0; + + err = GetControlByID(mWindow, &id, &progressText); + if(err == noErr) + { + err = SetControlData(progressText, kControlEntireControl, kControlStaticTextCFStringTag, sizeof(CFStringRef), (Ptr)&string); + Draw1Control(progressText); + } + + CFRelease(string); + } + } +} + + +void LLSplashScreenMacOSX::hideImpl() +{ + if(mWindow != NULL) + { + DisposeWindow(mWindow); + mWindow = NULL; + } +} + + + +S32 OSMessageBoxMacOSX(const char* text, const char* caption, U32 type) +{ + S32 result = OSBTN_CANCEL; + SInt16 retval_mac = 1; + AlertStdCFStringAlertParamRec params; + CFStringRef errorString = NULL; + CFStringRef explanationString = NULL; + DialogRef alert = NULL; + AlertType alertType = kAlertCautionAlert; + OSStatus err; + + if(text != NULL) + { + explanationString = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8); + } + else + { + explanationString = CFStringCreateWithCString(NULL, "", kCFStringEncodingUTF8); + } + + if(caption != NULL) + { + errorString = CFStringCreateWithCString(NULL, caption, kCFStringEncodingUTF8); + } + else + { + errorString = CFStringCreateWithCString(NULL, "", kCFStringEncodingUTF8); + } + + params.version = kStdCFStringAlertVersionOne; + params.movable = false; + params.helpButton = false; + params.defaultText = (CFStringRef)kAlertDefaultOKText; + params.cancelText = 0; + params.otherText = 0; + params.defaultButton = 1; + params.cancelButton = 0; + params.position = kWindowDefaultPosition; + params.flags = 0; + + switch(type) + { + case OSMB_OK: + default: + break; + case OSMB_OKCANCEL: + params.cancelText = (CFStringRef)kAlertDefaultCancelText; + params.cancelButton = 2; + break; + case OSMB_YESNO: + alertType = kAlertNoteAlert; + params.defaultText = CFSTR("Yes"); + params.cancelText = CFSTR("No"); + params.cancelButton = 2; + break; + } + + if(gWindowImplementation != NULL) + gWindowImplementation->beforeDialog(); + + err = CreateStandardAlert( + alertType, + errorString, + explanationString, + ¶ms, + &alert); + + if(err == noErr) + { + err = RunStandardAlert( + alert, + NULL, + &retval_mac); + } + + if(gWindowImplementation != NULL) + gWindowImplementation->afterDialog(); + + switch(type) + { + case OSMB_OK: + case OSMB_OKCANCEL: + default: + if(retval_mac == 1) + result = OSBTN_OK; + else + result = OSBTN_CANCEL; + break; + case OSMB_YESNO: + if(retval_mac == 1) + result = OSBTN_YES; + else + result = OSBTN_NO; + break; + } + + if(errorString != NULL) + { + CFRelease(errorString); + } + + if(explanationString != NULL) + { + CFRelease(explanationString); + } + + return result; +} + +// Open a URL with the user's default web browser. +// Must begin with protocol identifier. +void spawn_web_browser(const char* escaped_url) +{ + bool found = false; + S32 i; + for (i = 0; i < gURLProtocolWhitelistCount; i++) + { + S32 len = strlen(gURLProtocolWhitelist[i]); + if (!strncmp(escaped_url, gURLProtocolWhitelist[i], len) + && escaped_url[len] == ':') + { + found = true; + break; + } + } + + if (!found) + { + llwarns << "spawn_web_browser() called for url with protocol not on whitelist: " << escaped_url << llendl; + return; + } + + OSStatus result = noErr; + CFURLRef urlRef = NULL; + + llinfos << "Opening URL " << escaped_url << llendl; + + CFStringRef stringRef = CFStringCreateWithCString(NULL, escaped_url, kCFStringEncodingUTF8); + if (stringRef) + { + // This will succeed if the string is a full URL, including the http:// + // Note that URLs specified this way need to be properly percent-escaped. + urlRef = CFURLCreateWithString(NULL, stringRef, NULL); + + // Don't use CRURLCreateWithFileSystemPath -- only want valid URLs + + CFRelease(stringRef); + } + + if (urlRef) + { + result = LSOpenCFURLRef(urlRef, NULL); + + if (result != noErr) + { + llinfos << "Error " << result << " on open." << llendl; + } + + CFRelease(urlRef); + } + else + { + llinfos << "Error: couldn't create URL." << llendl; + } +} + +void shell_open( const char* file_path ) +{ + OSStatus result = noErr; + + llinfos << "Opening " << file_path << llendl; + CFURLRef urlRef = NULL; + + CFStringRef stringRef = CFStringCreateWithCString(NULL, file_path, kCFStringEncodingUTF8); + if (stringRef) + { + // This will succeed if the string is a full URL, including the http:// + // Note that URLs specified this way need to be properly percent-escaped. + urlRef = CFURLCreateWithString(NULL, stringRef, NULL); + + if(urlRef == NULL) + { + // This will succeed if the string is a full or partial posix path. + // This will work even if the path contains characters that would need to be percent-escaped + // in the URL (such as spaces). + urlRef = CFURLCreateWithFileSystemPath(NULL, stringRef, kCFURLPOSIXPathStyle, false); + } + + CFRelease(stringRef); + } + + if (urlRef) + { + result = LSOpenCFURLRef(urlRef, NULL); + + if (result != noErr) + { + llinfos << "Error " << result << " on open." << llendl; + } + CFRelease(urlRef); + } + else + { + llinfos << "Error: couldn't create URL." << llendl; + } +} + +BOOL LLWindowMacOSX::dialog_color_picker ( F32 *r, F32 *g, F32 *b) +{ + BOOL retval = FALSE; + OSErr error = noErr; + NColorPickerInfo info; + + memset(&info, 0, sizeof(info)); + info.theColor.color.rgb.red = (UInt16)(*r * 65535.f); + info.theColor.color.rgb.green = (UInt16)(*g * 65535.f); + info.theColor.color.rgb.blue = (UInt16)(*b * 65535.f); + info.placeWhere = kCenterOnMainScreen; + + if(gWindowImplementation != NULL) + gWindowImplementation->beforeDialog(); + + error = NPickColor(&info); + + if(gWindowImplementation != NULL) + gWindowImplementation->afterDialog(); + + if (error == noErr) + { + retval = info.newColorChosen; + if (info.newColorChosen) + { + *r = ((float) info.theColor.color.rgb.red) / 65535.0; + *g = ((float) info.theColor.color.rgb.green) / 65535.0; + *b = ((float) info.theColor.color.rgb.blue) / 65535.0; + } + } + return (retval); +} + +static WindowRef dummywindowref = NULL; + +void *LLWindowMacOSX::getPlatformWindow() +{ + if(mWindow != NULL) + return (void*)mWindow; + + // If we're in fullscreen mode, there's no window pointer available. + // Since Mozilla needs one to function, create a dummy window here. + // Note that we will never destroy it, but since only one will be created per run of the application, that's okay. + + if(dummywindowref == NULL) + { + Rect window_rect = {100, 100, 200, 200}; + + dummywindowref = NewCWindow( + NULL, + &window_rect, + "\p", + false, // Create the window invisible. + zoomDocProc, // Window with a grow box and a zoom box + kLastWindowOfClass, // create it behind other windows + false, // no close box + 0); + } + + return (void*)dummywindowref; +} + +void LLWindowMacOSX::stopDockTileBounce() +{ + NMRemove(&mBounceRec); + mBounceTimer.stop(); +} + +// get a double value from a dictionary +static double getDictDouble (CFDictionaryRef refDict, CFStringRef key) +{ + double double_value; + CFNumberRef number_value = (CFNumberRef) CFDictionaryGetValue(refDict, key); + if (!number_value) // if can't get a number for the dictionary + return -1; // fail + if (!CFNumberGetValue(number_value, kCFNumberDoubleType, &double_value)) // or if cant convert it + return -1; // fail + return double_value; // otherwise return the long value +} + +// get a long value from a dictionary +static long getDictLong (CFDictionaryRef refDict, CFStringRef key) +{ + long int_value; + CFNumberRef number_value = (CFNumberRef) CFDictionaryGetValue(refDict, key); + if (!number_value) // if can't get a number for the dictionary + return -1; // fail + if (!CFNumberGetValue(number_value, kCFNumberLongType, &int_value)) // or if cant convert it + return -1; // fail + return int_value; // otherwise return the long value +} + +#endif // LL_DARWIN diff --git a/indra/llwindow/llwindowmacosx.h b/indra/llwindow/llwindowmacosx.h new file mode 100644 index 0000000000..1927f9bf31 --- /dev/null +++ b/indra/llwindow/llwindowmacosx.h @@ -0,0 +1,189 @@ +/** + * @file llwindowmacosx.h + * @brief Mac implementation of LLWindow class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLWINDOWMACOSX_H +#define LL_LLWINDOWMACOSX_H + +#include "llwindow.h" + +#include +#include + +// AssertMacros.h does bad things. +#undef verify +#undef check +#undef require + + +class LLWindowMacOSX : public LLWindow +{ +public: + /*virtual*/ void show(); + /*virtual*/ void hide(); + /*virtual*/ void close(); + /*virtual*/ BOOL getVisible(); + /*virtual*/ BOOL getMinimized(); + /*virtual*/ BOOL getMaximized(); + /*virtual*/ BOOL maximize(); + /*virtual*/ BOOL getFullscreen(); + /*virtual*/ BOOL getPosition(LLCoordScreen *position); + /*virtual*/ BOOL getSize(LLCoordScreen *size); + /*virtual*/ BOOL getSize(LLCoordWindow *size); + /*virtual*/ BOOL setPosition(LLCoordScreen position); + /*virtual*/ BOOL setSize(LLCoordScreen size); + /*virtual*/ BOOL switchContext(BOOL fullscreen, LLCoordScreen size, BOOL disable_vsync); + /*virtual*/ BOOL setCursorPosition(LLCoordWindow position); + /*virtual*/ BOOL getCursorPosition(LLCoordWindow *position); + /*virtual*/ void showCursor(); + /*virtual*/ void hideCursor(); + /*virtual*/ void showCursorFromMouseMove(); + /*virtual*/ void hideCursorUntilMouseMove(); + /*virtual*/ BOOL isCursorHidden(); + /*virtual*/ void setCursor(ECursorType cursor); + /*virtual*/ ECursorType getCursor(); + /*virtual*/ void captureMouse(); + /*virtual*/ void releaseMouse(); + /*virtual*/ void setMouseClipping( BOOL b ); + /*virtual*/ BOOL isClipboardTextAvailable(); + /*virtual*/ BOOL pasteTextFromClipboard(LLWString &dst); + /*virtual*/ BOOL copyTextToClipboard(const LLWString & src); + /*virtual*/ void flashIcon(F32 seconds); + /*virtual*/ F32 getGamma(); + /*virtual*/ BOOL setGamma(const F32 gamma); // Set the gamma + /*virtual*/ BOOL restoreGamma(); // Restore original gamma table (before updating gamma) + /*virtual*/ ESwapMethod getSwapMethod() { return mSwapMethod; } + /*virtual*/ void gatherInput(); + /*virtual*/ void delayInputProcessing() {}; + /*virtual*/ void swapBuffers(); + + /*virtual*/ LLString getTempFileName(); + /*virtual*/ void deleteFile( const char* file_name ); + /*virtual*/ S32 stat( const char* file_name, struct stat* stat_info ); + /*virtual*/ BOOL sendEmail(const char* address,const char* subject,const char* body_text,const char* attachment=NULL, const char* attachment_displayed_name=NULL); + + + // handy coordinate space conversion routines + /*virtual*/ BOOL convertCoords(LLCoordScreen from, LLCoordWindow *to); + /*virtual*/ BOOL convertCoords(LLCoordWindow from, LLCoordScreen *to); + /*virtual*/ BOOL convertCoords(LLCoordWindow from, LLCoordGL *to); + /*virtual*/ BOOL convertCoords(LLCoordGL from, LLCoordWindow *to); + /*virtual*/ BOOL convertCoords(LLCoordScreen from, LLCoordGL *to); + /*virtual*/ BOOL convertCoords(LLCoordGL from, LLCoordScreen *to); + + /*virtual*/ LLWindowResolution* getSupportedResolutions(S32 &num_resolutions); + /*virtual*/ F32 getNativeAspectRatio(); + /*virtual*/ F32 getPixelAspectRatio(); + /*virtual*/ void setNativeAspectRatio(F32 ratio) { mOverrideAspectRatio = ratio; } + + /*virtual*/ void beforeDialog(); + /*virtual*/ void afterDialog(); + + /*virtual*/ BOOL dialog_color_picker(F32 *r, F32 *g, F32 *b); + + /*virtual*/ void *getPlatformWindow(); + /*virtual*/ void bringToFront() {}; + +protected: + LLWindowMacOSX( + char *title, char *name, int x, int y, int width, int height, U32 flags, + BOOL fullscreen, BOOL clearBg, BOOL disable_vsync, BOOL use_gl, + BOOL ignore_pixel_depth); + ~LLWindowMacOSX(); + + void initCursors(); + BOOL isValid(); + void moveWindow(const LLCoordScreen& position,const LLCoordScreen& size); + + + // Changes display resolution. Returns true if successful + BOOL setDisplayResolution(S32 width, S32 height, S32 bits, S32 refresh); + + // Go back to last fullscreen display resolution. + BOOL setFullscreenResolution(); + + // Restore the display resolution to its value before we ran the app. + BOOL resetDisplayResolution(); + + void minimize(); + void restore(); + + BOOL shouldPostQuit() { return mPostQuit; } + + +protected: + // + // Platform specific methods + // + + // create or re-create the GL context/window. Called from the constructor and switchContext(). + BOOL createContext(int x, int y, int width, int height, int bits, BOOL fullscreen, BOOL disable_vsync); + void destroyContext(); + void setupFailure(const char* text, const char* caption, U32 type); + static pascal OSStatus staticEventHandler (EventHandlerCallRef myHandler, EventRef event, void* userData); + OSStatus eventHandler (EventHandlerCallRef myHandler, EventRef event); + void adjustCursorDecouple(bool warpingMouse = false); + void fixWindowSize(void); + void stopDockTileBounce(); + + + // + // Platform specific variables + // + WindowRef mWindow; + AGLContext mContext; + AGLPixelFormat mPixelFormat; + CGDirectDisplayID mDisplay; + CFDictionaryRef mOldDisplayMode; + EventLoopTimerRef mTimer; + EventHandlerUPP mEventHandlerUPP; + EventHandlerRef mGlobalHandlerRef; + EventHandlerRef mWindowHandlerRef; + Rect mOldMouseClip; // Screen rect to which the mouse cursor was globally constrained before we changed it in clipMouse() + Str255 mWindowTitle; + double mOriginalAspectRatio; + BOOL mSimulatedRightClick; + UInt32 mLastModifiers; + BOOL mHandsOffEvents; // When true, temporarially disable CarbonEvent processing. + // Used to allow event processing when putting up dialogs in fullscreen mode. + BOOL mCursorDecoupled; + S32 mCursorLastEventDeltaX; + S32 mCursorLastEventDeltaY; + BOOL mCursorIgnoreNextDelta; + BOOL mNeedsResize; // Constructor figured out the window is too big, it needs a resize. + LLCoordScreen mNeedsResizeSize; + F32 mOverrideAspectRatio; + BOOL mMinimized; + + F32 mBounceTime; + NMRec mBounceRec; + LLTimer mBounceTimer; + + friend class LLWindowManager; +}; + + +class LLSplashScreenMacOSX : public LLSplashScreen +{ +public: + LLSplashScreenMacOSX(); + virtual ~LLSplashScreenMacOSX(); + + /*virtual*/ void showImpl(); + /*virtual*/ void updateImpl(const char* mesg); + /*virtual*/ void hideImpl(); + +private: + WindowRef mWindow; +}; + +S32 OSMessageBoxMacOSX(const char* text, const char* caption, U32 type); + +void load_url_external(const char* url); +void shell_open( const char* file_path ); + +#endif //LL_LLWINDOWMACOSX_H diff --git a/indra/llwindow/llwindowmesaheadless.cpp b/indra/llwindow/llwindowmesaheadless.cpp new file mode 100644 index 0000000000..c924bb1efa --- /dev/null +++ b/indra/llwindow/llwindowmesaheadless.cpp @@ -0,0 +1,64 @@ +/** + * @file llwindowmesaheadless.cpp + * @brief Platform-dependent implementation of llwindow + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#if LL_MESA_HEADLESS + +#include "linden_common.h" +#include "indra_constants.h" + +#include "llwindowmesaheadless.h" +#include "llgl.h" +#include "llglheaders.h" + +#define MESA_CHANNEL_TYPE GL_UNSIGNED_SHORT +#define MESA_CHANNEL_SIZE 2 + +U16 *gMesaBuffer = NULL; + +// +// LLWindowMesaHeadless +// +LLWindowMesaHeadless::LLWindowMesaHeadless(char *title, char *name, S32 x, S32 y, S32 width, S32 height, + U32 flags, BOOL fullscreen, BOOL clearBg, + BOOL disable_vsync, BOOL use_gl, BOOL ignore_pixel_depth) + : LLWindow(fullscreen, flags) +{ + if (use_gl) + { + llinfos << "MESA Init" << llendl; + mMesaContext = OSMesaCreateContextExt( GL_RGBA, 32, 0, 0, NULL ); + + /* Allocate the image buffer */ + mMesaBuffer = new unsigned char [width * height * 4 * MESA_CHANNEL_SIZE]; + llassert(mMesaBuffer); + + gMesaBuffer = (U16*)mMesaBuffer; + + /* Bind the buffer to the context and make it current */ + if (!OSMesaMakeCurrent( mMesaContext, mMesaBuffer, MESA_CHANNEL_TYPE, width, height )) + { + llerrs << "MESA: OSMesaMakeCurrent failed!" << llendl; + } + + llverify(gGLManager.initGL()); + } +} + + +LLWindowMesaHeadless::~LLWindowMesaHeadless() +{ + delete mMesaBuffer; + OSMesaDestroyContext( mMesaContext ); +} + +void LLWindowMesaHeadless::swapBuffers() +{ + glFinish(); +} + +#endif diff --git a/indra/llwindow/llwindowmesaheadless.h b/indra/llwindow/llwindowmesaheadless.h new file mode 100644 index 0000000000..550a61d37a --- /dev/null +++ b/indra/llwindow/llwindowmesaheadless.h @@ -0,0 +1,104 @@ +/** + * @file llwindowmesaheadless.h + * @brief Windows implementation of LLWindow class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLWINDOWMESAHEADLESS_H +#define LL_LLWINDOWMESAHEADLESS_H + +#if LL_MESA_HEADLESS + +#include "llwindow.h" +#include "GL/osmesa.h" + +class LLWindowMesaHeadless : public LLWindow +{ +public: + /*virtual*/ void show() {}; + /*virtual*/ void hide() {}; + /*virtual*/ void close() {}; + /*virtual*/ BOOL getVisible() {return FALSE;}; + /*virtual*/ BOOL getMinimized() {return FALSE;}; + /*virtual*/ BOOL getMaximized() {return FALSE;}; + /*virtual*/ BOOL maximize() {return FALSE;}; + /*virtual*/ BOOL getFullscreen() {return FALSE;}; + /*virtual*/ BOOL getPosition(LLCoordScreen *position) {return FALSE;}; + /*virtual*/ BOOL getSize(LLCoordScreen *size) {return FALSE;}; + /*virtual*/ BOOL getSize(LLCoordWindow *size) {return FALSE;}; + /*virtual*/ BOOL setPosition(LLCoordScreen position) {return FALSE;}; + /*virtual*/ BOOL setSize(LLCoordScreen size) {return FALSE;}; + /*virtual*/ BOOL switchContext(BOOL fullscreen, LLCoordScreen size, BOOL disable_vsync) {return FALSE;}; + /*virtual*/ BOOL setCursorPosition(LLCoordWindow position) {return FALSE;}; + /*virtual*/ BOOL getCursorPosition(LLCoordWindow *position) {return FALSE;}; + /*virtual*/ void showCursor() {}; + /*virtual*/ void hideCursor() {}; + /*virtual*/ void showCursorFromMouseMove() {}; + /*virtual*/ void hideCursorUntilMouseMove() {}; + /*virtual*/ BOOL isCursorHidden() {return FALSE;}; + /*virtual*/ void setCursor(ECursorType cursor) {}; + //virtual ECursorType getCursor() { return mCurrentCursor; }; + /*virtual*/ void captureMouse() {}; + /*virtual*/ void releaseMouse() {}; + /*virtual*/ void setMouseClipping( BOOL b ) {}; + /*virtual*/ BOOL isClipboardTextAvailable() {return FALSE; }; + /*virtual*/ BOOL pasteTextFromClipboard(LLWString &dst) {return FALSE; }; + /*virtual*/ BOOL copyTextToClipboard(const LLWString &src) {return FALSE; }; + /*virtual*/ void flashIcon(F32 seconds) {}; + /*virtual*/ F32 getGamma() {return 1.0f; }; + /*virtual*/ BOOL setGamma(const F32 gamma) {return FALSE; }; // Set the gamma + /*virtual*/ BOOL restoreGamma() {return FALSE; }; // Restore original gamma table (before updating gamma) + //virtual ESwapMethod getSwapMethod() { return mSwapMethod; } + /*virtual*/ void gatherInput() {}; + /*virtual*/ void delayInputProcessing() {}; + /*virtual*/ void swapBuffers(); + + /*virtual*/ LLString getTempFileName() {return LLString(""); }; + /*virtual*/ void deleteFile( const char* file_name ) {}; + /*virtual*/ S32 stat( const char* file_name, struct stat* stat_info ) {return 0; }; + /*virtual*/ BOOL sendEmail(const char* address,const char* subject,const char* body_text,const char* attachment=NULL, const char* attachment_displayed_name=NULL) { return FALSE; }; + + + // handy coordinate space conversion routines + /*virtual*/ BOOL convertCoords(LLCoordScreen from, LLCoordWindow *to) { return FALSE; }; + /*virtual*/ BOOL convertCoords(LLCoordWindow from, LLCoordScreen *to) { return FALSE; }; + /*virtual*/ BOOL convertCoords(LLCoordWindow from, LLCoordGL *to) { return FALSE; }; + /*virtual*/ BOOL convertCoords(LLCoordGL from, LLCoordWindow *to) { return FALSE; }; + /*virtual*/ BOOL convertCoords(LLCoordScreen from, LLCoordGL *to) { return FALSE; }; + /*virtual*/ BOOL convertCoords(LLCoordGL from, LLCoordScreen *to) { return FALSE; }; + + /*virtual*/ LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) { return NULL; }; + /*virtual*/ F32 getNativeAspectRatio() { return 1.0f; }; + /*virtual*/ F32 getPixelAspectRatio() { return 1.0f; }; + /*virtual*/ void setNativeAspectRatio(F32 ratio) {} + + /*virtual*/ void *getPlatformWindow() { return 0; }; + /*virtual*/ void bringToFront() {}; + + LLWindowMesaHeadless(char *title, char *name, S32 x, S32 y, S32 width, S32 height, + U32 flags, BOOL fullscreen, BOOL clearBg, + BOOL disable_vsync, BOOL use_gl, BOOL ignore_pixel_depth); + ~LLWindowMesaHeadless(); + +private: + OSMesaContext mMesaContext; + unsigned char * mMesaBuffer; +}; + +class LLSplashScreenMesaHeadless : public LLSplashScreen +{ +public: + LLSplashScreenMesaHeadless() {}; + virtual ~LLSplashScreenMesaHeadless() {}; + + /*virtual*/ void showImpl() {}; + /*virtual*/ void updateImpl(const char* mesg) {}; + /*virtual*/ void hideImpl() {}; + +}; + +#endif + +#endif //LL_LLWINDOWMESAHEADLESS_H diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp new file mode 100644 index 0000000000..75793eb739 --- /dev/null +++ b/indra/llwindow/llwindowsdl.cpp @@ -0,0 +1,2487 @@ +/** + * @file llwindowsdl.cpp + * @brief Platform-dependent implementation of llwindow + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#if LL_SDL + +#include "linden_common.h" + +#include "llwindowsdl.h" +#include "llkeyboardsdl.h" +#include "llerror.h" +#include "llgl.h" +#include "llstring.h" +#include "lldir.h" + +#include "llglheaders.h" + +#include "indra_constants.h" + +#if LL_GTK +# include "gtk/gtk.h" +#endif // LL_GTK + +#if LL_LINUX +// not necessarily available on random SDL platforms, so #if LL_LINUX +// for execv(), waitpid(), fork() +# include +# include +# include +#endif // LL_LINUX + +extern BOOL gDebugWindowProc; + +// culled from winuser.h +//const S32 WHEEL_DELTA = 120; /* Value for rolling one detent */ +// On the Mac, the scroll wheel reports a delta of 1 for each detent. +// There's also acceleration for faster scrolling, based on a slider in the system preferences. +const S32 WHEEL_DELTA = 1; /* Value for rolling one detent */ +const S32 BITS_PER_PIXEL = 32; +const S32 MAX_NUM_RESOLUTIONS = 32; + +// +// LLWindowSDL +// + +#if LL_X11 +# include +// A global! Well, SDL isn't really designed for communicating +// with multiple physical X11 displays. Heck, it's not really +// designed for multiple X11 windows. +// So, we need this for the SDL/X11 event filter callback (which +// doesnt have a userdata parameter) and more. +static Display *SDL_Display = NULL; +static Window SDL_XWindowID = None; +#endif //LL_X11 + +// TOFU HACK -- (*exactly* the same hack as LLWindowMacOSX for the same reasons) +// For SDL, to put up an OS dialog in full screen mode, we must first switch OUT of full screen mode. +// The proper way to do this is to bracket the dialog with calls to beforeDialog() and afterDialog(), but these +// require a pointer to the LLWindowMacSDL object. Stash it here and maintain in the constructor and destructor. +// This assumes that there will be only one object of this class at any time. Hopefully this is true. +static LLWindowSDL *gWindowImplementation = NULL; + +static BOOL was_fullscreen = FALSE; + +// Cross-platform bits: + +void show_window_creation_error(const char* title) +{ + llwarns << title << llendl; + shell_open( "help/window_creation_error.html"); + /* + OSMessageBox( + "Second Life is unable to run because it can't set up your display.\n" + "We need to be able to make a 32-bit color window at 1024x768, with\n" + "an 8 bit alpha channel.\n" + "\n" + "First, be sure your monitor is set to True Color (32-bit) in\n" + "Start -> Control Panels -> Display -> Settings.\n" + "\n" + "Otherwise, this may be due to video card driver issues.\n" + "Please make sure you have the latest video card drivers installed.\n" + "ATI drivers are available at http://www.ati.com/\n" + "nVidia drivers are available at http://www.nvidia.com/\n" + "\n" + "If you continue to receive this message, contact customer service.", + title, + OSMB_OK); + */ +} + + +#if LL_GTK +// Check the runtime GTK version for goodness. +static BOOL maybe_do_gtk_diagnostics(void) +{ + static BOOL done_gtk_diag = FALSE; + static BOOL is_good = TRUE; + gtk_disable_setlocale(); + if ((!done_gtk_diag) && gtk_init_check(NULL, NULL)) + { + llinfos << "GTK Initialized." << llendl; + llinfos << "- Compiled against GTK version " + << GTK_MAJOR_VERSION << "." + << GTK_MINOR_VERSION << "." + << GTK_MICRO_VERSION << llendl; + llinfos << "- Running against GTK version " + << gtk_major_version << "." + << gtk_minor_version << "." + << gtk_micro_version << llendl; + gchar *gtk_warning; + gtk_warning = gtk_check_version(GTK_MAJOR_VERSION, + GTK_MINOR_VERSION, + GTK_MICRO_VERSION); + if (gtk_warning) + { + llwarns << "- GTK COMPATIBILITY WARNING: " << + gtk_warning << llendl; + is_good = FALSE; + } + + done_gtk_diag = TRUE; + } + return is_good; +} +#endif // LL_GTK + + +BOOL check_for_card(const char* RENDERER, const char* bad_card) +{ + if (!strncasecmp(RENDERER, bad_card, strlen(bad_card))) + { + char buffer[1024]; + sprintf(buffer, + "Your video card appears to be a %s, which Second Life does not support.\n" + "\n" + "Second Life requires a video card with 32 Mb of memory or more, as well as\n" + "multitexture support. We explicitly support nVidia GeForce 2 or better, \n" + "and ATI Radeon 8500 or better.\n" + "\n" + "If you own a supported card and continue to receive this message, try \n" + "updating to the latest video card drivers. Otherwise look in the\n" + "secondlife.com support section or e-mail technical support\n" + "\n" + "You can try to run Second Life, but it will probably crash or run\n" + "very slowly. Try anyway?", + bad_card); + S32 button = OSMessageBox(buffer, "Unsupported video card", OSMB_YESNO); + if (OSBTN_YES == button) + { + return FALSE; + } + else + { + return TRUE; + } + } + + return FALSE; +} + + + + +LLWindowSDL::LLWindowSDL(char *title, S32 x, S32 y, S32 width, + S32 height, U32 flags, + BOOL fullscreen, BOOL clearBg, + BOOL disable_vsync, BOOL use_gl, + BOOL ignore_pixel_depth) + : LLWindow(fullscreen, flags), mGamma(1.0f) +{ + // Initialize the keyboard + gKeyboard = new LLKeyboardSDL(); + // Note that we can't set up key-repeat until after SDL has init'd video + + // Ignore use_gl for now, only used for drones on PC + mWindow = NULL; + mCursorDecoupled = FALSE; + mCursorLastEventDeltaX = 0; + mCursorLastEventDeltaY = 0; + mCursorIgnoreNextDelta = FALSE; + mNeedsResize = FALSE; + mOverrideAspectRatio = 0.f; + mGrabbyKeyFlags = 0; + mReallyCapturedCount = 0; + mHaveInputFocus = -1; + mIsMinimized = -1; + + // Get the original aspect ratio of the main device. + mOriginalAspectRatio = 1024.0 / 768.0; // !!! FIXME //(double)CGDisplayPixelsWide(mDisplay) / (double)CGDisplayPixelsHigh(mDisplay); + + if (!title) + title = "SDL Window"; // !!! FIXME + + // Stash the window title + mWindowTitle = new char[strlen(title) + 1]; + strcpy(mWindowTitle, title); + + // Create the GL context and set it up for windowed or fullscreen, as appropriate. + if(createContext(x, y, width, height, 32, fullscreen, disable_vsync)) + { + gGLManager.initGL(); + + //start with arrow cursor + initCursors(); + setCursor( UI_CURSOR_ARROW ); + } + + stop_glerror(); + + // Stash an object pointer for OSMessageBox() + gWindowImplementation = this; + +#if LL_X11 + mFlashing = FALSE; +#endif // LL_X11 +} + +static SDL_Surface *Load_BMP_Resource(const char *basename) +{ + const int PATH_BUFFER_SIZE=1000; + char path_buffer[PATH_BUFFER_SIZE]; + + // Figure out where our BMP is living on the disk + snprintf(path_buffer, PATH_BUFFER_SIZE-1, "%s%sres-sdl%s%s", + gDirUtilp->getAppRODataDir().c_str(), + gDirUtilp->getDirDelimiter().c_str(), + gDirUtilp->getDirDelimiter().c_str(), + basename); + path_buffer[PATH_BUFFER_SIZE-1] = '\0'; + + return SDL_LoadBMP(path_buffer); +} + +BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, BOOL fullscreen, BOOL disable_vsync) +{ + //bool glneedsinit = false; +// const char *gllibname = null; // !!! fixme + + llinfos << "createContext, fullscreen=" << fullscreen << + " size=" << width << "x" << height << llendl; + + // captures don't survive contexts + mGrabbyKeyFlags = 0; + mReallyCapturedCount = 0; + + if (SDL_Init(SDL_INIT_VIDEO) < 0) + { + // !!! fixme: stderr? + llinfos << "sdl_init() failed! " << SDL_GetError() << llendl; + setupFailure("window creation error", "error", OSMB_OK); + return false; + } + + SDL_version c_sdl_version; + SDL_VERSION(&c_sdl_version); + llinfos << "Compiled against SDL " + << int(c_sdl_version.major) << "." + << int(c_sdl_version.minor) << "." + << int(c_sdl_version.patch) << llendl; + const SDL_version *r_sdl_version; + r_sdl_version = SDL_Linked_Version(); + llinfos << " Running against SDL " + << int(r_sdl_version->major) << "." + << int(r_sdl_version->minor) << "." + << int(r_sdl_version->patch) << llendl; + + const SDL_VideoInfo *videoInfo = SDL_GetVideoInfo( ); + if (!videoInfo) + { + llinfos << "SDL_GetVideoInfo() failed! " << SDL_GetError() << llendl; + setupFailure("Window creation error", "Error", OSMB_OK); + return FALSE; + } + + SDL_EnableUNICODE(1); + SDL_WM_SetCaption(mWindowTitle, mWindowTitle); + + // Set the application icon. + SDL_Surface *bmpsurface; + bmpsurface = Load_BMP_Resource("ll_icon.BMP"); + if (bmpsurface) + { + // This attempts to give a black-keyed mask to the icon. + SDL_SetColorKey(bmpsurface, + SDL_SRCCOLORKEY, + SDL_MapRGB(bmpsurface->format, 0,0,0) ); + SDL_WM_SetIcon(bmpsurface, NULL); + // The SDL examples cheerfully avoid freeing the icon + // surface, but I'm betting that's leaky. + SDL_FreeSurface(bmpsurface); + bmpsurface = NULL; + } + + // note: these SetAttributes make Tom's 9600-on-AMD64 fail to + // get a visual, but it's broken anyway when it does, and without + // these SetAttributes we might easily get an avoidable substandard + // visual to work with on most other machines. + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, (bits <= 16) ? 16 : 24); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, (bits <= 16) ? 1 : 8); + + // !!! FIXME: try to toggle vsync here? + + mFullscreen = fullscreen; + was_fullscreen = fullscreen; + + int sdlflags = SDL_OPENGL | SDL_RESIZABLE | SDL_ANYFORMAT; + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + + mSDLFlags = sdlflags; + + if (mFullscreen) + { + llinfos << "createContext: setting up fullscreen " << width << "x" << height << llendl; + + // If the requested width or height is 0, find the best default for the monitor. + if((width == 0) || (height == 0)) + { + // Scan through the list of modes, looking for one which has: + // height between 700 and 800 + // aspect ratio closest to the user's original mode + S32 resolutionCount = 0; + LLWindowResolution *resolutionList = getSupportedResolutions(resolutionCount); + + if(resolutionList != NULL) + { + F32 closestAspect = 0; + U32 closestHeight = 0; + U32 closestWidth = 0; + int i; + + llinfos << "createContext: searching for a display mode, original aspect is " << mOriginalAspectRatio << llendl; + + for(i=0; i < resolutionCount; i++) + { + F32 aspect = (F32)resolutionList[i].mWidth / (F32)resolutionList[i].mHeight; + + llinfos << "createContext: width " << resolutionList[i].mWidth << " height " << resolutionList[i].mHeight << " aspect " << aspect << llendl; + + if( (resolutionList[i].mHeight >= 700) && (resolutionList[i].mHeight <= 800) && + (fabs(aspect - mOriginalAspectRatio) < fabs(closestAspect - mOriginalAspectRatio))) + { + llinfos << " (new closest mode) " << llendl; + + // This is the closest mode we've seen yet. + closestWidth = resolutionList[i].mWidth; + closestHeight = resolutionList[i].mHeight; + closestAspect = aspect; + } + } + + width = closestWidth; + height = closestHeight; + } + } + + if((width == 0) || (height == 0)) + { + // Mode search failed for some reason. Use the old-school default. + width = 1024; + height = 768; + } + + mWindow = SDL_SetVideoMode(width, height, bits, sdlflags | SDL_FULLSCREEN); + + if (mWindow) + { + mFullscreen = TRUE; + was_fullscreen = TRUE; + mFullscreenWidth = mWindow->w; + mFullscreenHeight = mWindow->h; + mFullscreenBits = mWindow->format->BitsPerPixel; + mFullscreenRefresh = -1; + + llinfos << "Running at " << mFullscreenWidth + << "x" << mFullscreenHeight + << "x" << mFullscreenBits + << " @ " << mFullscreenRefresh + << llendl; + } + else + { + llwarns << "createContext: fullscreen creation failure. SDL: " << SDL_GetError() << llendl; + // No fullscreen support + mFullscreen = FALSE; + was_fullscreen = FALSE; + mFullscreenWidth = -1; + mFullscreenHeight = -1; + mFullscreenBits = -1; + mFullscreenRefresh = -1; + + char error[256]; + sprintf(error, "Unable to run fullscreen at %d x %d.\nRunning in window.", width, height); + OSMessageBox(error, "Error", OSMB_OK); + } + } + + if(!mFullscreen && (mWindow == NULL)) + { + if (width == 0) + width = 1024; + if (height == 0) + width = 768; + + llinfos << "createContext: creating window " << width << "x" << height << "x" << bits << llendl; + mWindow = SDL_SetVideoMode(width, height, bits, sdlflags); + + if (!mWindow) + { + llwarns << "createContext: window creation failure. SDL: " << SDL_GetError() << llendl; + setupFailure("Window creation error", "Error", OSMB_OK); + return FALSE; + } + } else if (!mFullscreen && (mWindow != NULL)) + { + llinfos << "createContext: SKIPPING - !fullscreen, but +mWindow " << width << "x" << height << "x" << bits << llendl; + } + + /*if (!load_all_glsyms(gllibname)) + { + SDL_QuitSubSystem(SDL_INIT_VIDEO); + return FALSE; + }*/ + + gGLManager.mVRAM = videoInfo->video_mem / 1024; + if (gGLManager.mVRAM != 0) + { + llinfos << "Detected " << gGLManager.mVRAM << "MB VRAM." << llendl; + } + // If VRAM is not detected, that is handled later + +#if 0 // !!! FIXME: all video cards suck under Linux. :) + // Since we just created the context, it needs to be set up. + glNeedsInit = TRUE; + if(glNeedsInit) + { + // Check for some explicitly unsupported cards. + const char* RENDERER = (const char*) glGetString(GL_RENDERER); + + const char* CARD_LIST[] = + { "RAGE 128", + "RIVA TNT2", + "Intel 810", + "3Dfx/Voodoo3", + "Radeon 7000", + "Radeon 7200", + "Radeon 7500", + "Radeon DDR", + "Radeon VE", + "GDI Generic" }; + const S32 CARD_COUNT = sizeof(CARD_LIST)/sizeof(char*); + + // Future candidates: + // ProSavage/Twister + // SuperSavage + + S32 i; + for (i = 0; i < CARD_COUNT; i++) + { + if (check_for_card(RENDERER, CARD_LIST[i])) + { + close(); + shell_open( "help/unsupported_card.html" ); + return FALSE; + } + } + } +#endif + + GLint depthBits, stencilBits, redBits, greenBits, blueBits, alphaBits; + + glGetIntegerv(GL_RED_BITS, &redBits); + glGetIntegerv(GL_GREEN_BITS, &greenBits); + glGetIntegerv(GL_BLUE_BITS, &blueBits); + glGetIntegerv(GL_ALPHA_BITS, &alphaBits); + glGetIntegerv(GL_DEPTH_BITS, &depthBits); + glGetIntegerv(GL_STENCIL_BITS, &stencilBits); + + llinfos << "GL buffer:" << llendl + llinfos << " Red Bits " << S32(redBits) << llendl + llinfos << " Green Bits " << S32(greenBits) << llendl + llinfos << " Blue Bits " << S32(blueBits) << llendl + llinfos << " Alpha Bits " << S32(alphaBits) << llendl + llinfos << " Depth Bits " << S32(depthBits) << llendl + llinfos << " Stencil Bits " << S32(stencilBits) << llendl; + + GLint colorBits = redBits + greenBits + blueBits + alphaBits; + // fixme: actually, it's REALLY important for picking that we get at + // least 8 bits each of red,green,blue. Alpha we can be a bit more + // relaxed about if we have to. + if (colorBits < 32) + { + close(); + setupFailure( + "Second Life requires True Color (32-bit) to run in a window.\n" + "Please go to Control Panels -> Display -> Settings and\n" + "set the screen to 32-bit color.\n" + "Alternately, if you choose to run fullscreen, Second Life\n" + "will automatically adjust the screen each time it runs.", + "Error", + OSMB_OK); + return FALSE; + } + +#if 0 // !!! FIXME: we're going to brave it for now... + if (alphaBits < 8) + { + close(); + setupFailure( + "Second Life is unable to run because it can't get an 8 bit alpha\n" + "channel. Usually this is due to video card driver issues.\n" + "Please make sure you have the latest video card drivers installed.\n" + "Also be sure your monitor is set to True Color (32-bit) in\n" + "Control Panels -> Display -> Settings.\n" + "If you continue to receive this message, contact customer service.", + "Error", + OSMB_OK); + return FALSE; + } +#endif + +#if LL_X11 + init_x11clipboard(); +#endif // LL_X11 + + // We need to do this here, once video is init'd + if (-1 == SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, + SDL_DEFAULT_REPEAT_INTERVAL)) + llwarns << "Couldn't enable key-repeat: " << SDL_GetError() <mX = 0; + position->mY = 0; + return TRUE; +} + +BOOL LLWindowSDL::getSize(LLCoordScreen *size) +{ + if (mWindow) + { + size->mX = mWindow->w; + size->mY = mWindow->h; + return (TRUE); + } + + llerrs << "LLWindowSDL::getPosition(): no window and not fullscreen!" << llendl; + return (FALSE); +} + +BOOL LLWindowSDL::getSize(LLCoordWindow *size) +{ + if (mWindow) + { + size->mX = mWindow->w; + size->mY = mWindow->h; + return (TRUE); + } + + llerrs << "LLWindowSDL::getPosition(): no window and not fullscreen!" << llendl; + return (FALSE); +} + +BOOL LLWindowSDL::setPosition(const LLCoordScreen position) +{ + if(mWindow) + { + // !!! FIXME... + //MacMoveWindow(mWindow, position.mX, position.mY, false); + } + + return TRUE; +} + +BOOL LLWindowSDL::setSize(const LLCoordScreen size) +{ + if(mWindow) + { + // !!! FIXME... + //SizeWindow(mWindow, size.mX, size.mY, true); + } + + return TRUE; +} + +void LLWindowSDL::swapBuffers() +{ + if (mWindow) + SDL_GL_SwapBuffers(); +} + +F32 LLWindowSDL::getGamma() +{ + return 1/mGamma; +} + +BOOL LLWindowSDL::restoreGamma() +{ + //CGDisplayRestoreColorSyncSettings(); + SDL_SetGamma(1.0f, 1.0f, 1.0f); + return true; +} + +BOOL LLWindowSDL::setGamma(const F32 gamma) +{ + mGamma = gamma; + if (mGamma == 0) mGamma = 0.1f; + mGamma = 1/mGamma; + SDL_SetGamma(mGamma, mGamma, mGamma); + return true; +} + +BOOL LLWindowSDL::isCursorHidden() +{ + return mCursorHidden; +} + + + +// Constrains the mouse to the window. +void LLWindowSDL::setMouseClipping( BOOL b ) +{ + //llinfos << "LLWindowSDL::setMouseClipping " << b << llendl; + // Just stash the requested state. We'll simulate this when the cursor is hidden by decoupling. + mIsMouseClipping = b; + //SDL_WM_GrabInput(b ? SDL_GRAB_ON : SDL_GRAB_OFF); + adjustCursorDecouple(); +} + +BOOL LLWindowSDL::setCursorPosition(const LLCoordWindow position) +{ + BOOL result = TRUE; + LLCoordScreen screen_pos; + + if (!convertCoords(position, &screen_pos)) + { + return FALSE; + } + + //llinfos << "setCursorPosition(" << screen_pos.mX << ", " << screen_pos.mY << ")" << llendl; + + SDL_WarpMouse(screen_pos.mX, screen_pos.mY); + + // Under certain circumstances, this will trigger us to decouple the cursor. + adjustCursorDecouple(true); + + return result; +} + +BOOL LLWindowSDL::getCursorPosition(LLCoordWindow *position) +{ + //Point cursor_point; + LLCoordScreen screen_pos; + + //GetMouse(&cursor_point); + int x, y; + SDL_GetMouseState(&x, &y); + + screen_pos.mX = x; + screen_pos.mY = y; + + return convertCoords(screen_pos, position); +} + +void LLWindowSDL::adjustCursorDecouple(bool warpingMouse) +{ + if(mIsMouseClipping && mCursorHidden) + { + if(warpingMouse) + { + // The cursor should be decoupled. Make sure it is. + if(!mCursorDecoupled) + { + // llinfos << "adjustCursorDecouple: decoupling cursor" << llendl; + //CGAssociateMouseAndMouseCursorPosition(false); + mCursorDecoupled = true; + mCursorIgnoreNextDelta = TRUE; + } + } + } + else + { + // The cursor should not be decoupled. Make sure it isn't. + if(mCursorDecoupled) + { + // llinfos << "adjustCursorDecouple: recoupling cursor" << llendl; + //CGAssociateMouseAndMouseCursorPosition(true); + mCursorDecoupled = false; + } + } +} + +F32 LLWindowSDL::getNativeAspectRatio() +{ +#if 0 + // RN: this hack presumes that the largest supported resolution is monitor-limited + // and that pixels in that mode are square, therefore defining the native aspect ratio + // of the monitor...this seems to work to a close approximation for most CRTs/LCDs + S32 num_resolutions; + LLWindowResolution* resolutions = getSupportedResolutions(num_resolutions); + + + return ((F32)resolutions[num_resolutions - 1].mWidth / (F32)resolutions[num_resolutions - 1].mHeight); + //rn: AC +#endif + + // MBW -- there are a couple of bad assumptions here. One is that the display list won't include + // ridiculous resolutions nobody would ever use. The other is that the list is in order. + + // New assumptions: + // - pixels are square (the only reasonable choice, really) + // - The user runs their display at a native resolution, so the resolution of the display + // when the app is launched has an aspect ratio that matches the monitor. + + //RN: actually, the assumption that there are no ridiculous resolutions (above the display's native capabilities) has + // been born out in my experience. + // Pixels are often not square (just ask the people who run their LCDs at 1024x768 or 800x600 when running fullscreen, like me) + // The ordering of display list is a blind assumption though, so we should check for max values + // Things might be different on the Mac though, so I'll defer to MBW + + // The constructor for this class grabs the aspect ratio of the monitor before doing any resolution + // switching, and stashes it in mOriginalAspectRatio. Here, we just return it. + + if (mOverrideAspectRatio > 0.f) + { + return mOverrideAspectRatio; + } + + return mOriginalAspectRatio; +} + +F32 LLWindowSDL::getPixelAspectRatio() +{ + F32 pixel_aspect = 1.f; + if (getFullscreen()) + { + LLCoordScreen screen_size; + getSize(&screen_size); + pixel_aspect = getNativeAspectRatio() * (F32)screen_size.mY / (F32)screen_size.mX; + } + + return pixel_aspect; +} + + +// some of this stuff is to support 'temporarily windowed' mode so that +// dialogs are still usable in fullscreen. HOWEVER! - it's not enabled/working +// yet. +static LLCoordScreen old_size; +static BOOL old_fullscreen; +void LLWindowSDL::beforeDialog() +{ + llinfos << "LLWindowSDL::beforeDialog()" << llendl; + + if (SDLReallyCaptureInput(FALSE) // must ungrab input so popup works! + && getSize(&old_size)) + { + old_fullscreen = was_fullscreen; + + if (old_fullscreen) + { + // NOT YET WORKING + //switchContext(FALSE, old_size, TRUE); + } + } + +#if LL_X11 + if (SDL_Display) + { + // Everything that we/SDL asked for should happen before we + // potentially hand control over to GTK. + XSync(SDL_Display, False); + } +#endif // LL_X11 + +#if LL_GTK + // this is a good time to grab some GTK version information for + // diagnostics + maybe_do_gtk_diagnostics(); +#endif // LL_GTK +} + +void LLWindowSDL::afterDialog() +{ + llinfos << "LLWindowSDL::afterDialog()" << llendl; + if (old_fullscreen && !was_fullscreen) + { + // NOT YET WORKING (see below) + //switchContext(TRUE, old_size, TRUE); + } + // !!! FIXME - we need to restore the GL context using + // LLViewerWindow::restoreGL() - but how?? +} + + +S32 LLWindowSDL::stat(const char* file_name, struct stat* stat_info) +{ + return ::stat( file_name, stat_info ); +} + +#if LL_X11 +// set/reset the XWMHints flag for 'urgency' that usually makes the icon flash +void LLWindowSDL::x11_set_urgent(BOOL urgent) +{ + if (SDL_Display && !mFullscreen) + { + XWMHints *wm_hints; + + llinfos << "X11 hint for urgency, " << urgent << llendl; + + wm_hints = XGetWMHints(SDL_Display, mSDL_XWindowID); + if (!wm_hints) + wm_hints = XAllocWMHints(); + + if (urgent) + wm_hints->flags |= XUrgencyHint; + else + wm_hints->flags &= ~XUrgencyHint; + + XSetWMHints(SDL_Display, mSDL_XWindowID, wm_hints); + XFree(wm_hints); + XSync(SDL_Display, False); + } +} +#endif // LL_X11 + +void LLWindowSDL::flashIcon(F32 seconds) +{ +#if !LL_X11 + llinfos << "Stub LLWindowSDL::flashIcon(" << seconds << ")" << llendl; +#else + llinfos << "X11 LLWindowSDL::flashIcon(" << seconds << ")" << llendl; + + F32 remaining_time = mFlashTimer.getRemainingTimeF32(); + if (remaining_time < seconds) + remaining_time = seconds; + mFlashTimer.reset(); + mFlashTimer.setTimerExpirySec(remaining_time); + + x11_set_urgent(TRUE); + mFlashing = TRUE; +#endif // LL_X11 +} + +#if LL_X11 +/* Lots of low-level X11 stuff to handle X11 copy-and-paste */ + +/* Our X11 clipboard support is a bit bizarre in various + organically-grown ways. Ideally it should be fixed to do + real string-type negotiation (this would make pasting to + xterm faster and pasting to UTF-8 emacs work properly), but + right now it has the rare and desirable trait of being + generally stable and working. */ + +/* PRIMARY and CLIPBOARD are the two main kinds of + X11 clipboard. A third are the CUT_BUFFERs which an + obsolete holdover from X10 days and use a quite orthogonal + mechanism. CLIPBOARD is the type whose design most + closely matches SL's own win32-alike explicit copy-and-paste + paradigm. + + Pragmatically we support all three to varying degrees. When + we paste into SL, it is strictly from CLIPBOARD. When we copy, + we support (to as full an extent as the clipboard content type + allows) CLIPBOARD, PRIMARY, and CUT_BUFFER0. + */ +#define SL_READWRITE_XCLIPBOARD_TYPE XInternAtom(SDL_Display, "CLIPBOARD", False) +#define SL_WRITE_XCLIPBOARD_TYPE XA_PRIMARY + +/* This is where our own private cutbuffer goes - we don't use + a regular cutbuffer (XA_CUT_BUFFER0 etc) for intermediate + storage because their use isn't really defined for holding UTF8. */ +#define SL_CUTBUFFER_TYPE XInternAtom(SDL_Display, "SECONDLIFE_CUTBUFFER", False) + +/* These defines, and convert_data/convert_x11clipboard, + mostly exist to support non-text or unusually-encoded + clipboard data, which we don't really have a need for at + the moment. */ +#define SDLCLIPTYPE(A, B, C, D) (int)((A<<24)|(B<<16)|(C<<8)|(D<<0)) +#define FORMAT_PREFIX "SECONDLIFE_x11clipboard_0x" + +typedef Atom x11clipboard_type; + +static +x11clipboard_type convert_format(int type) +{ + switch (type) + { + case SDLCLIPTYPE('T', 'E', 'X', 'T'): + // old-style X11 clipboard, strictly only ISO 8859-1 encoding + return XA_STRING; + case SDLCLIPTYPE('U', 'T', 'F', '8'): + // newer de-facto UTF8 clipboard atom + return XInternAtom(SDL_Display, "UTF8_STRING", False); + default: + { + /* completely arbitrary clipboard types... we don't actually use + these right now, and support is skeletal. */ + char format[sizeof(FORMAT_PREFIX)+8+1]; + + sprintf(format, "%s%08lx", FORMAT_PREFIX, (unsigned long)type); + return XInternAtom(SDL_Display, format, False); + } + } +} + +/* convert platform string to x11 clipboard format. for our + purposes this is pretty trivial right now. */ +static int +convert_data(int type, char *dst, const char *src, int srclen) +{ + int dstlen; + + dstlen = 0; + switch (type) + { + case SDLCLIPTYPE('T', 'E', 'X', 'T'): + case SDLCLIPTYPE('U', 'T', 'F', '8'): + if ( srclen == 0 ) + srclen = strlen(src); + + dstlen = srclen + 1; + + if ( dst ) // assume caller made it big enough by asking us + { + memcpy(dst, src, srclen); + dst[srclen] = '\0'; + } + break; + + default: + llwarns << "convert_data: Unknown medium type" << llendl; + break; + } + return(dstlen); +} + +/* Convert x11clipboard data to platform string. This too is + pretty trivial for our needs right now, and just about identical + to above. */ +static int +convert_x11clipboard(int type, char *dst, const char *src, int srclen) +{ + int dstlen; + + dstlen = 0; + switch (type) + { + case SDLCLIPTYPE('U', 'T', 'F', '8'): + case SDLCLIPTYPE('T', 'E', 'X', 'T'): + if ( srclen == 0 ) + srclen = strlen(src); + + dstlen = srclen + 1; + + if ( dst ) // assume caller made it big enough by asking us + { + memcpy(dst, src, srclen); + dst[srclen] = '\0'; + } + break; + + default: + llwarns << "convert_x11clipboard: Unknown medium type" << llendl; + break; + } + return dstlen; +} + +int +LLWindowSDL::is_empty_x11clipboard(void) +{ + int retval; + + Lock_Display(); + retval = ( XGetSelectionOwner(SDL_Display, SL_READWRITE_XCLIPBOARD_TYPE) == None ); + Unlock_Display(); + + return(retval); +} + +void +LLWindowSDL::put_x11clipboard(int type, int srclen, const char *src) +{ + x11clipboard_type format; + int dstlen; + char *dst; + + format = convert_format(type); + dstlen = convert_data(type, NULL, src, srclen); + + dst = (char *)malloc(dstlen); + if ( dst != NULL ) + { + Window root = DefaultRootWindow(SDL_Display); + Lock_Display(); + convert_data(type, dst, src, srclen); + // Cutbuffers are only allowed to have STRING atom types, + // but Emacs puts UTF8 inside them anyway. We cautiously + // don't. + if (type == SDLCLIPTYPE('T','E','X','T')) + { + // dstlen-1 so we don't include the trailing \0 + llinfos << "X11: Populating cutbuffer." <event.xevent; + + if ( (xevent.type == SelectionNotify)&& + (xevent.xselection.requestor == owner) ) + selection_response = 1; + } + } else { + llinfos << "X11: Waiting for SYSWM event..." << llendl; + } + } + llinfos << "X11: Clipboard arrived." <type != SDL_SYSWMEVENT ) + { + return(1); + } + + /* Handle window-manager specific clipboard events */ + switch (event->syswm.msg->event.xevent.type) { + /* Copy the selection from SL_CUTBUFFER_TYPE to the requested property */ + case SelectionRequest: { + XSelectionRequestEvent *req; + XEvent sevent; + int seln_format; + unsigned long nbytes; + unsigned long overflow; + unsigned char *seln_data; + + req = &event->syswm.msg->event.xevent.xselectionrequest; + sevent.xselection.type = SelectionNotify; + sevent.xselection.display = req->display; + sevent.xselection.selection = req->selection; + sevent.xselection.target = None; + sevent.xselection.property = None; + sevent.xselection.requestor = req->requestor; + sevent.xselection.time = req->time; + if ( XGetWindowProperty(SDL_Display, DefaultRootWindow(SDL_Display), + SL_CUTBUFFER_TYPE, 0, INT_MAX/4, False, req->target, + &sevent.xselection.target, &seln_format, + &nbytes, &overflow, &seln_data) == Success ) + { + if ( sevent.xselection.target == req->target) + { + if ( sevent.xselection.target == XA_STRING || + sevent.xselection.target == + convert_format(SDLCLIPTYPE('U','T','F','8')) ) + { + if ( seln_data[nbytes-1] == '\0' ) + --nbytes; + } + XChangeProperty(SDL_Display, req->requestor, req->property, + req->target, seln_format, PropModeReplace, + seln_data, nbytes); + sevent.xselection.property = req->property; +#define XA_TARGETS XInternAtom(SDL_Display, "TARGETS", False) + } else if (XA_TARGETS == req->target) { + /* only advertise what we currently support */ + const int num_supported = 3; + Atom supported[num_supported] = { + XA_STRING, // will be over-written below + XInternAtom(SDL_Display, "TEXT",False), + XA_TARGETS + }; + supported[0] = sevent.xselection.target; + XChangeProperty(SDL_Display, req->requestor, + req->property, XA_ATOM, 32, PropModeReplace, + (unsigned char*)supported, + num_supported); + sevent.xselection.property = req->property; + llinfos << "Clipboard: An app asked us what selections format we offer." << llendl; + } else { + llinfos << "Clipboard: An app requested an unsupported selection format " << req->target << ", we have " << sevent.xselection.target << llendl; + sevent.xselection.target = None; + } + XFree(seln_data); + } + int sendret = + XSendEvent(SDL_Display,req->requestor,False,0,&sevent); + if ((sendret==BadValue) || (sendret==BadWindow)) + llwarns << "Clipboard SendEvent failed" << llendl; + XSync(SDL_Display, False); + } + break; + } + + /* Post the event for X11 clipboard reading above */ + return(1); +} + +int +LLWindowSDL::init_x11clipboard(void) +{ + SDL_SysWMinfo info; + int retval; + + /* Grab the window manager specific information */ + retval = -1; + SDL_SetError("SDL is not running on known window manager"); + + SDL_VERSION(&info.version); + if ( SDL_GetWMInfo(&info) ) + { + /* Save the information for later use */ + if ( info.subsystem == SDL_SYSWM_X11 ) + { + SDL_Display = info.info.x11.display; + SDL_XWindowID = info.info.x11.wmwindow; + mSDL_XWindowID = info.info.x11.wmwindow; + Lock_Display = info.info.x11.lock_func; + Unlock_Display = info.info.x11.unlock_func; + + /* Enable the special window hook events */ + SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); + SDL_SetEventFilter(clipboard_filter_callback); + + retval = 0; + } + else + { + SDL_SetError("SDL is not running on X11"); + } + } + return(retval); +} + +void +LLWindowSDL::quit_x11clipboard(void) +{ + SDL_Display = NULL; + SDL_XWindowID = None; + mSDL_XWindowID = None; + Lock_Display = NULL; + Unlock_Display = NULL; + + SDL_SetEventFilter(NULL); // Stop custom event filtering +} + +/************************************************/ + +BOOL LLWindowSDL::isClipboardTextAvailable() +{ + return !is_empty_x11clipboard(); +} + +BOOL LLWindowSDL::pasteTextFromClipboard(LLWString &dst) +{ + int cliplen; // seems 1 or 2 bytes longer than expected + char *cliptext = NULL; + get_x11clipboard(SDLCLIPTYPE('U','T','F','8'), &cliplen, &cliptext); + if (cliptext) + { + llinfos << "X11: Got UTF8 clipboard text." << llendl; + // at some future time we can use cliplen instead of relying on \0, + // if we ever grok non-ascii, non-utf8 encodings on the clipboard. + std::string clip_str(cliptext); + // we can't necessarily trust the incoming text to be valid UTF-8, + // but utf8str_to_wstring() seems to do an appropriate level of + // validation for avoiding over-reads. + dst = utf8str_to_wstring(clip_str); + /*llinfos << "X11 pasteTextFromClipboard: cliplen=" << cliplen << + " strlen(cliptext)=" << strlen(cliptext) << + " clip_str.length()=" << clip_str.length() << + " dst.length()=" << dst.length() << + llendl;*/ + free(cliptext); + return TRUE; // success + } + get_x11clipboard(SDLCLIPTYPE('T','E','X','T'), &cliplen, &cliptext); + if (cliptext) + { + llinfos << "X11: Got ISO 8859-1 clipboard text." << llendl; + std::string clip_str(cliptext); + std::string utf8_str = rawstr_to_utf8(clip_str); + dst = utf8str_to_wstring(utf8_str); + free(cliptext); + } + return FALSE; // failure +} + +BOOL LLWindowSDL::copyTextToClipboard(const LLWString &s) +{ + std::string utf8text = wstring_to_utf8str(s); + const char* cstr = utf8text.c_str(); + int cstrlen = strlen(cstr); + int i; + for (i=0; iw; + int h = r->h; + if ((w >= 800) && (h >= 600)) + { + // make sure we don't add the same resolution multiple times! + if ( (mNumSupportedResolutions == 0) || + ((mSupportedResolutions[mNumSupportedResolutions-1].mWidth != w) && + (mSupportedResolutions[mNumSupportedResolutions-1].mHeight != h)) ) + { + mSupportedResolutions[mNumSupportedResolutions].mWidth = w; + mSupportedResolutions[mNumSupportedResolutions].mHeight = h; + mNumSupportedResolutions++; + } + } + } + } + } + + num_resolutions = mNumSupportedResolutions; + return mSupportedResolutions; +} + +BOOL LLWindowSDL::convertCoords(LLCoordGL from, LLCoordWindow *to) +{ + if (!to) + return FALSE; + + to->mX = from.mX; + to->mY = mWindow->h - from.mY - 1; + + return TRUE; +} + +BOOL LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordGL* to) +{ + if (!to) + return FALSE; + + to->mX = from.mX; + to->mY = mWindow->h - from.mY - 1; + + return TRUE; +} + +BOOL LLWindowSDL::convertCoords(LLCoordScreen from, LLCoordWindow* to) +{ + if (!to) + return FALSE; + + // In the fullscreen case, window and screen coordinates are the same. + to->mX = from.mX; + to->mY = from.mY; + return (TRUE); +} + +BOOL LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordScreen *to) +{ + if (!to) + return FALSE; + + // In the fullscreen case, window and screen coordinates are the same. + to->mX = from.mX; + to->mY = from.mY; + return (TRUE); +} + +BOOL LLWindowSDL::convertCoords(LLCoordScreen from, LLCoordGL *to) +{ + LLCoordWindow window_coord; + + return(convertCoords(from, &window_coord) && convertCoords(window_coord, to)); +} + +BOOL LLWindowSDL::convertCoords(LLCoordGL from, LLCoordScreen *to) +{ + LLCoordWindow window_coord; + + return(convertCoords(from, &window_coord) && convertCoords(window_coord, to)); +} + + + + +void LLWindowSDL::setupFailure(const char* text, const char* caption, U32 type) +{ + destroyContext(); + + OSMessageBox(text, caption, type); +} + +BOOL LLWindowSDL::SDLReallyCaptureInput(BOOL capture) +{ + // note: this used to be safe to call nestedly, but in the + // end that's not really a wise usage pattern, so don't. + + if (capture) + mReallyCapturedCount = 1; + else + mReallyCapturedCount = 0; + + SDL_GrabMode wantmode, newmode; + if (mReallyCapturedCount <= 0) // uncapture + { + wantmode = SDL_GRAB_OFF; + } else // capture + { + wantmode = SDL_GRAB_ON; + } + + if (mReallyCapturedCount < 0) // yuck, imbalance. + { + mReallyCapturedCount = 0; + llwarns << "ReallyCapture count was < 0" << llendl; + } + + if (!mFullscreen) /* only bother if we're windowed anyway */ + { +#if LL_X11 + if (SDL_Display) + { + /* we dirtily mix raw X11 with SDL so that our pointer + isn't (as often) constrained to the limits of the + window while grabbed, which feels nicer and + hopefully eliminates some reported 'sticky pointer' + problems. We use raw X11 instead of + SDL_WM_GrabInput() because the latter constrains + the pointer to the window and also steals all + *keyboard* input from the window manager, which was + frustrating users. */ + int result; + if (wantmode == SDL_GRAB_ON) + { + //llinfos << "X11 POINTER GRABBY" << llendl; + //newmode = SDL_WM_GrabInput(wantmode); + result = XGrabPointer(SDL_Display, mSDL_XWindowID, + True, 0, GrabModeAsync, + GrabModeAsync, + None, None, CurrentTime); + if (GrabSuccess == result) + newmode = SDL_GRAB_ON; + else + newmode = SDL_GRAB_OFF; + } else if (wantmode == SDL_GRAB_OFF) + { + //llinfos << "X11 POINTER UNGRABBY" << llendl; + newmode = SDL_GRAB_OFF; + //newmode = SDL_WM_GrabInput(SDL_GRAB_OFF); + + XUngrabPointer(SDL_Display, CurrentTime); + // Make sure the ungrab happens RIGHT NOW. + XSync(SDL_Display, False); + } else + { + newmode = SDL_GRAB_QUERY; // neutral + } + } else // not actually running on X11, for some reason + newmode = wantmode; +#endif // LL_X11 + } else { + // pretend we got what we wanted, when really we don't care. + newmode = wantmode; + } + + // return boolean success for whether we ended up in the desired state + return (capture && SDL_GRAB_ON==newmode) || + (!capture && SDL_GRAB_OFF==newmode); +} + +U32 LLWindowSDL::SDLCheckGrabbyKeys(SDLKey keysym, BOOL gain) +{ + /* part of the fix for SL-13243: Some popular window managers like + to totally eat alt-drag for the purposes of moving windows. We + spoil their day by acquiring the exclusive X11 mouse lock for as + long as LALT is held down, so the window manager can't easily + see what's happening. Tested successfully with Metacity. + And... do the same with CTRL, for other darn WMs. We don't + care about other metakeys as SL doesn't use them with dragging + (for now). */ + + /* We maintain a bitmap of critical keys which are up and down + instead of simply key-counting, because SDL sometimes reports + misbalanced keyup/keydown event pairs to us for whatever reason. */ + + U32 mask = 0; + switch (keysym) + { + case SDLK_LALT: + mask = 1U << 0; break; + case SDLK_LCTRL: + mask = 1U << 1; break; + case SDLK_RCTRL: + mask = 1U << 2; break; + default: + break; + } + + if (gain) + mGrabbyKeyFlags |= mask; + else + mGrabbyKeyFlags &= ~mask; + + //llinfos << "mGrabbyKeyFlags=" << mGrabbyKeyFlags << llendl; + + /* 0 means we don't need to mousegrab, otherwise grab. */ + return mGrabbyKeyFlags; +} + +void LLWindowSDL::gatherInput() +{ + const Uint32 CLICK_THRESHOLD = 300; // milliseconds + static int leftClick = 0; + static int rightClick = 0; + static Uint32 lastLeftDown = 0; + static Uint32 lastRightDown = 0; + SDL_Event event; + + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_MOUSEMOTION: + { + LLCoordWindow winCoord(event.button.x, event.button.y); + LLCoordGL openGlCoord; + convertCoords(winCoord, &openGlCoord); + MASK mask = gKeyboard->currentMask(TRUE); + mCallbacks->handleMouseMove(this, openGlCoord, mask); + break; + } + + case SDL_KEYDOWN: + gKeyboard->handleKeyDown(event.key.keysym.sym, event.key.keysym.mod); + // part of the fix for SL-13243 + if (SDLCheckGrabbyKeys(event.key.keysym.sym, TRUE) != 0) + SDLReallyCaptureInput(TRUE); + + if (event.key.keysym.unicode) + mCallbacks->handleUnicodeChar(event.key.keysym.unicode, gKeyboard->currentMask(FALSE)); + break; + + case SDL_KEYUP: + if (SDLCheckGrabbyKeys(event.key.keysym.sym, FALSE) == 0) + SDLReallyCaptureInput(FALSE); // part of the fix for SL-13243 + + // This is a testing hack to pop up a dialog when 4 is pressed + //if (event.key.keysym.sym == SDLK_4) + //OSMessageBox("a whole bunch of text goes right here, whee! test test test.", "this is the title!", OSMB_YESNO); + + gKeyboard->handleKeyUp(event.key.keysym.sym, event.key.keysym.mod); + break; + + case SDL_MOUSEBUTTONDOWN: + { + bool isDoubleClick = false; + LLCoordWindow winCoord(event.button.x, event.button.y); + LLCoordGL openGlCoord; + convertCoords(winCoord, &openGlCoord); + MASK mask = gKeyboard->currentMask(TRUE); + + if (event.button.button == SDL_BUTTON_LEFT) // SDL doesn't manage double clicking... + { + Uint32 now = SDL_GetTicks(); + if ((now - lastLeftDown) > CLICK_THRESHOLD) + leftClick = 1; + else + { + if (++leftClick >= 2) + { + leftClick = 0; + isDoubleClick = true; + } + } + lastLeftDown = now; + } + else if (event.button.button == SDL_BUTTON_RIGHT) + { + Uint32 now = SDL_GetTicks(); + if ((now - lastRightDown) > CLICK_THRESHOLD) + rightClick = 1; + else + { + if (++rightClick >= 2) + { + rightClick = 0; + isDoubleClick = true; + } + } + lastRightDown = now; + } + + if (event.button.button == SDL_BUTTON_LEFT) // left + { + if (isDoubleClick) + mCallbacks->handleDoubleClick(this, openGlCoord, mask); + else + mCallbacks->handleMouseDown(this, openGlCoord, mask); + } + + else if (event.button.button == SDL_BUTTON_RIGHT) // right ... yes, it's 3, not 2, in SDL... + { + // right double click isn't handled right now in Second Life ... if (isDoubleClick) + mCallbacks->handleRightMouseDown(this, openGlCoord, mask); + } + + else if (event.button.button == SDL_BUTTON_MIDDLE) // middle + ; // Middle mouse isn't handled right now in Second Life ... mCallbacks->handleMiddleMouseDown(this, openGlCoord, mask); + else if (event.button.button == 4) // mousewheel up...thanks to X11 for making SDL consider these "buttons". + mCallbacks->handleScrollWheel(this, -1); + else if (event.button.button == 5) // mousewheel down...thanks to X11 for making SDL consider these "buttons". + mCallbacks->handleScrollWheel(this, 1); + + break; + } + + case SDL_MOUSEBUTTONUP: + { + LLCoordWindow winCoord(event.button.x, event.button.y); + LLCoordGL openGlCoord; + convertCoords(winCoord, &openGlCoord); + MASK mask = gKeyboard->currentMask(TRUE); + + if (event.button.button == SDL_BUTTON_LEFT) // left + mCallbacks->handleMouseUp(this, openGlCoord, mask); + else if (event.button.button == SDL_BUTTON_RIGHT) // right ... yes, it's 3, not 2, in SDL... + mCallbacks->handleRightMouseUp(this, openGlCoord, mask); + else if (event.button.button == SDL_BUTTON_MIDDLE) // middle + ; // UNUSED IN SECOND LIFE RIGHT NOW mCallbacks->handleMiddleMouseUp(this, openGlCoord, mask); + + // don't handle mousewheel here... + + break; + } + + case SDL_VIDEOEXPOSE: // VIDEOEXPOSE doesn't specify the damage, but hey, it's OpenGL...repaint the whole thing! + mCallbacks->handlePaint(this, 0, 0, mWindow->w, mWindow->h); + break; + + case SDL_VIDEORESIZE: // !!! FIXME: handle this? + llinfos << "Handling a resize event: " << event.resize.w << + "x" << event.resize.h << llendl; + + // !!! FIXME: I'm not sure this is necessary! + mWindow = SDL_SetVideoMode(event.resize.w, event.resize.h, 32, mSDLFlags); + if (!mWindow) + { + // FIXME: More informative dialog? + llinfos << "Could not recreate context after resize! Quitting..." << llendl; + if(mCallbacks->handleCloseRequest(this)) + { + // Get the app to initiate cleanup. + mCallbacks->handleQuit(this); + // The app is responsible for calling destroyWindow when done with GL + } + break; + } + + mCallbacks->handleResize(this, event.resize.w, event.resize.h ); + break; + + case SDL_ACTIVEEVENT: + if (event.active.state & SDL_APPINPUTFOCUS) + { + // Note that for SDL (particularly on X11), keyboard + // and mouse focus are independent things. Here we are + // tracking keyboard focus state changes. + + // We have to do our own state massaging because SDL + // can send us two unfocus events in a row for example, + // which confuses the focus code [SL-24071]. + if (event.active.gain != mHaveInputFocus) + { + if (event.active.gain) + mCallbacks->handleFocus(this); + else + mCallbacks->handleFocusLost(this); + + mHaveInputFocus = !!event.active.gain; + } + } + if (event.active.state & SDL_APPACTIVE) + { + // Change in iconification/minimization state. + if ((!event.active.gain) != mIsMinimized) + { + mCallbacks->handleActivate(this, !!event.active.gain); + llinfos << "SDL deiconification state switched to " << BOOL(event.active.gain) << llendl; + + mIsMinimized = (!event.active.gain); + } + else + { + llinfos << "Ignored bogus redundant SDL deiconification state switch to " << BOOL(event.active.gain) << llendl; + } + } + break; + + case SDL_QUIT: + if(mCallbacks->handleCloseRequest(this)) + { + // Get the app to initiate cleanup. + mCallbacks->handleQuit(this); + // The app is responsible for calling destroyWindow when done with GL + } + break; + default: + //llinfos << "Unhandled SDL event type " << event.type << llendl; + break; + } + } + +#if LL_X11 + // This is a good time to stop flashing the icon if our mFlashTimer has + // expired. + if (mFlashing && mFlashTimer.hasExpired()) + { + x11_set_urgent(FALSE); + mFlashing = FALSE; + } +#endif // LL_X11 +} + +static SDL_Cursor *makeSDLCursorFromBMP(const char *filename, int hotx, int hoty) +{ + SDL_Cursor *sdlcursor = NULL; + SDL_Surface *bmpsurface; + + // Load cursor pixel data from BMP file + bmpsurface = Load_BMP_Resource(filename); + if (bmpsurface && bmpsurface->w%8==0) + { + SDL_Surface *cursurface; + llinfos << "Loaded cursor file " << filename << " " + << bmpsurface->w << "x" << bmpsurface->h << llendl; + cursurface = SDL_CreateRGBSurface (SDL_SWSURFACE, + bmpsurface->w, + bmpsurface->h, + 32, + 0xFFU, + 0xFF00U, + 0xFF0000U, + 0xFF000000U); + SDL_FillRect(cursurface, NULL, 0x00000000U); + + // Blit the cursor pixel data onto a 32-bit RGBA surface so we + // only have to cope with processing one type of pixel format. + if (0 == SDL_BlitSurface(bmpsurface, NULL, + cursurface, NULL)) + { + // n.b. we already checked that width is a multiple of 8. + const int bitmap_bytes = (cursurface->w * cursurface->h) / 8; + unsigned char *cursor_data = new unsigned char[bitmap_bytes]; + unsigned char *cursor_mask = new unsigned char[bitmap_bytes]; + memset(cursor_data, 0, bitmap_bytes); + memset(cursor_mask, 0, bitmap_bytes); + int i,j; + // Walk the RGBA cursor pixel data, extracting both data and + // mask to build SDL-friendly cursor bitmaps from. The mask + // is inferred by color-keying against 200,200,200 + for (i=0; ih; ++i) { + for (j=0; jw; ++j) { + unsigned char *pixelp = + ((unsigned char *)cursurface->pixels) + + cursurface->pitch * i + + j*cursurface->format->BytesPerPixel; + unsigned char srcred = pixelp[0]; + unsigned char srcgreen = pixelp[1]; + unsigned char srcblue = pixelp[2]; + BOOL mask_bit = (srcred != 200) + || (srcgreen != 200) + || (srcblue != 200); + BOOL data_bit = mask_bit && (srcgreen <= 80);//not 0x80 + unsigned char bit_offset = (cursurface->w/8) * i + + j/8; + cursor_data[bit_offset] |= (data_bit) << (7 - (j&7)); + cursor_mask[bit_offset] |= (mask_bit) << (7 - (j&7)); + } + } + sdlcursor = SDL_CreateCursor((Uint8*)cursor_data, + (Uint8*)cursor_mask, + cursurface->w, cursurface->h, + hotx, hoty); + delete[] cursor_data; + delete[] cursor_mask; + } else { + llwarns << "CURSOR BLIT FAILURE, cursurface: " << cursurface << llendl; + } + SDL_FreeSurface(cursurface); + SDL_FreeSurface(bmpsurface); + } else { + llwarns << "CURSOR LOAD FAILURE " << filename << llendl; + } + + return sdlcursor; +} + +void LLWindowSDL::setCursor(ECursorType cursor) +{ + if (mCurrentCursor != cursor) + { + if (cursor < UI_CURSOR_COUNT) + { + SDL_Cursor *sdlcursor = mSDLCursors[cursor]; + // Try to default to the arrow for any cursors that + // did not load correctly. + if (!sdlcursor && mSDLCursors[UI_CURSOR_ARROW]) + sdlcursor = mSDLCursors[UI_CURSOR_ARROW]; + if (sdlcursor) + SDL_SetCursor(sdlcursor); + } else { + llwarns << "Tried to set invalid cursor number " << cursor << llendl; + } + mCurrentCursor = cursor; + } +} + +ECursorType LLWindowSDL::getCursor() +{ + return mCurrentCursor; +} + +void LLWindowSDL::initCursors() +{ + int i; + // Blank the cursor pointer array for those we may miss. + for (i=0; ibeforeDialog(); + + gtk_disable_setlocale(); + if (gtk_init_check(NULL, NULL) + // We can NOT expect to combine GTK and SDL's aggressive fullscreen + && ((NULL==gWindowImplementation) || (!was_fullscreen)) + ) + { + GtkWidget *win = NULL; + + llinfos << "Creating a dialog because we're in windowed mode and GTK is happy." << llendl; + + GtkDialogFlags flags = GTK_DIALOG_MODAL; + GtkMessageType messagetype; + GtkButtonsType buttons; + switch (type) + { + default: + case OSMB_OK: + messagetype = GTK_MESSAGE_WARNING; + buttons = GTK_BUTTONS_OK; + break; + case OSMB_OKCANCEL: + messagetype = GTK_MESSAGE_QUESTION; + buttons = GTK_BUTTONS_OK_CANCEL; + break; + case OSMB_YESNO: + messagetype = GTK_MESSAGE_QUESTION; + buttons = GTK_BUTTONS_YES_NO; + break; + } + win = gtk_message_dialog_new(NULL, + flags, messagetype, buttons, + text); + +# if LL_X11 + // Make GTK tell the window manager to associate this + // dialog with our non-GTK SDL window, which should try + // to keep it on top etc. + if (SDL_XWindowID != None) + { + gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin + GdkWindow *gdkwin = gdk_window_foreign_new(SDL_XWindowID); + gdk_window_set_transient_for(GTK_WIDGET(win)->window, + gdkwin); + } +# endif //LL_X11 + + gtk_window_set_position(GTK_WINDOW(win), + GTK_WIN_POS_CENTER_ON_PARENT); + + gtk_window_set_type_hint(GTK_WINDOW(win), + GDK_WINDOW_TYPE_HINT_DIALOG); + + if (caption) + gtk_window_set_title(GTK_WINDOW(win), caption); + + gint response = GTK_RESPONSE_NONE; + g_signal_connect (win, + "response", + G_CALLBACK (response_callback), + &response); + + // we should be able to us a gtk_dialog_run(), but it's + // apparently not written to exist in a world without a higher + // gtk_main(), so we manage its signal/destruction outselves. + gtk_widget_show_all (win); + gtk_main(); + + //llinfos << "response: " << response << llendl; + switch (response) + { + case GTK_RESPONSE_OK: rtn = OSBTN_OK; break; + case GTK_RESPONSE_YES: rtn = OSBTN_YES; break; + case GTK_RESPONSE_NO: rtn = OSBTN_NO; break; + case GTK_RESPONSE_APPLY: rtn = OSBTN_OK; break; + case GTK_RESPONSE_NONE: + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_CLOSE: + case GTK_RESPONSE_DELETE_EVENT: + default: rtn = OSBTN_CANCEL; + } + } + else + { + fprintf(stderr, "MSGBOX: %s: %s\n", caption, text); + llinfos << "Skipping dialog because we're in fullscreen mode or GTK is not happy." << llendl; + rtn = OSBTN_OK; + } + + if(gWindowImplementation != NULL) + gWindowImplementation->afterDialog(); + + return rtn; +} + +static void color_changed_callback(GtkWidget *widget, + gpointer user_data) +{ + GtkColorSelection *colorsel = GTK_COLOR_SELECTION(widget); + GdkColor *colorp = (GdkColor*)user_data; + + gtk_color_selection_get_current_color(colorsel, colorp); +} + +BOOL LLWindowSDL::dialog_color_picker ( F32 *r, F32 *g, F32 *b) +{ + BOOL rtn = FALSE; + + beforeDialog(); + + gtk_disable_setlocale(); + if (gtk_init_check(NULL, NULL) + // We can NOT expect to combine GTK and SDL's aggressive fullscreen + && !was_fullscreen + ) + { + GtkWidget *win = NULL; + + win = gtk_color_selection_dialog_new(NULL); + +# if LL_X11 + // Get GTK to tell the window manager to associate this + // dialog with our non-GTK SDL window, which should try + // to keep it on top etc. + if (SDL_XWindowID != None) + { + gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin + GdkWindow *gdkwin = gdk_window_foreign_new(SDL_XWindowID); + gdk_window_set_transient_for(GTK_WIDGET(win)->window, + gdkwin); + } +# endif //LL_X11 + + GtkColorSelection *colorsel = GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG(win)->colorsel); + + GdkColor color, orig_color; + orig_color.red = guint16(65535 * *r); + orig_color.green= guint16(65535 * *g); + orig_color.blue = guint16(65535 * *b); + color = orig_color; + + gtk_color_selection_set_previous_color (colorsel, &color); + gtk_color_selection_set_current_color (colorsel, &color); + gtk_color_selection_set_has_palette (colorsel, TRUE); + gtk_color_selection_set_has_opacity_control(colorsel, FALSE); + + gint response = GTK_RESPONSE_NONE; + g_signal_connect (win, + "response", + G_CALLBACK (response_callback), + &response); + + g_signal_connect (G_OBJECT (colorsel), "color_changed", + G_CALLBACK (color_changed_callback), + &color); + + gtk_window_set_modal(GTK_WINDOW(win), TRUE); + gtk_widget_show_all(win); + // hide the help button - we don't service it. + gtk_widget_hide(GTK_COLOR_SELECTION_DIALOG(win)->help_button); + gtk_main(); + + if (response == GTK_RESPONSE_OK && + (orig_color.red != color.red + || orig_color.green != color.green + || orig_color.blue != color.blue) ) + { + *r = color.red / 65535.0f; + *g = color.green / 65535.0f; + *b = color.blue / 65535.0f; + rtn = TRUE; + } + } + + afterDialog(); + + return rtn; +} +#else +S32 OSMessageBoxSDL(const char* text, const char* caption, U32 type) +{ + fprintf(stderr, "MSGBOX: %s: %s\n", caption, text); + return 0; +} + +BOOL LLWindowSDL::dialog_color_picker ( F32 *r, F32 *g, F32 *b) +{ + return (FALSE); +} +#endif // LL_GTK + +// Open a URL with the user's default web browser. +// Must begin with protocol identifier. +void spawn_web_browser(const char* escaped_url) +{ + llinfos << "spawn_web_browser: " << escaped_url << llendl; + +#if LL_LINUX +# if LL_X11 + if (SDL_Display) // Just in case - before forking. + XSync(SDL_Display, False); +# endif // LL_X11 + + std::string cmd; + cmd = gDirUtilp->getAppRODataDir().c_str(); + cmd += gDirUtilp->getDirDelimiter().c_str(); + cmd += "launch_url.sh"; + char* const argv[] = {(char*)cmd.c_str(), (char*)escaped_url, NULL}; + + pid_t pid = fork(); + if (pid == 0) + { // child + // disconnect from stdin/stdout/stderr, or child will + // keep our output pipe undesirably alive if it outlives us. + close(0); + close(1); + close(2); + // end ourself by running the command + execv(cmd.c_str(), argv); + // if execv returns at all, there was a problem. + llwarns << "execv failure when trying to start " << cmd << llendl; + _exit(1); // _exit because we don't want atexit() clean-up! + } else { + if (pid > 0) + { + // parent - wait for child to die + int childExitStatus; + waitpid(pid, &childExitStatus, 0); + } else { + llwarns << "fork failure." << llendl; + } + } +#endif // LL_LINUX + + llinfos << "spawn_web_browser returning." << llendl; +} + +void shell_open( const char* file_path ) +{ + // !!! FIXME: + fprintf(stderr, "shell_open: %s\n", file_path); +} + +void *LLWindowSDL::getPlatformWindow() +{ +#if LL_X11 + // pointer to our static raw X window + return (void*)&SDL_XWindowID; +#else + // doubt we really want to return a high-level SDL structure here. + return NULL; +#endif +} + +void LLWindowSDL::bringToFront() +{ + // !!! FIXME: + fprintf(stderr, "bringToFront\n"); +} + +#endif // LL_SDL diff --git a/indra/llwindow/llwindowsdl.h b/indra/llwindow/llwindowsdl.h new file mode 100644 index 0000000000..704262061a --- /dev/null +++ b/indra/llwindow/llwindowsdl.h @@ -0,0 +1,198 @@ +/** + * @file llwindowsdl.h + * @brief SDL implementation of LLWindow class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLWINDOWSDL_H +#define LL_LLWINDOWSDL_H + +// Simple Directmedia Layer (http://libsdl.org/) implementation of LLWindow class + +#include "llwindow.h" + +#include "SDL/SDL.h" + +#if LL_X11 +// get X11-specific headers for use in low-level stuff like copy-and-paste support +#include "SDL/SDL_syswm.h" +#endif + +// AssertMacros.h does bad things. +#undef verify +#undef check +#undef require + + +class LLWindowSDL : public LLWindow +{ +public: + /*virtual*/ void show(); + /*virtual*/ void hide(); + /*virtual*/ void close(); + /*virtual*/ BOOL getVisible(); + /*virtual*/ BOOL getMinimized(); + /*virtual*/ BOOL getMaximized(); + /*virtual*/ BOOL maximize(); + /*virtual*/ BOOL getFullscreen(); + /*virtual*/ BOOL getPosition(LLCoordScreen *position); + /*virtual*/ BOOL getSize(LLCoordScreen *size); + /*virtual*/ BOOL getSize(LLCoordWindow *size); + /*virtual*/ BOOL setPosition(LLCoordScreen position); + /*virtual*/ BOOL setSize(LLCoordScreen size); + /*virtual*/ BOOL switchContext(BOOL fullscreen, LLCoordScreen size, BOOL disable_vsync); + /*virtual*/ BOOL setCursorPosition(LLCoordWindow position); + /*virtual*/ BOOL getCursorPosition(LLCoordWindow *position); + /*virtual*/ void showCursor(); + /*virtual*/ void hideCursor(); + /*virtual*/ void showCursorFromMouseMove(); + /*virtual*/ void hideCursorUntilMouseMove(); + /*virtual*/ BOOL isCursorHidden(); + /*virtual*/ void setCursor(ECursorType cursor); + /*virtual*/ ECursorType getCursor(); + /*virtual*/ void captureMouse(); + /*virtual*/ void releaseMouse(); + /*virtual*/ void setMouseClipping( BOOL b ); + /*virtual*/ BOOL isClipboardTextAvailable(); + /*virtual*/ BOOL pasteTextFromClipboard(LLWString &dst); + /*virtual*/ BOOL copyTextToClipboard(const LLWString & src); + /*virtual*/ void flashIcon(F32 seconds); + /*virtual*/ F32 getGamma(); + /*virtual*/ BOOL setGamma(const F32 gamma); // Set the gamma + /*virtual*/ BOOL restoreGamma(); // Restore original gamma table (before updating gamma) + /*virtual*/ ESwapMethod getSwapMethod() { return mSwapMethod; } + /*virtual*/ void gatherInput(); + /*virtual*/ void swapBuffers(); + + /*virtual*/ LLString getTempFileName(); + /*virtual*/ void deleteFile( const char* file_name ); + /*virtual*/ S32 stat( const char* file_name, struct stat* stat_info ); + /*virtual*/ BOOL sendEmail(const char* address,const char* subject,const char* body_text,const char* attachment=NULL, const char* attachment_displayed_name=NULL); + + /*virtual*/ void delayInputProcessing() { }; + + // handy coordinate space conversion routines + /*virtual*/ BOOL convertCoords(LLCoordScreen from, LLCoordWindow *to); + /*virtual*/ BOOL convertCoords(LLCoordWindow from, LLCoordScreen *to); + /*virtual*/ BOOL convertCoords(LLCoordWindow from, LLCoordGL *to); + /*virtual*/ BOOL convertCoords(LLCoordGL from, LLCoordWindow *to); + /*virtual*/ BOOL convertCoords(LLCoordScreen from, LLCoordGL *to); + /*virtual*/ BOOL convertCoords(LLCoordGL from, LLCoordScreen *to); + + /*virtual*/ LLWindowResolution* getSupportedResolutions(S32 &num_resolutions); + /*virtual*/ F32 getNativeAspectRatio(); + /*virtual*/ F32 getPixelAspectRatio(); + /*virtual*/ void setNativeAspectRatio(F32 ratio) { mOverrideAspectRatio = ratio; } + + /*virtual*/ void beforeDialog(); + /*virtual*/ void afterDialog(); + + /*virtual*/ BOOL dialog_color_picker(F32 *r, F32 *g, F32 *b); + + /*virtual*/ void *getPlatformWindow(); + /*virtual*/ void bringToFront(); + +protected: + LLWindowSDL( + char *title, int x, int y, int width, int height, U32 flags, + BOOL fullscreen, BOOL clearBg, BOOL disable_vsync, BOOL use_gl, + BOOL ignore_pixel_depth); + ~LLWindowSDL(); + + void initCursors(); + void quitCursors(); + BOOL isValid(); + void moveWindow(const LLCoordScreen& position,const LLCoordScreen& size); + + + // Changes display resolution. Returns true if successful + BOOL setDisplayResolution(S32 width, S32 height, S32 bits, S32 refresh); + + // Go back to last fullscreen display resolution. + BOOL setFullscreenResolution(); + + void minimize(); + void restore(); + + BOOL shouldPostQuit() { return mPostQuit; } + + +protected: + // + // Platform specific methods + // + + // create or re-create the GL context/window. Called from the constructor and switchContext(). + BOOL createContext(int x, int y, int width, int height, int bits, BOOL fullscreen, BOOL disable_vsync); + void destroyContext(); + void setupFailure(const char* text, const char* caption, U32 type); + void adjustCursorDecouple(bool warpingMouse = false); + void fixWindowSize(void); + U32 SDLCheckGrabbyKeys(SDLKey keysym, BOOL gain); + BOOL SDLReallyCaptureInput(BOOL capture); + + // + // Platform specific variables + // + U32 mGrabbyKeyFlags; + int mReallyCapturedCount; + SDL_Surface * mWindow; + char * mWindowTitle; + double mOriginalAspectRatio; + BOOL mCursorDecoupled; + S32 mCursorLastEventDeltaX; + S32 mCursorLastEventDeltaY; + BOOL mCursorIgnoreNextDelta; + BOOL mNeedsResize; // Constructor figured out the window is too big, it needs a resize. + LLCoordScreen mNeedsResizeSize; + F32 mOverrideAspectRatio; + F32 mGamma; + + int mSDLFlags; + + SDL_Cursor* mSDLCursors[UI_CURSOR_COUNT]; + int mHaveInputFocus; /* 0=no, 1=yes, else unknown */ + int mIsMinimized; /* 0=no, 1=yes, else unknown */ + + friend class LLWindowManager; + +#if LL_X11 +private: + // These are set up by the X11 clipboard initialization code + Window mSDL_XWindowID; + void (*Lock_Display)(void); + void (*Unlock_Display)(void); + // more X11 clipboard stuff + int init_x11clipboard(void); + void quit_x11clipboard(void); + int is_empty_x11clipboard(void); + void put_x11clipboard(int type, int srclen, const char *src); + void get_x11clipboard(int type, int *dstlen, char **dst); + void x11_set_urgent(BOOL urgent); + BOOL mFlashing; + LLTimer mFlashTimer; +#endif //LL_X11 + + +}; + + +class LLSplashScreenSDL : public LLSplashScreen +{ +public: + LLSplashScreenSDL(); + virtual ~LLSplashScreenSDL(); + + /*virtual*/ void showImpl(); + /*virtual*/ void updateImpl(const char* mesg); + /*virtual*/ void hideImpl(); +}; + +S32 OSMessageBoxSDL(const char* text, const char* caption, U32 type); + +void load_url_external(const char* url); +void shell_open( const char* file_path ); + +#endif //LL_LLWINDOWSDL_H diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp new file mode 100644 index 0000000000..ad56b97577 --- /dev/null +++ b/indra/llwindow/llwindowwin32.cpp @@ -0,0 +1,3247 @@ +/** + * @file llwindowwin32.cpp + * @brief Platform-dependent implementation of llwindow + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#if LL_WINDOWS && !LL_MESA_HEADLESS + +#include +#include +#include +#include // for _spawn +#include + +// Require DirectInput version 8 +#define DIRECTINPUT_VERSION 0x0800 +#include + +#include "llwindowwin32.h" +#include "llkeyboardwin32.h" +#include "llerror.h" +#include "llgl.h" +#include "llstring.h" +#include "lldir.h" + +#include "llglheaders.h" + +#include "indra_constants.h" + +// culled from winuser.h +const S32 WM_MOUSEWHEEL = 0x020A; +const S32 WHEEL_DELTA = 120; /* Value for rolling one detent */ +const S32 MAX_MESSAGE_PER_UPDATE = 20; +const S32 BITS_PER_PIXEL = 32; +const S32 MAX_NUM_RESOLUTIONS = 32; +const F32 ICON_FLASH_TIME = 0.5f; + +extern BOOL gDebugWindowProc; + +LPWSTR gIconResource = IDI_APPLICATION; + +LLW32MsgCallback gAsyncMsgCallback = NULL; + +// +// LLWindowWin32 +// + +void show_window_creation_error(const char* title) +{ + llwarns << title << llendl; + shell_open( "help/window_creation_error.html"); + /* + OSMessageBox( + "Second Life is unable to run because it can't set up your display.\n" + "We need to be able to make a 32-bit color window at 1024x768, with\n" + "an 8 bit alpha channel.\n" + "\n" + "First, be sure your monitor is set to True Color (32-bit) in\n" + "Start -> Control Panels -> Display -> Settings.\n" + "\n" + "Otherwise, this may be due to video card driver issues.\n" + "Please make sure you have the latest video card drivers installed.\n" + "ATI drivers are available at http://www.ati.com/\n" + "nVidia drivers are available at http://www.nvidia.com/\n" + "\n" + "If you continue to receive this message, contact customer service.", + title, + OSMB_OK); + */ +} + +BOOL check_for_card(const char* RENDERER, const char* bad_card) +{ + if (!strnicmp(RENDERER, bad_card, strlen(bad_card))) + { + char buffer[1024]; + sprintf(buffer, + "Your video card appears to be a %s, which Second Life does not support.\n" + "\n" + "Second Life requires a video card with 32 Mb of memory or more, as well as\n" + "multitexture support. We explicitly support nVidia GeForce 2 or better, \n" + "and ATI Radeon 8500 or better.\n" + "\n" + "If you own a supported card and continue to receive this message, try \n" + "updating to the latest video card drivers. Otherwise look in the\n" + "secondlife.com support section or e-mail technical support\n" + "\n" + "You can try to run Second Life, but it will probably crash or run\n" + "very slowly. Try anyway?", + bad_card); + S32 button = OSMessageBox(buffer, "Unsupported video card", OSMB_YESNO); + if (OSBTN_YES == button) + { + return FALSE; + } + else + { + return TRUE; + } + } + + return FALSE; +} + +//static +BOOL LLWindowWin32::sIsClassRegistered = FALSE; + + + +LPDIRECTINPUT8 g_pDI = NULL; +LPDIRECTINPUTDEVICE8 g_pJoystick = NULL; +BOOL CALLBACK EnumJoysticksCallback( const DIDEVICEINSTANCE* pdidInstance, + VOID* pContext ); +BOOL CALLBACK EnumObjectsCallback( const DIDEVICEOBJECTINSTANCE* pdidoi, + VOID* pContext ); + + +LLWindowWin32::LLWindowWin32(char *title, char *name, S32 x, S32 y, S32 width, + S32 height, U32 flags, + BOOL fullscreen, BOOL clearBg, + BOOL disable_vsync, BOOL use_gl, + BOOL ignore_pixel_depth) + : LLWindow(fullscreen, flags) +{ + mIconResource = gIconResource; + mOverrideAspectRatio = 0.f; + mNativeAspectRatio = 0.f; + mMousePositionModified = FALSE; + mInputProcessingPaused = FALSE; + + // Initialize the keyboard + gKeyboard = new LLKeyboardWin32(); + + GLuint pixel_format; + WNDCLASS wc; + DWORD dw_ex_style; + DWORD dw_style; + RECT window_rect; + + // Set the window title + if (!title) + { + mWindowTitle = new WCHAR[50]; + wsprintf(mWindowTitle, L"OpenGL Window"); + } + else + { + mWindowTitle = new WCHAR[256]; // Assume title length < 255 chars. + mbstowcs(mWindowTitle, title, 255); + mWindowTitle[255] = 0; + } + + // Set the window class name + if (!name) + { + mWindowClassName = new WCHAR[50]; + wsprintf(mWindowClassName, L"OpenGL Window"); + } + else + { + mWindowClassName = new WCHAR[256]; // Assume title length < 255 chars. + mbstowcs(mWindowClassName, name, 255); + mWindowClassName[255] = 0; + } + + + // We're not clipping yet + SetRect( &mOldMouseClip, 0, 0, 0, 0 ); + + // Make an instance of our window then define the window class + mhInstance = GetModuleHandle(NULL); + mWndProc = NULL; + + mSwapMethod = SWAP_METHOD_UNDEFINED; + + // No WPARAM yet. + mLastSizeWParam = 0; + + // Windows GDI rects don't include rightmost pixel + window_rect.left = (long) 0; + window_rect.right = (long) width; + window_rect.top = (long) 0; + window_rect.bottom = (long) height; + + // Grab screen size to sanitize the window + S32 window_border_y = GetSystemMetrics(SM_CYBORDER); + S32 virtual_screen_x = GetSystemMetrics(SM_XVIRTUALSCREEN); + S32 virtual_screen_y = GetSystemMetrics(SM_YVIRTUALSCREEN); + S32 virtual_screen_width = GetSystemMetrics(SM_CXVIRTUALSCREEN); + S32 virtual_screen_height = GetSystemMetrics(SM_CYVIRTUALSCREEN); + + if (x < virtual_screen_x) x = virtual_screen_x; + if (y < virtual_screen_y - window_border_y) y = virtual_screen_y - window_border_y; + + if (x + width > virtual_screen_x + virtual_screen_width) x = virtual_screen_x + virtual_screen_width - width; + if (y + height > virtual_screen_y + virtual_screen_height) y = virtual_screen_y + virtual_screen_height - height; + + if (!sIsClassRegistered) + { + // Force redraw when resized and create a private device context + + // Makes double click messages. + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS; + + // Set message handler function + wc.lpfnWndProc = (WNDPROC) mainWindowProc; + + // unused + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + + wc.hInstance = mhInstance; + wc.hIcon = LoadIcon(mhInstance, mIconResource); + + // We will set the cursor ourselves + wc.hCursor = NULL; + + // background color is not used + if (clearBg) + { + wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); + } + else + { + wc.hbrBackground = (HBRUSH) NULL; + } + + // we don't use windows menus + wc.lpszMenuName = NULL; + + wc.lpszClassName = mWindowClassName; + + if (!RegisterClass(&wc)) + { + OSMessageBox("RegisterClass failed", "Error", OSMB_OK); + return; + } + sIsClassRegistered = TRUE; + } + + //----------------------------------------------------------------------- + // Get the current refresh rate + //----------------------------------------------------------------------- + + DEVMODE dev_mode; + DWORD current_refresh; + if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dev_mode)) + { + current_refresh = dev_mode.dmDisplayFrequency; + mNativeAspectRatio = ((F32)dev_mode.dmPelsWidth) / ((F32)dev_mode.dmPelsHeight); + } + else + { + current_refresh = 60; + } + + //----------------------------------------------------------------------- + // Drop resolution and go fullscreen + // use a display mode with our desired size and depth, with a refresh + // rate as close at possible to the users' default + //----------------------------------------------------------------------- + if (mFullscreen) + { + BOOL success = FALSE; + DWORD closest_refresh = 0; + + for (S32 mode_num = 0;; mode_num++) + { + if (!EnumDisplaySettings(NULL, mode_num, &dev_mode)) + { + break; + } + + if (dev_mode.dmPelsWidth == width && + dev_mode.dmPelsHeight == height && + dev_mode.dmBitsPerPel == BITS_PER_PIXEL) + { + success = TRUE; + if ((dev_mode.dmDisplayFrequency - current_refresh) + < (closest_refresh - current_refresh)) + { + closest_refresh = dev_mode.dmDisplayFrequency; + } + } + } + + if (closest_refresh == 0) + { + llwarns << "Couldn't find display mode " << width << " by " << height << " at " << BITS_PER_PIXEL << " bits per pixel" << llendl; + success = FALSE; + } + + // If we found a good resolution, use it. + if (success) + { + success = setDisplayResolution(width, height, BITS_PER_PIXEL, closest_refresh); + } + + // Keep a copy of the actual current device mode in case we minimize + // and change the screen resolution. JC + EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dev_mode); + + // If it failed, we don't want to run fullscreen + if (success) + { + mFullscreen = TRUE; + mFullscreenWidth = dev_mode.dmPelsWidth; + mFullscreenHeight = dev_mode.dmPelsHeight; + mFullscreenBits = dev_mode.dmBitsPerPel; + mFullscreenRefresh = dev_mode.dmDisplayFrequency; + + llinfos << "Running at " << dev_mode.dmPelsWidth + << "x" << dev_mode.dmPelsHeight + << "x" << dev_mode.dmBitsPerPel + << " @ " << dev_mode.dmDisplayFrequency + << llendl; + } + else + { + mFullscreen = FALSE; + mFullscreenWidth = -1; + mFullscreenHeight = -1; + mFullscreenBits = -1; + mFullscreenRefresh = -1; + + char error[256]; + sprintf(error, "Unable to run fullscreen at %d x %d.\nRunning in window.", width, height); + OSMessageBox(error, "Error", OSMB_OK); + } + } + + //----------------------------------------------------------------------- + // Resize window to account for borders + //----------------------------------------------------------------------- + if (mFullscreen) + { + dw_ex_style = WS_EX_APPWINDOW; + dw_style = WS_POPUP; + + // Move window borders out not to cover window contents + AdjustWindowRectEx(&window_rect, dw_style, FALSE, dw_ex_style); + } + else + { + // Window with an edge + dw_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + dw_style = WS_OVERLAPPEDWINDOW; + } + + //----------------------------------------------------------------------- + // Create the window + // Microsoft help indicates that GL windows must be created with + // WS_CLIPSIBLINGS and WS_CLIPCHILDREN, but not CS_PARENTDC + //----------------------------------------------------------------------- + mWindowHandle = CreateWindowEx(dw_ex_style, + mWindowClassName, + mWindowTitle, + WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dw_style, + x, // x pos + y, // y pos + window_rect.right - window_rect.left, // width + window_rect.bottom - window_rect.top, // height + NULL, + NULL, + mhInstance, + NULL); + + if (!mWindowHandle) + { + DestroyWindow(mWindowHandle); + OSMessageBox("Window creation error", "Error", OSMB_OK); + return; + } + + // TODO: add this after resolving _WIN32_WINNT issue + // if (!fullscreen) + // { + // TRACKMOUSEEVENT track_mouse_event; + // track_mouse_event.cbSize = sizeof( TRACKMOUSEEVENT ); + // track_mouse_event.dwFlags = TME_LEAVE; + // track_mouse_event.hwndTrack = mWindowHandle; + // track_mouse_event.dwHoverTime = HOVER_DEFAULT; + // TrackMouseEvent( &track_mouse_event ); + // } + + + + S32 pfdflags = PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER; + if (use_gl) + { + pfdflags |= PFD_SUPPORT_OPENGL; + } + + //----------------------------------------------------------------------- + // Create GL drawing context + //----------------------------------------------------------------------- + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), + 1, + pfdflags, + PFD_TYPE_RGBA, + BITS_PER_PIXEL, + 0, 0, 0, 0, 0, 0, // RGB bits and shift, unused + 8, // alpha bits + 0, // alpha shift + 0, // accum bits + 0, 0, 0, 0, // accum RGBA + 24, // depth bits + 8, // stencil bits, avi added for stencil test + 0, + PFD_MAIN_PLANE, + 0, + 0, 0, 0 + }; + + if (!(mhDC = GetDC(mWindowHandle))) + { + close(); + OSMessageBox("Can't make GL device context", "Error", OSMB_OK); + return; + } + + if (!(pixel_format = ChoosePixelFormat(mhDC, &pfd))) + { + close(); + OSMessageBox("Can't find suitable pixel format", "Error", OSMB_OK); + return; + } + + // Verify what pixel format we actually received. + if (!DescribePixelFormat(mhDC, pixel_format, sizeof(PIXELFORMATDESCRIPTOR), + &pfd)) + { + close(); + OSMessageBox("Can't get pixel format description", "Error", OSMB_OK); + return; + } + + // sanity check pfd returned by Windows + if (!ignore_pixel_depth && (pfd.cColorBits < 32)) + { + close(); + OSMessageBox( + "Second Life requires True Color (32-bit) to run in a window.\n" + "Please go to Control Panels -> Display -> Settings and\n" + "set the screen to 32-bit color.\n" + "Alternately, if you choose to run fullscreen, Second Life\n" + "will automatically adjust the screen each time it runs.", + "Error", + OSMB_OK); + return; + } + + if (!ignore_pixel_depth && (pfd.cAlphaBits < 8)) + { + close(); + OSMessageBox( + "Second Life is unable to run because it can't get an 8 bit alpha\n" + "channel. Usually this is due to video card driver issues.\n" + "Please make sure you have the latest video card drivers installed.\n" + "Also be sure your monitor is set to True Color (32-bit) in\n" + "Control Panels -> Display -> Settings.\n" + "If you continue to receive this message, contact customer service.", + "Error", + OSMB_OK); + return; + } + + if (!SetPixelFormat(mhDC, pixel_format, &pfd)) + { + close(); + OSMessageBox("Can't set pixel format", "Error", OSMB_OK); + return; + } + + if (use_gl) + { + if (!(mhRC = wglCreateContext(mhDC))) + { + close(); + OSMessageBox("Can't create GL rendering context", "Error", OSMB_OK); + return; + } + + if (!wglMakeCurrent(mhDC, mhRC)) + { + close(); + OSMessageBox("Can't activate GL rendering context", "Error", OSMB_OK); + return; + } + + // Check for some explicitly unsupported cards. + const char* RENDERER = (const char*) glGetString(GL_RENDERER); + + const char* CARD_LIST[] = + { "RAGE 128", + "RIVA TNT2", + "Intel 810", + "3Dfx/Voodoo3", + "Radeon 7000", + "Radeon 7200", + "Radeon 7500", + "Radeon DDR", + "Radeon VE", + "GDI Generic" }; + const S32 CARD_COUNT = sizeof(CARD_LIST)/sizeof(char*); + + // Future candidates: + // ProSavage/Twister + // SuperSavage + + S32 i; + for (i = 0; i < CARD_COUNT; i++) + { + if (check_for_card(RENDERER, CARD_LIST[i])) + { + close(); + shell_open( "help/unsupported_card.html" ); + return; + } + } + + gGLManager.initWGL(); + + if (gGLManager.mHasWGLARBPixelFormat && (wglChoosePixelFormatARB != NULL)) + { + // OK, at this point, use the ARB wglChoosePixelFormatsARB function to see if we + // can get exactly what we want. + GLint attrib_list[256]; + S32 cur_attrib = 0; + + attrib_list[cur_attrib++] = WGL_DEPTH_BITS_ARB; + attrib_list[cur_attrib++] = 24; + + attrib_list[cur_attrib++] = WGL_STENCIL_BITS_ARB; + attrib_list[cur_attrib++] = 8; + + attrib_list[cur_attrib++] = WGL_DRAW_TO_WINDOW_ARB; + attrib_list[cur_attrib++] = GL_TRUE; + + attrib_list[cur_attrib++] = WGL_ACCELERATION_ARB; + attrib_list[cur_attrib++] = WGL_FULL_ACCELERATION_ARB; + + attrib_list[cur_attrib++] = WGL_SUPPORT_OPENGL_ARB; + attrib_list[cur_attrib++] = GL_TRUE; + + attrib_list[cur_attrib++] = WGL_DOUBLE_BUFFER_ARB; + attrib_list[cur_attrib++] = GL_TRUE; + + attrib_list[cur_attrib++] = WGL_COLOR_BITS_ARB; + attrib_list[cur_attrib++] = 24; + + attrib_list[cur_attrib++] = WGL_RED_BITS_ARB; + attrib_list[cur_attrib++] = 8; + + attrib_list[cur_attrib++] = WGL_GREEN_BITS_ARB; + attrib_list[cur_attrib++] = 8; + + attrib_list[cur_attrib++] = WGL_BLUE_BITS_ARB; + attrib_list[cur_attrib++] = 8; + + attrib_list[cur_attrib++] = WGL_ALPHA_BITS_ARB; + attrib_list[cur_attrib++] = 8; + + // End the list + attrib_list[cur_attrib++] = 0; + + GLint pixel_formats[256]; + U32 num_formats = 0; + + // First we try and get a 32 bit depth pixel format + BOOL result = wglChoosePixelFormatARB(mhDC, attrib_list, NULL, 256, pixel_formats, &num_formats); + if (!result) + { + close(); + show_window_creation_error("Error after wglChoosePixelFormatARB 32-bit"); + return; + } + + if (!num_formats) + { + llinfos << "No 32 bit z-buffer, trying 24 bits instead" << llendl; + // Try 24-bit format + attrib_list[1] = 24; + BOOL result = wglChoosePixelFormatARB(mhDC, attrib_list, NULL, 256, pixel_formats, &num_formats); + if (!result) + { + close(); + show_window_creation_error("Error after wglChoosePixelFormatARB 24-bit"); + return; + } + + if (!num_formats) + { + llwarns << "Couldn't get 24 bit z-buffer,trying 16 bits instead!" << llendl; + attrib_list[1] = 16; + BOOL result = wglChoosePixelFormatARB(mhDC, attrib_list, NULL, 256, pixel_formats, &num_formats); + if (!result || !num_formats) + { + close(); + show_window_creation_error("Error after wglChoosePixelFormatARB 16-bit"); + return; + } + } + + llinfos << "Choosing pixel formats: " << num_formats << " pixel formats returned" << llendl; + + pixel_format = pixel_formats[0]; + } + + DestroyWindow(mWindowHandle); + + mWindowHandle = CreateWindowEx(dw_ex_style, + mWindowClassName, + mWindowTitle, + WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dw_style, + x, // x pos + y, // y pos + window_rect.right - window_rect.left, // width + window_rect.bottom - window_rect.top, // height + NULL, + NULL, + mhInstance, + NULL); + + if (!(mhDC = GetDC(mWindowHandle))) + { + close(); + OSMessageBox("Can't make GL device context", "Error", OSMB_OK); + return; + } + + if (!SetPixelFormat(mhDC, pixel_format, &pfd)) + { + close(); + OSMessageBox("Can't set pixel format", "Error", OSMB_OK); + return; + } + + int swap_method = 0; + GLint swap_query = WGL_SWAP_METHOD_ARB; + + if (wglGetPixelFormatAttribivARB(mhDC, pixel_format, 0, 1, &swap_query, &swap_method)) + { + switch (swap_method) + { + case WGL_SWAP_EXCHANGE_ARB: + mSwapMethod = SWAP_METHOD_EXCHANGE; + llinfos << "Swap Method: Exchange" << llendl; + break; + case WGL_SWAP_COPY_ARB: + mSwapMethod = SWAP_METHOD_COPY; + llinfos << "Swap Method: Copy" << llendl; + break; + case WGL_SWAP_UNDEFINED_ARB: + mSwapMethod = SWAP_METHOD_UNDEFINED; + llinfos << "Swap Method: Undefined" << llendl; + break; + default: + mSwapMethod = SWAP_METHOD_UNDEFINED; + llinfos << "Swap Method: Unknown" << llendl; + break; + } + } + } + else + { + llwarns << "No wgl_ARB_pixel_format extension, using default ChoosePixelFormat!" << llendl; + } + + // Verify what pixel format we actually received. + if (!DescribePixelFormat(mhDC, pixel_format, sizeof(PIXELFORMATDESCRIPTOR), + &pfd)) + { + close(); + OSMessageBox("Can't get pixel format description", "Error", OSMB_OK); + return; + } + llinfos << "GL buffer: Color Bits " << S32(pfd.cColorBits) + << " Alpha Bits " << S32(pfd.cAlphaBits) + << " Depth Bits " << S32(pfd.cDepthBits) + << llendl; + + if (pfd.cColorBits < 32) + { + close(); + OSMessageBox( + "Second Life requires True Color (32-bit) to run in a window.\n" + "Please go to Control Panels -> Display -> Settings and\n" + "set the screen to 32-bit color.\n" + "Alternately, if you choose to run fullscreen, Second Life\n" + "will automatically adjust the screen each time it runs.", + "Error", + OSMB_OK); + return; + } + + if (pfd.cAlphaBits < 8) + { + close(); + OSMessageBox( + "Second Life is unable to run because it can't get an 8 bit alpha\n" + "channel. Usually this is due to video card driver issues.\n" + "Please make sure you have the latest video card drivers installed.\n" + "Also be sure your monitor is set to True Color (32-bit) in\n" + "Control Panels -> Display -> Settings.\n" + "If you continue to receive this message, contact customer service.", + "Error", + OSMB_OK); + return; + } + + if (!(mhRC = wglCreateContext(mhDC))) + { + close(); + OSMessageBox("Can't create GL rendering context", "Error", OSMB_OK); + return; + } + + if (!wglMakeCurrent(mhDC, mhRC)) + { + close(); + OSMessageBox("Can't activate GL rendering context", "Error", OSMB_OK); + return; + } + + if (!gGLManager.initGL()) + { + close(); + OSMessageBox( + "Second Life is unable to run because your video card drivers\n" + "are out of date or unsupported. Please make sure you have\n" + "the latest video card drivers installed.\n\n" + "If you continue to receive this message, contact customer service.", + "Error", + OSMB_OK); + return; + } + + // Disable vertical sync for swap + if (disable_vsync && wglSwapIntervalEXT) + { + llinfos << "Disabling vertical sync" << llendl; + wglSwapIntervalEXT(0); + } + else + { + llinfos << "Keeping vertical sync" << llendl; + } + + + // OK, let's get the current gamma information and store it off. + mCurrentGamma = 0.f; // Not set, default; + if (!GetDeviceGammaRamp(mhDC, mPrevGammaRamp)) + { + llwarns << "Unable to get device gamma ramp" << llendl; + } + + // Calculate what the current gamma is. From a posting by Garrett T. Bass, Get/SetDeviceGammaRamp Demystified + // http://apollo.iwt.uni-bielefeld.de/~ml_robot/OpenGL-04-2000/0058.html + + // We're going to assume that gamma's the same for all 3 channels, because I don't feel like doing it otherwise. + // Using the red channel. + + F32 Csum = 0.0; + S32 Ccount = 0; + for (i = 0; i < 256; i++) + { + if (i != 0 && mPrevGammaRamp[i] != 0 && mPrevGammaRamp[i] != 65536) + { + F64 B = (i % 256) / 256.0; + F64 A = mPrevGammaRamp[i] / 65536.0; + F32 C = (F32) ( log(A) / log(B) ); + Csum += C; + Ccount++; + } + } + mCurrentGamma = Csum / Ccount; + + llinfos << "Previous gamma: " << mCurrentGamma << llendl; + } + + + //store this pointer for wndProc callback + SetWindowLong(mWindowHandle, GWL_USERDATA, (U32)this); + + //start with arrow cursor + initCursors(); + setCursor( UI_CURSOR_ARROW ); + + // Direct Input + HRESULT hr; + + if( FAILED( hr = DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION, + IID_IDirectInput8, (VOID**)&g_pDI, NULL ) ) ) + { + llwarns << "Direct8InputCreate failed!" << llendl; + } + else + { + while(1) + { + // Look for a simple joystick we can use for this sample program. + if (FAILED( hr = g_pDI->EnumDevices( DI8DEVCLASS_GAMECTRL, + EnumJoysticksCallback, + NULL, DIEDFL_ATTACHEDONLY ) ) ) + break; + if (!g_pJoystick) + break; + if( FAILED( hr = g_pJoystick->SetDataFormat( &c_dfDIJoystick ) ) ) + break; + if( FAILED( hr = g_pJoystick->EnumObjects( EnumObjectsCallback, + (VOID*)mWindowHandle, DIDFT_ALL ) ) ) + break; + g_pJoystick->Acquire(); + break; + } + } + + SetTimer( mWindowHandle, 0, 1000 / 30, NULL ); // 30 fps timer + mJoyStickState = 0; + mJoyButtonState = 0; +} + + +LLWindowWin32::~LLWindowWin32() +{ + delete [] mWindowTitle; + mWindowTitle = NULL; + + delete [] mSupportedResolutions; + mSupportedResolutions = NULL; + + delete mWindowClassName; + mWindowClassName = NULL; +} + +void LLWindowWin32::show() +{ + ShowWindow(mWindowHandle, SW_SHOW); + SetForegroundWindow(mWindowHandle); + SetFocus(mWindowHandle); +} + +void LLWindowWin32::hide() +{ + setMouseClipping(FALSE); + ShowWindow(mWindowHandle, SW_HIDE); +} + +void LLWindowWin32::minimize() +{ + setMouseClipping(FALSE); + showCursor(); + ShowWindow(mWindowHandle, SW_MINIMIZE); +} + + +void LLWindowWin32::restore() +{ + ShowWindow(mWindowHandle, SW_RESTORE); + SetForegroundWindow(mWindowHandle); + SetFocus(mWindowHandle); +} + + +// close() destroys all OS-specific code associated with a window. +// Usually called from LLWindowManager::destroyWindow() +void LLWindowWin32::close() +{ + llinfos << "Closing LLWindowWin32" << llendl; + // Is window is already closed? + if (!mWindowHandle) + { + return; + } + + // Make sure cursor is visible and we haven't mangled the clipping state. + setMouseClipping(FALSE); + showCursor(); + + // Go back to screen mode written in the registry. + if (mFullscreen) + { + resetDisplayResolution(); + } + + // Clean up remaining GL state + llinfos << "Shutting down GL" << llendl; + gGLManager.shutdownGL(); + + llinfos << "Releasing Context" << llendl; + if (mhRC) + { + if (!wglMakeCurrent(NULL, NULL)) + { + llwarns << "Release of DC and RC failed" << llendl; + } + + if (!wglDeleteContext(mhRC)) + { + llwarns << "Release of rendering context failed" << llendl; + } + + mhRC = NULL; + } + + // Restore gamma to the system values. + restoreGamma(); + + if (mhDC && !ReleaseDC(mWindowHandle, mhDC)) + { + llwarns << "Release of ghDC failed" << llendl; + mhDC = NULL; + } + + llinfos << "Destroying Window" << llendl; + + // Don't process events in our mainWindowProc any longer. + SetWindowLong(mWindowHandle, GWL_USERDATA, NULL); + + // Make sure we don't leave a blank toolbar button. + ShowWindow(mWindowHandle, SW_HIDE); + + // This causes WM_DESTROY to be sent *immediately* + if (!DestroyWindow(mWindowHandle)) + { + OSMessageBox("DestroyWindow(mWindowHandle) failed", "Shutdown Error", OSMB_OK); + } + + mWindowHandle = NULL; +} + +BOOL LLWindowWin32::isValid() +{ + return (mWindowHandle != NULL); +} + +BOOL LLWindowWin32::getVisible() +{ + return (mWindowHandle && IsWindowVisible(mWindowHandle)); +} + +BOOL LLWindowWin32::getMinimized() +{ + return (mWindowHandle && IsIconic(mWindowHandle)); +} + +BOOL LLWindowWin32::getMaximized() +{ + return (mWindowHandle && IsZoomed(mWindowHandle)); +} + +BOOL LLWindowWin32::maximize() +{ + BOOL success = FALSE; + if (!mWindowHandle) return success; + + WINDOWPLACEMENT placement; + placement.length = sizeof(WINDOWPLACEMENT); + + success = GetWindowPlacement(mWindowHandle, &placement); + if (!success) return success; + + placement.showCmd = SW_MAXIMIZE; + + success = SetWindowPlacement(mWindowHandle, &placement); + return success; +} + +BOOL LLWindowWin32::getFullscreen() +{ + return mFullscreen; +} + +BOOL LLWindowWin32::getPosition(LLCoordScreen *position) +{ + RECT window_rect; + + if (!mWindowHandle || + !GetWindowRect(mWindowHandle, &window_rect) || + NULL == position) + { + return FALSE; + } + + position->mX = window_rect.left; + position->mY = window_rect.top; + return TRUE; +} + +BOOL LLWindowWin32::getSize(LLCoordScreen *size) +{ + RECT window_rect; + + if (!mWindowHandle || + !GetWindowRect(mWindowHandle, &window_rect) || + NULL == size) + { + return FALSE; + } + + size->mX = window_rect.right - window_rect.left; + size->mY = window_rect.bottom - window_rect.top; + return TRUE; +} + +BOOL LLWindowWin32::getSize(LLCoordWindow *size) +{ + RECT client_rect; + + if (!mWindowHandle || + !GetClientRect(mWindowHandle, &client_rect) || + NULL == size) + { + return FALSE; + } + + size->mX = client_rect.right - client_rect.left; + size->mY = client_rect.bottom - client_rect.top; + return TRUE; +} + +BOOL LLWindowWin32::setPosition(const LLCoordScreen position) +{ + LLCoordScreen size; + + if (!mWindowHandle) + { + return FALSE; + } + getSize(&size); + moveWindow(position, size); + return TRUE; +} + +BOOL LLWindowWin32::setSize(const LLCoordScreen size) +{ + LLCoordScreen position; + + getPosition(&position); + if (!mWindowHandle) + { + return FALSE; + } + + moveWindow(position, size); + return TRUE; +} + +// changing fullscreen resolution +BOOL LLWindowWin32::switchContext(BOOL fullscreen, LLCoordScreen size, BOOL disable_vsync) +{ + GLuint pixel_format; + DEVMODE dev_mode; + DWORD current_refresh; + DWORD dw_ex_style; + DWORD dw_style; + RECT window_rect; + S32 width = size.mX; + S32 height = size.mY; + + resetDisplayResolution(); + + if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dev_mode)) + { + current_refresh = dev_mode.dmDisplayFrequency; + } + else + { + current_refresh = 60; + } + + gGLManager.shutdownGL(); + //destroy gl context + if (mhRC) + { + if (!wglMakeCurrent(NULL, NULL)) + { + llwarns << "Release of DC and RC failed" << llendl; + } + + if (!wglDeleteContext(mhRC)) + { + llwarns << "Release of rendering context failed" << llendl; + } + + mhRC = NULL; + } + + if (fullscreen) + { + mFullscreen = TRUE; + BOOL success = FALSE; + DWORD closest_refresh = 0; + + for (S32 mode_num = 0;; mode_num++) + { + if (!EnumDisplaySettings(NULL, mode_num, &dev_mode)) + { + break; + } + + if (dev_mode.dmPelsWidth == width && + dev_mode.dmPelsHeight == height && + dev_mode.dmBitsPerPel == BITS_PER_PIXEL) + { + success = TRUE; + if ((dev_mode.dmDisplayFrequency - current_refresh) + < (closest_refresh - current_refresh)) + { + closest_refresh = dev_mode.dmDisplayFrequency; + } + } + } + + if (closest_refresh == 0) + { + llwarns << "Couldn't find display mode " << width << " by " << height << " at " << BITS_PER_PIXEL << " bits per pixel" << llendl; + return FALSE; + } + + // If we found a good resolution, use it. + if (success) + { + success = setDisplayResolution(width, height, BITS_PER_PIXEL, closest_refresh); + } + + // Keep a copy of the actual current device mode in case we minimize + // and change the screen resolution. JC + EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dev_mode); + + if (success) + { + mFullscreen = TRUE; + mFullscreenWidth = dev_mode.dmPelsWidth; + mFullscreenHeight = dev_mode.dmPelsHeight; + mFullscreenBits = dev_mode.dmBitsPerPel; + mFullscreenRefresh = dev_mode.dmDisplayFrequency; + + llinfos << "Running at " << dev_mode.dmPelsWidth + << "x" << dev_mode.dmPelsHeight + << "x" << dev_mode.dmBitsPerPel + << " @ " << dev_mode.dmDisplayFrequency + << llendl; + + window_rect.left = (long) 0; + window_rect.right = (long) width; // Windows GDI rects don't include rightmost pixel + window_rect.top = (long) 0; + window_rect.bottom = (long) height; + dw_ex_style = WS_EX_APPWINDOW; + dw_style = WS_POPUP; + + // Move window borders out not to cover window contents + AdjustWindowRectEx(&window_rect, dw_style, FALSE, dw_ex_style); + } + // If it failed, we don't want to run fullscreen + else + { + mFullscreen = FALSE; + mFullscreenWidth = -1; + mFullscreenHeight = -1; + mFullscreenBits = -1; + mFullscreenRefresh = -1; + + llinfos << "Unable to run fullscreen at " << width << "x" << height << llendl; + llinfos << "Running in window." << llendl; + return FALSE; + } + } + else + { + mFullscreen = FALSE; + window_rect.left = (long) 0; + window_rect.right = (long) width; // Windows GDI rects don't include rightmost pixel + window_rect.top = (long) 0; + window_rect.bottom = (long) height; + // Window with an edge + dw_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + dw_style = WS_OVERLAPPEDWINDOW; + } + + // don't post quit messages when destroying old windows + mPostQuit = FALSE; + + // create window + DestroyWindow(mWindowHandle); + mWindowHandle = CreateWindowEx(dw_ex_style, + mWindowClassName, + mWindowTitle, + WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dw_style, + window_rect.left, // x pos + window_rect.top, // y pos + window_rect.right - window_rect.left, // width + window_rect.bottom - window_rect.top, // height + NULL, + NULL, + mhInstance, + NULL); + + //----------------------------------------------------------------------- + // Create GL drawing context + //----------------------------------------------------------------------- + static PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), + 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, + BITS_PER_PIXEL, + 0, 0, 0, 0, 0, 0, // RGB bits and shift, unused + 8, // alpha bits + 0, // alpha shift + 0, // accum bits + 0, 0, 0, 0, // accum RGBA + 24, // depth bits + 8, // stencil bits, avi added for stencil test + 0, + PFD_MAIN_PLANE, + 0, + 0, 0, 0 + }; + + if (!(mhDC = GetDC(mWindowHandle))) + { + close(); + OSMessageBox("Can't make GL device context", "Error", OSMB_OK); + return FALSE; + } + + if (!(pixel_format = ChoosePixelFormat(mhDC, &pfd))) + { + close(); + OSMessageBox("Can't find suitable pixel format", "Error", OSMB_OK); + return FALSE; + } + + // Verify what pixel format we actually received. + if (!DescribePixelFormat(mhDC, pixel_format, sizeof(PIXELFORMATDESCRIPTOR), + &pfd)) + { + close(); + OSMessageBox("Can't get pixel format description", "Error", OSMB_OK); + return FALSE; + } + + if (pfd.cColorBits < 32) + { + close(); + OSMessageBox( + "Second Life requires True Color (32-bit) to run in a window.\n" + "Please go to Control Panels -> Display -> Settings and\n" + "set the screen to 32-bit color.\n" + "Alternately, if you choose to run fullscreen, Second Life\n" + "will automatically adjust the screen each time it runs.", + "Error", + OSMB_OK); + return FALSE; + } + + if (pfd.cAlphaBits < 8) + { + close(); + OSMessageBox( + "Second Life is unable to run because it can't get an 8 bit alpha\n" + "channel. Usually this is due to video card driver issues.\n" + "Please make sure you have the latest video card drivers installed.\n" + "Also be sure your monitor is set to True Color (32-bit) in\n" + "Control Panels -> Display -> Settings.\n" + "If you continue to receive this message, contact customer service.", + "Error", + OSMB_OK); + return FALSE; + } + + if (!SetPixelFormat(mhDC, pixel_format, &pfd)) + { + close(); + OSMessageBox("Can't set pixel format", "Error", OSMB_OK); + return FALSE; + } + + if (!(mhRC = wglCreateContext(mhDC))) + { + close(); + OSMessageBox("Can't create GL rendering context", "Error", OSMB_OK); + return FALSE; + } + + if (!wglMakeCurrent(mhDC, mhRC)) + { + close(); + OSMessageBox("Can't activate GL rendering context", "Error", OSMB_OK); + return FALSE; + } + + gGLManager.initWGL(); + + if (wglChoosePixelFormatARB) + { + // OK, at this point, use the ARB wglChoosePixelFormatsARB function to see if we + // can get exactly what we want. + GLint attrib_list[256]; + S32 cur_attrib = 0; + + attrib_list[cur_attrib++] = WGL_DEPTH_BITS_ARB; + attrib_list[cur_attrib++] = 24; + + attrib_list[cur_attrib++] = WGL_STENCIL_BITS_ARB; + attrib_list[cur_attrib++] = 8; + + attrib_list[cur_attrib++] = WGL_DRAW_TO_WINDOW_ARB; + attrib_list[cur_attrib++] = GL_TRUE; + + attrib_list[cur_attrib++] = WGL_ACCELERATION_ARB; + attrib_list[cur_attrib++] = WGL_FULL_ACCELERATION_ARB; + + attrib_list[cur_attrib++] = WGL_SUPPORT_OPENGL_ARB; + attrib_list[cur_attrib++] = GL_TRUE; + + attrib_list[cur_attrib++] = WGL_DOUBLE_BUFFER_ARB; + attrib_list[cur_attrib++] = GL_TRUE; + + attrib_list[cur_attrib++] = WGL_COLOR_BITS_ARB; + attrib_list[cur_attrib++] = 24; + + attrib_list[cur_attrib++] = WGL_RED_BITS_ARB; + attrib_list[cur_attrib++] = 8; + + attrib_list[cur_attrib++] = WGL_GREEN_BITS_ARB; + attrib_list[cur_attrib++] = 8; + + attrib_list[cur_attrib++] = WGL_BLUE_BITS_ARB; + attrib_list[cur_attrib++] = 8; + + attrib_list[cur_attrib++] = WGL_ALPHA_BITS_ARB; + attrib_list[cur_attrib++] = 8; + + // End the list + attrib_list[cur_attrib++] = 0; + + GLint pixel_formats[256]; + U32 num_formats = 0; + + // First we try and get a 32 bit depth pixel format + BOOL result = wglChoosePixelFormatARB(mhDC, attrib_list, NULL, 256, pixel_formats, &num_formats); + if (!result) + { + close(); + show_window_creation_error("Error after wglChoosePixelFormatARB 32-bit"); + return FALSE; + } + + if (!num_formats) + { + llinfos << "No 32 bit z-buffer, trying 24 bits instead" << llendl; + // Try 24-bit format + attrib_list[1] = 24; + BOOL result = wglChoosePixelFormatARB(mhDC, attrib_list, NULL, 256, pixel_formats, &num_formats); + if (!result) + { + close(); + show_window_creation_error("Error after wglChoosePixelFormatARB 24-bit"); + return FALSE; + } + + if (!num_formats) + { + llwarns << "Couldn't get 24 bit z-buffer,trying 16 bits instead!" << llendl; + attrib_list[1] = 16; + BOOL result = wglChoosePixelFormatARB(mhDC, attrib_list, NULL, 256, pixel_formats, &num_formats); + if (!result || !num_formats) + { + close(); + show_window_creation_error("Error after wglChoosePixelFormatARB 16-bit"); + return FALSE; + } + } + + llinfos << "Choosing pixel formats: " << num_formats << " pixel formats returned" << llendl; + + pixel_format = pixel_formats[0]; + } + + DestroyWindow(mWindowHandle); + mWindowHandle = CreateWindowEx(dw_ex_style, + mWindowClassName, + mWindowTitle, + WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dw_style, + window_rect.left, // x pos + window_rect.top, // y pos + window_rect.right - window_rect.left, // width + window_rect.bottom - window_rect.top, // height + NULL, + NULL, + mhInstance, + NULL); + + if (!(mhDC = GetDC(mWindowHandle))) + { + close(); + OSMessageBox("Can't make GL device context", "Error", OSMB_OK); + return FALSE; + } + + if (!SetPixelFormat(mhDC, pixel_format, &pfd)) + { + close(); + OSMessageBox("Can't set pixel format", "Error", OSMB_OK); + return FALSE; + } + + int swap_method = 0; + GLint swap_query = WGL_SWAP_METHOD_ARB; + + if (wglGetPixelFormatAttribivARB(mhDC, pixel_format, 0, 1, &swap_query, &swap_method)) + { + switch (swap_method) + { + case WGL_SWAP_EXCHANGE_ARB: + mSwapMethod = SWAP_METHOD_EXCHANGE; + llinfos << "Swap Method: Exchange" << llendl; + break; + case WGL_SWAP_COPY_ARB: + mSwapMethod = SWAP_METHOD_COPY; + llinfos << "Swap Method: Copy" << llendl; + break; + case WGL_SWAP_UNDEFINED_ARB: + mSwapMethod = SWAP_METHOD_UNDEFINED; + llinfos << "Swap Method: Undefined" << llendl; + break; + default: + mSwapMethod = SWAP_METHOD_UNDEFINED; + llinfos << "Swap Method: Unknown" << llendl; + break; + } + } + } + else + { + llwarns << "No wgl_ARB_pixel_format extension, using default ChoosePixelFormat!" << llendl; + } + + // Verify what pixel format we actually received. + if (!DescribePixelFormat(mhDC, pixel_format, sizeof(PIXELFORMATDESCRIPTOR), + &pfd)) + { + close(); + OSMessageBox("Can't get pixel format description", "Error", OSMB_OK); + return FALSE; + } + + llinfos << "GL buffer: Color Bits " << S32(pfd.cColorBits) + << " Alpha Bits " << S32(pfd.cAlphaBits) + << " Depth Bits " << S32(pfd.cDepthBits) + << llendl; + + if (pfd.cColorBits < 32) + { + close(); + OSMessageBox( + "Second Life requires True Color (32-bit) to run in a window.\n" + "Please go to Control Panels -> Display -> Settings and\n" + "set the screen to 32-bit color.\n" + "Alternately, if you choose to run fullscreen, Second Life\n" + "will automatically adjust the screen each time it runs.", + "Error", + OSMB_OK); + return FALSE; + } + + if (pfd.cAlphaBits < 8) + { + close(); + OSMessageBox( + "Second Life is unable to run because it can't get an 8 bit alpha\n" + "channel. Usually this is due to video card driver issues.\n" + "Please make sure you have the latest video card drivers installed.\n" + "Also be sure your monitor is set to True Color (32-bit) in\n" + "Control Panels -> Display -> Settings.\n" + "If you continue to receive this message, contact customer service.", + "Error", + OSMB_OK); + return FALSE; + } + + if (!(mhRC = wglCreateContext(mhDC))) + { + close(); + OSMessageBox("Can't create GL rendering context", "Error", OSMB_OK); + return FALSE; + } + + if (!wglMakeCurrent(mhDC, mhRC)) + { + close(); + OSMessageBox("Can't activate GL rendering context", "Error", OSMB_OK); + return FALSE; + } + + if (!gGLManager.initGL()) + { + close(); + OSMessageBox( + "Second Life is unable to run because your video card drivers\n" + "are out of date or unsupported. Please make sure you have\n" + "the latest video card drivers installed.\n\n" + "If you continue to receive this message, contact customer service.", + "Error", + OSMB_OK); + return FALSE; + } + + // Disable vertical sync for swap + if (disable_vsync && wglSwapIntervalEXT) + { + llinfos << "Disabling vertical sync" << llendl; + wglSwapIntervalEXT(0); + } + else + { + llinfos << "Keeping vertical sync" << llendl; + } + + SetWindowLong(mWindowHandle, GWL_USERDATA, (U32)this); + show(); + + // ok to post quit messages now + mPostQuit = TRUE; + return TRUE; +} + +void LLWindowWin32::moveWindow( const LLCoordScreen& position, const LLCoordScreen& size ) +{ + if( mIsMouseClipping ) + { + RECT client_rect_in_screen_space; + if( getClientRectInScreenSpace( &client_rect_in_screen_space ) ) + { + ClipCursor( &client_rect_in_screen_space ); + } + } + + MoveWindow(mWindowHandle, position.mX, position.mY, size.mX, size.mY, TRUE); +} + +BOOL LLWindowWin32::setCursorPosition(const LLCoordWindow position) +{ + LLCoordScreen screen_pos; + + mMousePositionModified = TRUE; + if (!mWindowHandle) + { + return FALSE; + } + + if (!convertCoords(position, &screen_pos)) + { + return FALSE; + } + + return SetCursorPos(screen_pos.mX, screen_pos.mY); +} + +BOOL LLWindowWin32::getCursorPosition(LLCoordWindow *position) +{ + POINT cursor_point; + LLCoordScreen screen_pos; + + if (!mWindowHandle || + !GetCursorPos(&cursor_point)) + { + return FALSE; + } + + screen_pos.mX = cursor_point.x; + screen_pos.mY = cursor_point.y; + + return convertCoords(screen_pos, position); +} + +void LLWindowWin32::hideCursor() +{ + while (ShowCursor(FALSE) >= 0) + { + // nothing, wait for cursor to push down + } + mCursorHidden = TRUE; + mHideCursorPermanent = TRUE; +} + +void LLWindowWin32::showCursor() +{ + // makes sure the cursor shows up + while (ShowCursor(TRUE) < 0) + { + // do nothing, wait for cursor to pop out + } + mCursorHidden = FALSE; + mHideCursorPermanent = FALSE; +} + +void LLWindowWin32::showCursorFromMouseMove() +{ + if (!mHideCursorPermanent) + { + showCursor(); + } +} + +void LLWindowWin32::hideCursorUntilMouseMove() +{ + if (!mHideCursorPermanent) + { + hideCursor(); + mHideCursorPermanent = FALSE; + } +} + +BOOL LLWindowWin32::isCursorHidden() +{ + return mCursorHidden; +} + + +HCURSOR LLWindowWin32::loadColorCursor(LPCTSTR name) +{ + return (HCURSOR)LoadImage(mhInstance, + name, + IMAGE_CURSOR, + 0, // default width + 0, // default height + LR_DEFAULTCOLOR); +} + + +void LLWindowWin32::initCursors() +{ + mCursor[ UI_CURSOR_ARROW ] = LoadCursor(NULL, IDC_ARROW); + mCursor[ UI_CURSOR_WAIT ] = LoadCursor(NULL, IDC_WAIT); + mCursor[ UI_CURSOR_HAND ] = LoadCursor(NULL, IDC_HAND); + mCursor[ UI_CURSOR_IBEAM ] = LoadCursor(NULL, IDC_IBEAM); + mCursor[ UI_CURSOR_CROSS ] = LoadCursor(NULL, IDC_CROSS); + mCursor[ UI_CURSOR_SIZENWSE ] = LoadCursor(NULL, IDC_SIZENWSE); + mCursor[ UI_CURSOR_SIZENESW ] = LoadCursor(NULL, IDC_SIZENESW); + mCursor[ UI_CURSOR_SIZEWE ] = LoadCursor(NULL, IDC_SIZEWE); + mCursor[ UI_CURSOR_SIZENS ] = LoadCursor(NULL, IDC_SIZENS); + mCursor[ UI_CURSOR_NO ] = LoadCursor(NULL, IDC_NO); + mCursor[ UI_CURSOR_WORKING ] = LoadCursor(NULL, IDC_APPSTARTING); + + HMODULE module = GetModuleHandle(NULL); + mCursor[ UI_CURSOR_TOOLGRAB ] = LoadCursor(module, TEXT("TOOLGRAB")); + mCursor[ UI_CURSOR_TOOLLAND ] = LoadCursor(module, TEXT("TOOLLAND")); + mCursor[ UI_CURSOR_TOOLFOCUS ] = LoadCursor(module, TEXT("TOOLFOCUS")); + mCursor[ UI_CURSOR_TOOLCREATE ] = LoadCursor(module, TEXT("TOOLCREATE")); + mCursor[ UI_CURSOR_ARROWDRAG ] = LoadCursor(module, TEXT("ARROWDRAG")); + mCursor[ UI_CURSOR_ARROWCOPY ] = LoadCursor(module, TEXT("ARROWCOPY")); + mCursor[ UI_CURSOR_ARROWDRAGMULTI ] = LoadCursor(module, TEXT("ARROWDRAGMULTI")); + mCursor[ UI_CURSOR_ARROWCOPYMULTI ] = LoadCursor(module, TEXT("ARROWCOPYMULTI")); + mCursor[ UI_CURSOR_NOLOCKED ] = LoadCursor(module, TEXT("NOLOCKED")); + mCursor[ UI_CURSOR_ARROWLOCKED ]= LoadCursor(module, TEXT("ARROWLOCKED")); + mCursor[ UI_CURSOR_GRABLOCKED ] = LoadCursor(module, TEXT("GRABLOCKED")); + mCursor[ UI_CURSOR_TOOLTRANSLATE ] = LoadCursor(module, TEXT("TOOLTRANSLATE")); + mCursor[ UI_CURSOR_TOOLROTATE ] = LoadCursor(module, TEXT("TOOLROTATE")); + mCursor[ UI_CURSOR_TOOLSCALE ] = LoadCursor(module, TEXT("TOOLSCALE")); + mCursor[ UI_CURSOR_TOOLCAMERA ] = LoadCursor(module, TEXT("TOOLCAMERA")); + mCursor[ UI_CURSOR_TOOLPAN ] = LoadCursor(module, TEXT("TOOLPAN")); + mCursor[ UI_CURSOR_TOOLZOOMIN ] = LoadCursor(module, TEXT("TOOLZOOMIN")); + mCursor[ UI_CURSOR_TOOLPICKOBJECT3 ] = LoadCursor(module, TEXT("TOOLPICKOBJECT3")); + mCursor[ UI_CURSOR_PIPETTE ] = LoadCursor(module, TEXT("TOOLPIPETTE")); + + // Color cursors + mCursor[UI_CURSOR_TOOLSIT] = loadColorCursor(TEXT("TOOLSIT")); + mCursor[UI_CURSOR_TOOLBUY] = loadColorCursor(TEXT("TOOLBUY")); + mCursor[UI_CURSOR_TOOLPAY] = loadColorCursor(TEXT("TOOLPAY")); + mCursor[UI_CURSOR_TOOLOPEN] = loadColorCursor(TEXT("TOOLOPEN")); + + // Note: custom cursors that are not found make LoadCursor() return NULL. + for( S32 i = 0; i < UI_CURSOR_COUNT; i++ ) + { + if( !mCursor[i] ) + { + mCursor[i] = LoadCursor(NULL, IDC_ARROW); + } + } +} + + + +void LLWindowWin32::setCursor(ECursorType cursor) +{ + if (cursor == UI_CURSOR_ARROW + && mBusyCount > 0) + { + cursor = UI_CURSOR_WORKING; + } + + if( mCurrentCursor != cursor ) + { + mCurrentCursor = cursor; + SetCursor( mCursor[cursor] ); + } +} + +ECursorType LLWindowWin32::getCursor() +{ + return mCurrentCursor; +} + +void LLWindowWin32::captureMouse() +{ + SetCapture(mWindowHandle); +} + +void LLWindowWin32::releaseMouse() +{ + ReleaseCapture(); +} + + +void LLWindowWin32::delayInputProcessing() +{ + mInputProcessingPaused = TRUE; +} + +void LLWindowWin32::gatherInput() +{ + MSG msg; + int msg_count = 0; + + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) && msg_count < MAX_MESSAGE_PER_UPDATE) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + msg_count++; + + if ( mInputProcessingPaused ) + { + break; + } + /* Attempted workaround for problem where typing fast and hitting + return would result in only part of the text being sent. JC + + BOOL key_posted = TranslateMessage(&msg); + DispatchMessage(&msg); + msg_count++; + + // If a key was translated, a WM_CHAR might have been posted to the end + // of the event queue. We need it immediately. + if (key_posted && msg.message == WM_KEYDOWN) + { + if (PeekMessage(&msg, NULL, WM_CHAR, WM_CHAR, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + msg_count++; + } + } + */ + + // For async host by name support. Really hacky. + if (gAsyncMsgCallback && (LL_WM_HOST_RESOLVED == msg.message)) + { + gAsyncMsgCallback(msg); + } + } + + mInputProcessingPaused = FALSE; + + // clear this once we've processed all mouse messages that might have occurred after + // we slammed the mouse position + mMousePositionModified = FALSE; +} + +LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_param, LPARAM l_param) +{ + LLWindowWin32 *window_imp = (LLWindowWin32 *)GetWindowLong(h_wnd, GWL_USERDATA); + + if (NULL != window_imp) + { + // Has user provided their own window callback? + if (NULL != window_imp->mWndProc) + { + if (!window_imp->mWndProc(h_wnd, u_msg, w_param, l_param)) + { + // user has handled window message + return 0; + } + } + + // Juggle to make sure we can get negative positions for when + // mouse is outside window. + LLCoordWindow window_coord((S32)(S16)LOWORD(l_param), (S32)(S16)HIWORD(l_param)); + + // This doesn't work, as LOWORD returns unsigned short. + //LLCoordWindow window_coord(LOWORD(l_param), HIWORD(l_param)); + LLCoordGL gl_coord; + + // pass along extended flag in mask + MASK mask = (l_param>>16 & KF_EXTENDED) ? MASK_EXTENDED : 0x0; + BOOL eat_keystroke = TRUE; + + switch(u_msg) + { + RECT update_rect; + S32 update_width; + S32 update_height; + + case WM_TIMER: + window_imp->updateJoystick( ); + break; + + case WM_PAINT: + GetUpdateRect(window_imp->mWindowHandle, &update_rect, FALSE); + update_width = update_rect.right - update_rect.left + 1; + update_height = update_rect.bottom - update_rect.top + 1; + window_imp->mCallbacks->handlePaint(window_imp, update_rect.left, update_rect.top, + update_width, update_height); + break; + case WM_PARENTNOTIFY: + u_msg = u_msg; + break; + + case WM_SETCURSOR: + // This message is sent whenever the cursor is moved in a window. + // You need to set the appropriate cursor appearance. + + // Only take control of cursor over client region of window + // This allows Windows(tm) to handle resize cursors, etc. + if (LOWORD(l_param) == HTCLIENT) + { + SetCursor(window_imp->mCursor[ window_imp->mCurrentCursor] ); + return 0; + } + break; + + case WM_ENTERMENULOOP: + window_imp->mCallbacks->handleWindowBlock(window_imp); + break; + + case WM_EXITMENULOOP: + window_imp->mCallbacks->handleWindowUnblock(window_imp); + break; + + case WM_ACTIVATEAPP: + { + // This message should be sent whenever the app gains or loses focus. + BOOL activating = (BOOL) w_param; + BOOL minimized = window_imp->getMinimized(); + + if (gDebugWindowProc) + { + llinfos << "WINDOWPROC ActivateApp " + << " activating " << S32(activating) + << " minimized " << S32(minimized) + << " fullscreen " << S32(window_imp->mFullscreen) + << llendl; + } + + if (window_imp->mFullscreen) + { + // When we run fullscreen, restoring or minimizing the app needs + // to switch the screen resolution + if (activating) + { + window_imp->setFullscreenResolution(); + window_imp->restore(); + } + else + { + window_imp->minimize(); + window_imp->resetDisplayResolution(); + } + } + break; + } + + case WM_ACTIVATE: + { + // Can be one of WA_ACTIVE, WA_CLICKACTIVE, or WA_INACTIVE + BOOL activating = (LOWORD(w_param) != WA_INACTIVE); + + BOOL minimized = BOOL(HIWORD(w_param)); + + // JC - I'm not sure why, but if we don't report that we handled the + // WM_ACTIVATE message, the WM_ACTIVATEAPP messages don't work + // properly when we run fullscreen. + if (gDebugWindowProc) + { + llinfos << "WINDOWPROC Activate " + << " activating " << S32(activating) + << " minimized " << S32(minimized) + << llendl; + } + + // Don't handle this. + break; + } + + case WM_QUERYOPEN: + // TODO: use this to return a nice icon + break; + + case WM_SYSCOMMAND: + switch(w_param) + { + case SC_KEYMENU: + // Disallow the ALT key from triggering the default system menu. + return 0; + + case SC_SCREENSAVE: + case SC_MONITORPOWER: + // eat screen save messages and prevent them! + return 0; + } + break; + + case WM_CLOSE: + // Will the app allow the window to close? + if (window_imp->mCallbacks->handleCloseRequest(window_imp)) + { + // Get the app to initiate cleanup. + window_imp->mCallbacks->handleQuit(window_imp); + // The app is responsible for calling destroyWindow when done with GL + } + return 0; + + case WM_DESTROY: + if (window_imp->shouldPostQuit()) + { + PostQuitMessage(0); // Posts WM_QUIT with an exit code of 0 + } + return 0; + + case WM_COMMAND: + if (!HIWORD(w_param)) // this message is from a menu + { + window_imp->mCallbacks->handleMenuSelect(window_imp, LOWORD(w_param)); + } + break; + + case WM_SYSKEYDOWN: + // allow system keys, such as ALT-F4 to be processed by Windows + eat_keystroke = FALSE; + case WM_KEYDOWN: + if (gDebugWindowProc) + { + llinfos << "Debug WindowProc WM_KEYDOWN " + << " key " << S32(w_param) + << llendl; + } + if (gKeyboard->handleKeyDown(w_param, mask) && eat_keystroke) + { + return 0; + } + // pass on to windows if we didn't handle it + break; + + case WM_SYSKEYUP: + eat_keystroke = FALSE; + case WM_KEYUP: + if (gDebugWindowProc) + { + llinfos << "Debug WindowProc WM_KEYUP " + << " key " << S32(w_param) + << llendl; + } + if (gKeyboard->handleKeyUp(w_param, mask) && eat_keystroke) + { + return 0; + } + + // pass on to windows + break; + + + case WM_CHAR: + // Should really use WM_UNICHAR eventually, but it requires a specific Windows version and I need + // to figure out how that works. - Doug + // llinfos << "WM_CHAR: " << w_param << llendl; + if (gDebugWindowProc) + { + llinfos << "Debug WindowProc WM_CHAR " + << " key " << S32(w_param) + << llendl; + } + if (window_imp->mCallbacks->handleUnicodeChar(w_param, gKeyboard->currentMask(FALSE))) + { + return 0; + } + break; + + case WM_LBUTTONDOWN: + { + // Because we move the cursor position in the app, we need to query + // to find out where the cursor at the time the event is handled. + // If we don't do this, many clicks could get buffered up, and if the + // first click changes the cursor position, all subsequent clicks + // will occur at the wrong location. JC + LLCoordWindow cursor_coord_window; + if (window_imp->mMousePositionModified) + { + window_imp->getCursorPosition(&cursor_coord_window); + window_imp->convertCoords(cursor_coord_window, &gl_coord); + } + else + { + window_imp->convertCoords(window_coord, &gl_coord); + } + MASK mask = gKeyboard->currentMask(TRUE); + if (window_imp->mCallbacks->handleMouseDown(window_imp, gl_coord, mask)) + { + return 0; + } + } + break; + + case WM_LBUTTONDBLCLK: + //RN: ignore right button double clicks for now + //case WM_RBUTTONDBLCLK: + { + // Because we move the cursor position in the app, we need to query + // to find out where the cursor at the time the event is handled. + // If we don't do this, many clicks could get buffered up, and if the + // first click changes the cursor position, all subsequent clicks + // will occur at the wrong location. JC + LLCoordWindow cursor_coord_window; + if (window_imp->mMousePositionModified) + { + window_imp->getCursorPosition(&cursor_coord_window); + window_imp->convertCoords(cursor_coord_window, &gl_coord); + } + else + { + window_imp->convertCoords(window_coord, &gl_coord); + } + MASK mask = gKeyboard->currentMask(TRUE); + if (window_imp->mCallbacks->handleDoubleClick(window_imp, gl_coord, mask) ) + { + return 0; + } + } + break; + + case WM_LBUTTONUP: + { + //if (gDebugClicks) + //{ + // llinfos << "WndProc left button up" << llendl; + //} + // Because we move the cursor position in the app, we need to query + // to find out where the cursor at the time the event is handled. + // If we don't do this, many clicks could get buffered up, and if the + // first click changes the cursor position, all subsequent clicks + // will occur at the wrong location. JC + LLCoordWindow cursor_coord_window; + if (window_imp->mMousePositionModified) + { + window_imp->getCursorPosition(&cursor_coord_window); + window_imp->convertCoords(cursor_coord_window, &gl_coord); + } + else + { + window_imp->convertCoords(window_coord, &gl_coord); + } + MASK mask = gKeyboard->currentMask(TRUE); + if (window_imp->mCallbacks->handleMouseUp(window_imp, gl_coord, mask)) + { + return 0; + } + } + break; + + case WM_RBUTTONDBLCLK: + case WM_RBUTTONDOWN: + { + // Because we move the cursor position in tllviewerhe app, we need to query + // to find out where the cursor at the time the event is handled. + // If we don't do this, many clicks could get buffered up, and if the + // first click changes the cursor position, all subsequent clicks + // will occur at the wrong location. JC + LLCoordWindow cursor_coord_window; + if (window_imp->mMousePositionModified) + { + window_imp->getCursorPosition(&cursor_coord_window); + window_imp->convertCoords(cursor_coord_window, &gl_coord); + } + else + { + window_imp->convertCoords(window_coord, &gl_coord); + } + MASK mask = gKeyboard->currentMask(TRUE); + if (window_imp->mCallbacks->handleRightMouseDown(window_imp, gl_coord, mask)) + { + return 0; + } + } + break; + + case WM_RBUTTONUP: + { + // Because we move the cursor position in the app, we need to query + // to find out where the cursor at the time the event is handled. + // If we don't do this, many clicks could get buffered up, and if the + // first click changes the cursor position, all subsequent clicks + // will occur at the wrong location. JC + LLCoordWindow cursor_coord_window; + if (window_imp->mMousePositionModified) + { + window_imp->getCursorPosition(&cursor_coord_window); + window_imp->convertCoords(cursor_coord_window, &gl_coord); + } + else + { + window_imp->convertCoords(window_coord, &gl_coord); + } + MASK mask = gKeyboard->currentMask(TRUE); + if (window_imp->mCallbacks->handleRightMouseUp(window_imp, gl_coord, mask)) + { + return 0; + } + } + break; + + case WM_MBUTTONDOWN: + // Handle middle button click + break; + + case WM_MOUSEWHEEL: + { + static short z_delta = 0; + + z_delta += HIWORD(w_param); + // cout << "z_delta " << z_delta << endl; + + // current mouse wheels report changes in increments of zDelta (+120, -120) + // Future, higher resolution mouse wheels may report smaller deltas. + // So we sum the deltas and only act when we've exceeded WHEEL_DELTA + // + // If the user rapidly spins the wheel, we can get messages with + // large deltas, like 480 or so. Thus we need to scroll more quickly. + if (z_delta <= -WHEEL_DELTA || WHEEL_DELTA <= z_delta) + { + window_imp->mCallbacks->handleScrollWheel(window_imp, -z_delta / WHEEL_DELTA); + z_delta = 0; + } + return 0; + } + /* + // TODO: add this after resolving _WIN32_WINNT issue + case WM_MOUSELEAVE: + { + window_imp->mCallbacks->handleMouseLeave(window_imp); + + // TRACKMOUSEEVENT track_mouse_event; + // track_mouse_event.cbSize = sizeof( TRACKMOUSEEVENT ); + // track_mouse_event.dwFlags = TME_LEAVE; + // track_mouse_event.hwndTrack = h_wnd; + // track_mouse_event.dwHoverTime = HOVER_DEFAULT; + // TrackMouseEvent( &track_mouse_event ); + return 0; + } + */ + // Handle mouse movement within the window + case WM_MOUSEMOVE: + { + window_imp->convertCoords(window_coord, &gl_coord); + MASK mask = gKeyboard->currentMask(TRUE); + window_imp->mCallbacks->handleMouseMove(window_imp, gl_coord, mask); + return 0; + } + + case WM_SIZE: + { + S32 width = S32( LOWORD(l_param) ); + S32 height = S32( HIWORD(l_param) ); + + if (gDebugWindowProc) + { + BOOL maximized = ( w_param == SIZE_MAXIMIZED ); + BOOL restored = ( w_param == SIZE_RESTORED ); + BOOL minimized = ( w_param == SIZE_MINIMIZED ); + + llinfos << "WINDOWPROC Size " + << width << "x" << height + << " max " << S32(maximized) + << " min " << S32(minimized) + << " rest " << S32(restored) + << llendl; + } + + // If we are now restored, but we weren't before, this + // means that the window was un-minimized. + if (w_param == SIZE_RESTORED && window_imp->mLastSizeWParam != SIZE_RESTORED) + { + window_imp->mCallbacks->handleActivate(window_imp, TRUE); + } + + // handle case of window being maximized from fully minimized state + if (w_param == SIZE_MAXIMIZED && window_imp->mLastSizeWParam != SIZE_MAXIMIZED) + { + window_imp->mCallbacks->handleActivate(window_imp, TRUE); + } + + // Also handle the minimization case + if (w_param == SIZE_MINIMIZED && window_imp->mLastSizeWParam != SIZE_MINIMIZED) + { + window_imp->mCallbacks->handleActivate(window_imp, FALSE); + } + + // Actually resize all of our views + if (w_param != SIZE_MINIMIZED) + { + // Ignore updates for minimizing and minimized "windows" + window_imp->mCallbacks->handleResize( window_imp, + LOWORD(l_param), + HIWORD(l_param) ); + } + + window_imp->mLastSizeWParam = w_param; + + return 0; + } + + case WM_SETFOCUS: + if (gDebugWindowProc) + { + llinfos << "WINDOWPROC SetFocus" << llendl; + } + window_imp->mCallbacks->handleFocus(window_imp); + return 0; + + case WM_KILLFOCUS: + if (gDebugWindowProc) + { + llinfos << "WINDOWPROC KillFocus" << llendl; + } + window_imp->mCallbacks->handleFocusLost(window_imp); + return 0; + + case WM_COPYDATA: + // received a URL + PCOPYDATASTRUCT myCDS = (PCOPYDATASTRUCT) l_param; + window_imp->mCallbacks->handleDataCopy(window_imp, myCDS->dwData, myCDS->lpData); + return 0; + } + } + + // pass unhandled messages down to Windows + return DefWindowProc(h_wnd, u_msg, w_param, l_param); +} + +BOOL LLWindowWin32::convertCoords(LLCoordGL from, LLCoordWindow *to) +{ + S32 client_height; + RECT client_rect; + LLCoordWindow window_position; + + if (!mWindowHandle || + !GetClientRect(mWindowHandle, &client_rect) || + NULL == to) + { + return FALSE; + } + + to->mX = from.mX; + client_height = client_rect.bottom - client_rect.top; + to->mY = client_height - from.mY - 1; + + return TRUE; +} + +BOOL LLWindowWin32::convertCoords(LLCoordWindow from, LLCoordGL* to) +{ + S32 client_height; + RECT client_rect; + + if (!mWindowHandle || + !GetClientRect(mWindowHandle, &client_rect) || + NULL == to) + { + return FALSE; + } + + to->mX = from.mX; + client_height = client_rect.bottom - client_rect.top; + to->mY = client_height - from.mY - 1; + + return TRUE; +} + +BOOL LLWindowWin32::convertCoords(LLCoordScreen from, LLCoordWindow* to) +{ + POINT mouse_point; + + mouse_point.x = from.mX; + mouse_point.y = from.mY; + BOOL result = ScreenToClient(mWindowHandle, &mouse_point); + + if (result) + { + to->mX = mouse_point.x; + to->mY = mouse_point.y; + } + + return result; +} + +BOOL LLWindowWin32::convertCoords(LLCoordWindow from, LLCoordScreen *to) +{ + POINT mouse_point; + + mouse_point.x = from.mX; + mouse_point.y = from.mY; + BOOL result = ClientToScreen(mWindowHandle, &mouse_point); + + if (result) + { + to->mX = mouse_point.x; + to->mY = mouse_point.y; + } + + return result; +} + +BOOL LLWindowWin32::convertCoords(LLCoordScreen from, LLCoordGL *to) +{ + LLCoordWindow window_coord; + + if (!mWindowHandle || (NULL == to)) + { + return FALSE; + } + + convertCoords(from, &window_coord); + convertCoords(window_coord, to); + return TRUE; +} + +BOOL LLWindowWin32::convertCoords(LLCoordGL from, LLCoordScreen *to) +{ + LLCoordWindow window_coord; + + if (!mWindowHandle || (NULL == to)) + { + return FALSE; + } + + convertCoords(from, &window_coord); + convertCoords(window_coord, to); + return TRUE; +} + + +BOOL LLWindowWin32::isClipboardTextAvailable() +{ + return IsClipboardFormatAvailable(CF_UNICODETEXT) || IsClipboardFormatAvailable( CF_TEXT ); +} + + +BOOL LLWindowWin32::pasteTextFromClipboard(LLWString &dst) +{ + BOOL success = FALSE; + + if (IsClipboardFormatAvailable(CF_UNICODETEXT)) + { + if (OpenClipboard(mWindowHandle)) + { + HGLOBAL h_data = GetClipboardData(CF_UNICODETEXT); + if (h_data) + { + WCHAR *utf16str = (WCHAR*) GlobalLock(h_data); + if (utf16str) + { + dst = utf16str_to_wstring(utf16str); + LLWString::removeCRLF(dst); + GlobalUnlock(h_data); + success = TRUE; + } + } + CloseClipboard(); + } + } + else if (IsClipboardFormatAvailable(CF_TEXT)) + { + // This must be an OLD OS. We don't do non-ASCII for old OSes + if (OpenClipboard(mWindowHandle)) + { + HGLOBAL h_data = GetClipboardData(CF_TEXT); + if (h_data) + { + char* str = (char*) GlobalLock(h_data); + if (str) + { + // Strip non-ASCII characters + dst = utf8str_to_wstring(mbcsstring_makeASCII(str)); + LLWString::removeCRLF(dst); + GlobalUnlock(h_data); + success = TRUE; + } + } + CloseClipboard(); + } + } + + return success; +} + + +BOOL LLWindowWin32::copyTextToClipboard(const LLWString& wstr) +{ + BOOL success = FALSE; + + if (OpenClipboard(mWindowHandle)) + { + EmptyClipboard(); + + // Provide a copy of the data in Unicode format. + LLWString sanitized_string(wstr); + LLWString::addCRLF(sanitized_string); + llutf16string out_utf16 = wstring_to_utf16str(sanitized_string); + const size_t size_utf16 = (out_utf16.length() + 1) * sizeof(WCHAR); + + // Memory is allocated and then ownership of it is transfered to the system. + HGLOBAL hglobal_copy_utf16 = GlobalAlloc(GMEM_MOVEABLE, size_utf16); + if (hglobal_copy_utf16) + { + WCHAR* copy_utf16 = (WCHAR*) GlobalLock(hglobal_copy_utf16); + if (copy_utf16) + { + memcpy(copy_utf16, out_utf16.c_str(), size_utf16); + GlobalUnlock(hglobal_copy_utf16); + + if (SetClipboardData(CF_UNICODETEXT, hglobal_copy_utf16)) + { + success = TRUE; + } + } + } + + // Also provide a copy as raw ASCII text. + LLWString ascii_string(wstr); + LLWString::_makeASCII(ascii_string); + LLWString::addCRLF(ascii_string); + std::string out_s = wstring_to_utf8str(ascii_string); + const size_t size = (out_s.length() + 1) * sizeof(char); + + // Memory is allocated and then ownership of it is transfered to the system. + HGLOBAL hglobal_copy = GlobalAlloc(GMEM_MOVEABLE, size); + if (hglobal_copy) + { + char* copy = (char*) GlobalLock(hglobal_copy); + if( copy ) + { + memcpy(copy, out_s.c_str(), size); + GlobalUnlock(hglobal_copy); + + if (SetClipboardData(CF_TEXT, hglobal_copy)) + { + success = TRUE; + } + } + } + + CloseClipboard(); + } + + return success; +} + +// Constrains the mouse to the window. +void LLWindowWin32::setMouseClipping( BOOL b ) +{ + if( b != mIsMouseClipping ) + { + BOOL success = FALSE; + + if( b ) + { + GetClipCursor( &mOldMouseClip ); + + RECT client_rect_in_screen_space; + if( getClientRectInScreenSpace( &client_rect_in_screen_space ) ) + { + success = ClipCursor( &client_rect_in_screen_space ); + } + } + else + { + // Must restore the old mouse clip, which may be set by another window. + success = ClipCursor( &mOldMouseClip ); + SetRect( &mOldMouseClip, 0, 0, 0, 0 ); + } + + if( success ) + { + mIsMouseClipping = b; + } + } +} + +BOOL LLWindowWin32::getClientRectInScreenSpace( RECT* rectp ) +{ + BOOL success = FALSE; + + RECT client_rect; + if( mWindowHandle && GetClientRect(mWindowHandle, &client_rect) ) + { + POINT top_left; + top_left.x = client_rect.left; + top_left.y = client_rect.top; + ClientToScreen(mWindowHandle, &top_left); + + POINT bottom_right; + bottom_right.x = client_rect.right; + bottom_right.y = client_rect.bottom; + ClientToScreen(mWindowHandle, &bottom_right); + + SetRect( rectp, + top_left.x, + top_left.y, + bottom_right.x, + bottom_right.y ); + + success = TRUE; + } + + return success; +} + + +BOOL LLWindowWin32::sendEmail(const char* address, const char* subject, const char* body_text, + const char* attachment, const char* attachment_displayed_name ) +{ + // Based on "A SendMail() DLL" by Greg Turner, Windows Developer Magazine, Nov. 1997. + // See article for use of GetProcAddress + // No restrictions on use. + + enum SendResult + { + LL_EMAIL_SUCCESS, + LL_EMAIL_MAPI_NOT_INSTALLED, // No MAPI Server (eg Microsoft Exchange) installed + LL_EMAIL_MAPILOAD_FAILED, // Load of MAPI32.DLL failed + LL_EMAIL_SEND_FAILED // The message send itself failed + }; + + SendResult result = LL_EMAIL_SUCCESS; + + U32 mapi_installed = GetProfileInt(L"Mail", L"MAPI", 0); + if( !mapi_installed) + { + result = LL_EMAIL_MAPI_NOT_INSTALLED; + } + else + { + HINSTANCE hMAPIInst = LoadLibrary(L"MAPI32.DLL"); + if(!hMAPIInst) + { + result = LL_EMAIL_MAPILOAD_FAILED; + } + else + { + LPMAPISENDMAIL pMAPISendMail = (LPMAPISENDMAIL) GetProcAddress(hMAPIInst, "MAPISendMail"); + + // Send the message + MapiRecipDesc recipients[1]; + recipients[0].ulReserved = 0; + recipients[0].ulRecipClass = MAPI_TO; + recipients[0].lpszName = (char*)address; + recipients[0].lpszAddress = (char*)address; + recipients[0].ulEIDSize = 0; + recipients[0].lpEntryID = 0; + + MapiFileDesc files[1]; + files[0].ulReserved = 0; + files[0].flFlags = 0; // non-OLE file + files[0].nPosition = -1; // Leave file location in email unspecified. + files[0].lpszPathName = (char*)attachment; // Must be fully qualified name, including drive letter. + files[0].lpszFileName = (char*)attachment_displayed_name; // If NULL, uses attachment as displayed name. + files[0].lpFileType = NULL; // Recipient will have to figure out what kind of file this is. + + MapiMessage msg; + memset(&msg, 0, sizeof(msg)); + msg.lpszSubject = (char*)subject; // may be NULL + msg.lpszNoteText = (char*)body_text; + msg.nRecipCount = address ? 1 : 0; + msg.lpRecips = address ? recipients : NULL; + msg.nFileCount = attachment ? 1 : 0; + msg.lpFiles = attachment ? files : NULL; + + U32 success = pMAPISendMail(0, (U32) mWindowHandle, &msg, MAPI_DIALOG|MAPI_LOGON_UI|MAPI_NEW_SESSION, 0); + if(success != SUCCESS_SUCCESS) + { + result = LL_EMAIL_SEND_FAILED; + } + + FreeLibrary(hMAPIInst); + } + } + + return result == LL_EMAIL_SUCCESS; +} + + +S32 LLWindowWin32::stat(const char* file_name, struct stat* stat_info) +{ + llassert( sizeof(struct stat) == sizeof(struct _stat) ); // They are defined identically in sys/stat.h, but I'm paranoid. + return LLFile::stat( file_name, (struct _stat*) stat_info ); +} + +void LLWindowWin32::flashIcon(F32 seconds) +{ + FLASHWINFO flash_info; + + flash_info.cbSize = sizeof(FLASHWINFO); + flash_info.hwnd = mWindowHandle; + flash_info.dwFlags = FLASHW_TRAY; + flash_info.uCount = UINT(seconds / ICON_FLASH_TIME); + flash_info.dwTimeout = DWORD(1000.f * ICON_FLASH_TIME); // milliseconds + FlashWindowEx(&flash_info); +} + +F32 LLWindowWin32::getGamma() +{ + return mCurrentGamma; +} + +BOOL LLWindowWin32::restoreGamma() +{ + return SetDeviceGammaRamp(mhDC, mPrevGammaRamp); +} + +BOOL LLWindowWin32::setGamma(const F32 gamma) +{ + mCurrentGamma = gamma; + + llinfos << "Setting gamma to " << gamma << llendl; + + for ( int i = 0; i < 256; ++i ) + { + int mult = 256 - ( int ) ( ( gamma - 1.0f ) * 128.0f ); + + int value = mult * i; + + if ( value > 0xffff ) + value = 0xffff; + + mCurrentGammaRamp [ 0 * 256 + i ] = + mCurrentGammaRamp [ 1 * 256 + i ] = + mCurrentGammaRamp [ 2 * 256 + i ] = ( WORD )value; + }; + + return SetDeviceGammaRamp ( mhDC, mCurrentGammaRamp ); +} + +LLWindow::LLWindowResolution* LLWindowWin32::getSupportedResolutions(S32 &num_resolutions) +{ + if (!mSupportedResolutions) + { + mSupportedResolutions = new LLWindowResolution[MAX_NUM_RESOLUTIONS]; + DEVMODE dev_mode; + + mNumSupportedResolutions = 0; + for (S32 mode_num = 0; mNumSupportedResolutions < MAX_NUM_RESOLUTIONS; mode_num++) + { + if (!EnumDisplaySettings(NULL, mode_num, &dev_mode)) + { + break; + } + + if (dev_mode.dmBitsPerPel == BITS_PER_PIXEL && + dev_mode.dmPelsWidth >= 800 && + dev_mode.dmPelsHeight >= 600) + { + BOOL resolution_exists = FALSE; + for(S32 i = 0; i < mNumSupportedResolutions; i++) + { + if (mSupportedResolutions[i].mWidth == dev_mode.dmPelsWidth && + mSupportedResolutions[i].mHeight == dev_mode.dmPelsHeight) + { + resolution_exists = TRUE; + } + } + if (!resolution_exists) + { + mSupportedResolutions[mNumSupportedResolutions].mWidth = dev_mode.dmPelsWidth; + mSupportedResolutions[mNumSupportedResolutions].mHeight = dev_mode.dmPelsHeight; + mNumSupportedResolutions++; + } + } + } + } + + num_resolutions = mNumSupportedResolutions; + return mSupportedResolutions; +} + + +F32 LLWindowWin32::getNativeAspectRatio() +{ + if (mOverrideAspectRatio > 0.f) + { + return mOverrideAspectRatio; + } + else if (mNativeAspectRatio > 0.f) + { + // we grabbed this value at startup, based on the user's desktop settings + return mNativeAspectRatio; + } + // RN: this hack presumes that the largest supported resolution is monitor-limited + // and that pixels in that mode are square, therefore defining the native aspect ratio + // of the monitor...this seems to work to a close approximation for most CRTs/LCDs + S32 num_resolutions; + LLWindowResolution* resolutions = getSupportedResolutions(num_resolutions); + + return ((F32)resolutions[num_resolutions - 1].mWidth / (F32)resolutions[num_resolutions - 1].mHeight); +} + +F32 LLWindowWin32::getPixelAspectRatio() +{ + F32 pixel_aspect = 1.f; + if (getFullscreen()) + { + LLCoordScreen screen_size; + getSize(&screen_size); + pixel_aspect = getNativeAspectRatio() * (F32)screen_size.mY / (F32)screen_size.mX; + } + + return pixel_aspect; +} + +// Change display resolution. Returns true if successful. +// protected +BOOL LLWindowWin32::setDisplayResolution(S32 width, S32 height, S32 bits, S32 refresh) +{ + DEVMODE dev_mode; + dev_mode.dmSize = sizeof(dev_mode); + BOOL success = FALSE; + + // Don't change anything if we don't have to + if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dev_mode)) + { + if (dev_mode.dmPelsWidth == width && + dev_mode.dmPelsHeight == height && + dev_mode.dmBitsPerPel == bits && + dev_mode.dmDisplayFrequency == refresh ) + { + // ...display mode identical, do nothing + return TRUE; + } + } + + memset(&dev_mode, 0, sizeof(dev_mode)); + dev_mode.dmSize = sizeof(dev_mode); + dev_mode.dmPelsWidth = width; + dev_mode.dmPelsHeight = height; + dev_mode.dmBitsPerPel = bits; + dev_mode.dmDisplayFrequency = refresh; + dev_mode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY; + + // CDS_FULLSCREEN indicates that this is a temporary change to the device mode. + LONG cds_result = ChangeDisplaySettings(&dev_mode, CDS_FULLSCREEN); + + success = (DISP_CHANGE_SUCCESSFUL == cds_result); + + if (!success) + { + llwarns << "setDisplayResolution failed, " + << width << "x" << height << "x" << bits << " @ " << refresh << llendl; + } + + return success; +} + +// protected +BOOL LLWindowWin32::setFullscreenResolution() +{ + if (mFullscreen) + { + return setDisplayResolution( mFullscreenWidth, mFullscreenHeight, mFullscreenBits, mFullscreenRefresh); + } + else + { + return FALSE; + } +} + +// protected +BOOL LLWindowWin32::resetDisplayResolution() +{ + llinfos << "resetDisplayResolution START" << llendl; + + LONG cds_result = ChangeDisplaySettings(NULL, 0); + + BOOL success = (DISP_CHANGE_SUCCESSFUL == cds_result); + + if (!success) + { + llwarns << "resetDisplayResolution failed" << llendl; + } + + llinfos << "resetDisplayResolution END" << llendl; + + return success; +} + +void LLWindowWin32::swapBuffers() +{ + SwapBuffers(mhDC); +} + + +BOOL CALLBACK EnumJoysticksCallback( const DIDEVICEINSTANCE* pdidInstance, + VOID* pContext ) +{ + HRESULT hr; + + // Obtain an interface to the enumerated joystick. + hr = g_pDI->CreateDevice( pdidInstance->guidInstance, &g_pJoystick, NULL ); + + // If it failed, then we can't use this joystick. (Maybe the user unplugged + // it while we were in the middle of enumerating it.) + if( FAILED(hr) ) + return DIENUM_CONTINUE; + + // Stop enumeration. Note: we're just taking the first joystick we get. You + // could store all the enumerated joysticks and let the user pick. + return DIENUM_STOP; +} + +BOOL CALLBACK EnumObjectsCallback( const DIDEVICEOBJECTINSTANCE* pdidoi, + VOID* pContext ) +{ + if( pdidoi->dwType & DIDFT_AXIS ) + { + DIPROPRANGE diprg; + diprg.diph.dwSize = sizeof(DIPROPRANGE); + diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER); + diprg.diph.dwHow = DIPH_BYID; + diprg.diph.dwObj = pdidoi->dwType; // Specify the enumerated axis + diprg.lMin = -1000; + diprg.lMax = +1000; + + // Set the range for the axis + if( FAILED( g_pJoystick->SetProperty( DIPROP_RANGE, &diprg.diph ) ) ) + return DIENUM_STOP; + + } + return DIENUM_CONTINUE; +} + +void LLWindowWin32::updateJoystick( ) +{ + HRESULT hr; + DIJOYSTATE js; // DInput joystick state + + if (!g_pJoystick) + return; + hr = g_pJoystick->Poll(); + if ( hr == DIERR_INPUTLOST ) + { + hr = g_pJoystick->Acquire(); + return; + } + else if ( FAILED(hr) ) + return; + + // Get the input's device state + if( FAILED( hr = g_pJoystick->GetDeviceState( sizeof(DIJOYSTATE), &js ) ) ) + return; // The device should have been acquired during the Poll() + + if (js.lX <= -500) + { + if (!(mJoyStickState & 0x1)) + { + gKeyboard->handleTranslatedKeyDown(KEY_PAD_LEFT, 0); + mJoyStickState |= 0x1; + } + } + else + { + if (mJoyStickState & 0x1) + { + gKeyboard->handleTranslatedKeyUp(KEY_PAD_LEFT, 0); + mJoyStickState &= ~0x1; + } + } + if (js.lX >= 500) + { + if (!(mJoyStickState & 0x2)) + { + gKeyboard->handleTranslatedKeyDown(KEY_PAD_RIGHT, 0); + mJoyStickState |= 0x2; + } + } + else + { + if (mJoyStickState & 0x2) + { + gKeyboard->handleTranslatedKeyUp(KEY_PAD_RIGHT, 0); + mJoyStickState &= ~0x2; + } + } + if (js.lY <= -500) + { + if (!(mJoyStickState & 0x4)) + { + gKeyboard->handleTranslatedKeyDown(KEY_PAD_UP, 0); + mJoyStickState |= 0x4; + } + } + else + { + if (mJoyStickState & 0x4) + { + gKeyboard->handleTranslatedKeyUp(KEY_PAD_UP, 0); + mJoyStickState &= ~0x4; + } + } + if (js.lY >= 500) + { + if (!(mJoyStickState & 0x8)) + { + gKeyboard->handleTranslatedKeyDown(KEY_PAD_DOWN, 0); + mJoyStickState |= 0x8; + } + } + else + { + if (mJoyStickState & 0x8) + { + gKeyboard->handleTranslatedKeyUp(KEY_PAD_DOWN, 0); + mJoyStickState &= ~0x8; + } + } + + for( int i = 0; i < 15; i++ ) + { + if ( js.rgbButtons[i] & 0x80 ) + { + if (!(mJoyButtonState & (1<handleTranslatedKeyDown(KEY_BUTTON1+i, 0); + mJoyButtonState |= (1<handleTranslatedKeyUp(KEY_BUTTON1+i, 0); + mJoyButtonState &= ~(1<mWindowHandle; +} + + +void llwindow_install_wndproc(LLWindow *window, WNDPROC wnd_proc) +{ + //assumes we are dealing with a Win32 window + ((LLWindowWin32*)window)->mWndProc = wnd_proc; +} + +S32 OSMessageBoxWin32(const char* text, const char* caption, U32 type) +{ + UINT uType; + + switch(type) + { + case OSMB_OK: + uType = MB_OK; + break; + case OSMB_OKCANCEL: + uType = MB_OKCANCEL; + break; + case OSMB_YESNO: + uType = MB_YESNO; + break; + default: + uType = MB_OK; + break; + } + + // HACK! Doesn't properly handle wide strings! + int retval_win = MessageBoxA(NULL, text, caption, uType); + S32 retval; + + switch(retval_win) + { + case IDYES: + retval = OSBTN_YES; + break; + case IDNO: + retval = OSBTN_NO; + break; + case IDOK: + retval = OSBTN_OK; + break; + case IDCANCEL: + retval = OSBTN_CANCEL; + break; + default: + retval = OSBTN_CANCEL; + break; + } + + return retval; +} + + +void spawn_web_browser(const char* escaped_url ) +{ + bool found = false; + S32 i; + for (i = 0; i < gURLProtocolWhitelistCount; i++) + { + S32 len = strlen(gURLProtocolWhitelist[i]); + if (!strncmp(escaped_url, gURLProtocolWhitelist[i], len) + && escaped_url[len] == ':') + { + found = true; + break; + } + } + + if (!found) + { + llwarns << "spawn_web_browser() called for url with protocol not on whitelist: " << escaped_url << llendl; + return; + } + + llinfos << "Opening URL " << escaped_url << llendl; + + // Figure out the user's default web browser + // HKEY_CLASSES_ROOT\http\shell\open\command + char reg_path_str[256]; + sprintf(reg_path_str, "%s\\shell\\open\\command", gURLProtocolWhitelistHandler[i]); + WCHAR reg_path_wstr[256]; + mbstowcs(reg_path_wstr, reg_path_str, 1024); + + HKEY key; + WCHAR browser_open_wstr[1024]; + DWORD buffer_length = 1024; + RegOpenKeyEx(HKEY_CLASSES_ROOT, reg_path_wstr, 0, KEY_QUERY_VALUE, &key); + RegQueryValueEx(key, NULL, NULL, NULL, (LPBYTE)browser_open_wstr, &buffer_length); + RegCloseKey(key); + + // Convert to STL string + LLWString browser_open_wstring = utf16str_to_wstring(browser_open_wstr); + + if (browser_open_wstring.length() < 2) + { + llwarns << "Invalid browser executable in registry " << browser_open_wstring << llendl; + return; + } + + // Extract the process that's supposed to be launched + LLWString browser_executable; + if (browser_open_wstring[0] == '"') + { + // executable is quoted, find the matching quote + size_t quote_pos = browser_open_wstring.find('"', 1); + // copy out the string including both quotes + browser_executable = browser_open_wstring.substr(0, quote_pos+1); + } + else + { + // executable not quoted, find a space + size_t space_pos = browser_open_wstring.find(' ', 1); + browser_executable = browser_open_wstring.substr(0, space_pos); + } + + llinfos << "Browser reg key: " << wstring_to_utf8str(browser_open_wstring) << llendl; + llinfos << "Browser executable: " << wstring_to_utf8str(browser_executable) << llendl; + + // Convert URL to wide string for Windows API + // Assume URL is UTF8, as can come from scripts + LLWString url_wstring = utf8str_to_wstring(escaped_url); + llutf16string url_utf16 = wstring_to_utf16str(url_wstring); + + // Convert executable and path to wide string for Windows API + llutf16string browser_exec_utf16 = wstring_to_utf16str(browser_executable); + + // ShellExecute returns HINSTANCE for backwards compatiblity. + // MS docs say to cast to int and compare to 32. + HWND our_window = NULL; + LPCWSTR directory_wstr = NULL; + int retval = (int) ShellExecute(our_window, + L"open", + browser_exec_utf16.c_str(), + url_utf16.c_str(), + directory_wstr, + SW_SHOWNORMAL); + if (retval > 32) + { + llinfos << "load_url success with " << retval << llendl; + } + else + { + llinfos << "load_url failure with " << retval << llendl; + } +} + +void shell_open( const char* file_path ) +{ + llinfos << "Opening " << file_path << llendl; + + WCHAR wstr[1024]; + mbstowcs(wstr, file_path, 1024); + + HWND our_window = NULL; + int retval = (int) ShellExecute(our_window, L"open", wstr, NULL, NULL, SW_SHOWNORMAL); + if (retval > 32) + { + llinfos << "ShellExecute success with " << retval << llendl; + } + else + { + llinfos << "ShellExecute failure with " << retval << llendl; + } +} + +BOOL LLWindowWin32::dialog_color_picker ( F32 *r, F32 *g, F32 *b ) +{ + BOOL retval = FALSE; + + static CHOOSECOLOR cc; + static COLORREF crCustColors[16]; + cc.lStructSize = sizeof(CHOOSECOLOR); + cc.hwndOwner = mWindowHandle; + cc.hInstance = NULL; + cc.rgbResult = RGB ((*r * 255.f),(*g *255.f),(*b * 255.f)); + //cc.rgbResult = RGB (0x80,0x80,0x80); + cc.lpCustColors = crCustColors; + cc.Flags = CC_RGBINIT | CC_FULLOPEN; + cc.lCustData = 0; + cc.lpfnHook = NULL; + cc.lpTemplateName = NULL; + + // This call is modal, so pause agent + //send_agent_pause(); // this is in newview and we don't want to set up a dependency + { + retval = ChooseColor(&cc); + } + //send_agent_resume(); // this is in newview and we don't want to set up a dependency + + *b = ((F32)((cc.rgbResult >> 16) & 0xff)) / 255.f; + + *g = ((F32)((cc.rgbResult >> 8) & 0xff)) / 255.f; + + *r = ((F32)(cc.rgbResult & 0xff)) / 255.f; + + return (retval); +} + +void *LLWindowWin32::getPlatformWindow() +{ + return (void*)mWindowHandle; +} + +void LLWindowWin32::bringToFront() +{ + BringWindowToTop(mWindowHandle); +} + +// set (OS) window focus back to the client +void LLWindowWin32::focusClient() +{ + SetFocus ( mWindowHandle ); +}; + +#endif // LL_WINDOWS diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h new file mode 100644 index 0000000000..6803ad6f2a --- /dev/null +++ b/indra/llwindow/llwindowwin32.h @@ -0,0 +1,187 @@ +/** + * @file llwindowwin32.h + * @brief Windows implementation of LLWindow class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLWINDOWWIN32_H +#define LL_LLWINDOWWIN32_H + +#include "llwindow.h" + +// Hack for async host by name +#define LL_WM_HOST_RESOLVED (WM_APP + 1) +typedef void (*LLW32MsgCallback)(const MSG &msg); + +class LLWindowWin32 : public LLWindow +{ +public: + /*virtual*/ void show(); + /*virtual*/ void hide(); + /*virtual*/ void close(); + /*virtual*/ BOOL getVisible(); + /*virtual*/ BOOL getMinimized(); + /*virtual*/ BOOL getMaximized(); + /*virtual*/ BOOL maximize(); + /*virtual*/ BOOL getFullscreen(); + /*virtual*/ BOOL getPosition(LLCoordScreen *position); + /*virtual*/ BOOL getSize(LLCoordScreen *size); + /*virtual*/ BOOL getSize(LLCoordWindow *size); + /*virtual*/ BOOL setPosition(LLCoordScreen position); + /*virtual*/ BOOL setSize(LLCoordScreen size); + /*virtual*/ BOOL switchContext(BOOL fullscreen, LLCoordScreen size, BOOL disable_vsync); + /*virtual*/ BOOL setCursorPosition(LLCoordWindow position); + /*virtual*/ BOOL getCursorPosition(LLCoordWindow *position); + /*virtual*/ void showCursor(); + /*virtual*/ void hideCursor(); + /*virtual*/ void showCursorFromMouseMove(); + /*virtual*/ void hideCursorUntilMouseMove(); + /*virtual*/ BOOL isCursorHidden(); + /*virtual*/ void setCursor(ECursorType cursor); + /*virtual*/ ECursorType getCursor(); + /*virtual*/ void captureMouse(); + /*virtual*/ void releaseMouse(); + /*virtual*/ void setMouseClipping( BOOL b ); + /*virtual*/ BOOL isClipboardTextAvailable(); + /*virtual*/ BOOL pasteTextFromClipboard(LLWString &dst); + /*virtual*/ BOOL copyTextToClipboard(const LLWString &src); + /*virtual*/ void flashIcon(F32 seconds); + /*virtual*/ F32 getGamma(); + /*virtual*/ BOOL setGamma(const F32 gamma); // Set the gamma + /*virtual*/ BOOL restoreGamma(); // Restore original gamma table (before updating gamma) + /*virtual*/ ESwapMethod getSwapMethod() { return mSwapMethod; } + /*virtual*/ void gatherInput(); + /*virtual*/ void delayInputProcessing(); + /*virtual*/ void swapBuffers(); + + /*virtual*/ LLString getTempFileName(); + /*virtual*/ void deleteFile( const char* file_name ); + /*virtual*/ S32 stat( const char* file_name, struct stat* stat_info ); + /*virtual*/ BOOL sendEmail(const char* address,const char* subject,const char* body_text,const char* attachment=NULL, const char* attachment_displayed_name=NULL); + + + // handy coordinate space conversion routines + /*virtual*/ BOOL convertCoords(LLCoordScreen from, LLCoordWindow *to); + /*virtual*/ BOOL convertCoords(LLCoordWindow from, LLCoordScreen *to); + /*virtual*/ BOOL convertCoords(LLCoordWindow from, LLCoordGL *to); + /*virtual*/ BOOL convertCoords(LLCoordGL from, LLCoordWindow *to); + /*virtual*/ BOOL convertCoords(LLCoordScreen from, LLCoordGL *to); + /*virtual*/ BOOL convertCoords(LLCoordGL from, LLCoordScreen *to); + + /*virtual*/ LLWindowResolution* getSupportedResolutions(S32 &num_resolutions); + /*virtual*/ F32 getNativeAspectRatio(); + /*virtual*/ F32 getPixelAspectRatio(); + /*virtual*/ void setNativeAspectRatio(F32 ratio) { mOverrideAspectRatio = ratio; } + + /*virtual*/ BOOL dialog_color_picker (F32 *r, F32 *g, F32 *b ); + + /*virtual*/ void *getPlatformWindow(); + /*virtual*/ void bringToFront(); + /*virtual*/ void focusClient(); + +protected: + LLWindowWin32( + char *title, char *name, int x, int y, int width, int height, U32 flags, + BOOL fullscreen, BOOL clearBg, BOOL disable_vsync, BOOL use_gl, + BOOL ignore_pixel_depth); + ~LLWindowWin32(); + + void initCursors(); + HCURSOR loadColorCursor(LPCTSTR name); + BOOL isValid(); + void moveWindow(const LLCoordScreen& position,const LLCoordScreen& size); + + + // Changes display resolution. Returns true if successful + BOOL setDisplayResolution(S32 width, S32 height, S32 bits, S32 refresh); + + // Go back to last fullscreen display resolution. + BOOL setFullscreenResolution(); + + // Restore the display resolution to its value before we ran the app. + BOOL resetDisplayResolution(); + + void minimize(); + void restore(); + + BOOL shouldPostQuit() { return mPostQuit; } + + +protected: + // + // Platform specific methods + // + + BOOL getClientRectInScreenSpace(RECT* rectp); + void updateJoystick( ); + + static LRESULT CALLBACK mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_param, LPARAM l_param); + static BOOL CALLBACK enumChildWindows(HWND h_wnd, LPARAM l_param); + + + // + // Platform specific variables + // + WCHAR *mWindowTitle; + WCHAR *mWindowClassName; + + HWND mWindowHandle; // window handle + HGLRC mhRC; // OpenGL rendering context + HDC mhDC; // Windows Device context handle + HINSTANCE mhInstance; // handle to application instance + WNDPROC mWndProc; // user-installable window proc + RECT mOldMouseClip; // Screen rect to which the mouse cursor was globally constrained before we changed it in clipMouse() + WPARAM mLastSizeWParam; + F32 mOverrideAspectRatio; + F32 mNativeAspectRatio; + + HCURSOR mCursor[ UI_CURSOR_COUNT ]; // Array of all mouse cursors + + static BOOL sIsClassRegistered; // has the window class been registered? + + F32 mCurrentGamma; + WORD mPrevGammaRamp[256*3]; + WORD mCurrentGammaRamp[256*3]; + + U32 mJoyStickState; + U32 mJoyButtonState; + + LPWSTR mIconResource; + BOOL mMousePositionModified; + BOOL mInputProcessingPaused; + + friend HWND llwindow_get_hwnd(LLWindow *window); + friend void llwindow_install_wndproc(LLWindow *window, WNDPROC wnd_proc); + friend class LLWindowManager; +}; + +class LLSplashScreenWin32 : public LLSplashScreen +{ +public: + LLSplashScreenWin32(); + virtual ~LLSplashScreenWin32(); + + /*virtual*/ void showImpl(); + /*virtual*/ void updateImpl(const char* mesg); + /*virtual*/ void hideImpl(); + +#if LL_WINDOWS + static LRESULT CALLBACK windowProc(HWND h_wnd, UINT u_msg, + WPARAM w_param, LPARAM l_param); +#endif + +private: +#if LL_WINDOWS + HWND mWindow; +#endif +}; + +extern LLW32MsgCallback gAsyncMsgCallback; + +static void handleMessage( const MSG& msg ); + +S32 OSMessageBoxWin32(const char* text, const char* caption, U32 type); + +#endif //LL_LLWINDOWWIN32_H diff --git a/indra/llxml/llcontrol.cpp b/indra/llxml/llcontrol.cpp new file mode 100644 index 0000000000..a9651fafc7 --- /dev/null +++ b/indra/llxml/llcontrol.cpp @@ -0,0 +1,1401 @@ +/** + * @file llcontrol.cpp + * @brief Holds global state for viewer. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include +#include +#include + +#include "llcontrol.h" + +#include "llstl.h" + +#include "linked_lists.h" +#include "llstring.h" +#include "v3math.h" +#include "v3dmath.h" +#include "v4coloru.h" +#include "v4color.h" +#include "v3color.h" +#include "llrect.h" +#include "llxmltree.h" +#include "llsdserialize.h" + +#if LL_RELEASE_FOR_DOWNLOAD +#define CONTROL_ERRS llwarns +#else +#define CONTROL_ERRS llerrs +#endif + +//this defines the current version of the settings file +U32 LLControlBase::sMaxControlNameLength = 0; + +//These lists are used to store the ID's of registered event listeners. +std::list LLControlBase::mFreeIDs; +std::list LLControlBase::mUsedIDs; + +S32 LLControlBase::mTopID; + +std::set LLControlBase::mChangedControls; + +const S32 CURRENT_VERSION = 101; + +BOOL control_insert_before( LLControlBase* first, LLControlBase* second ); + +BOOL LLControl::llsd_compare(const LLSD& a, const LLSD & b) +{ + switch (mType) + { + case TYPE_U32: + case TYPE_S32: + return a.asInteger() == b.asInteger(); + case TYPE_BOOLEAN: + return a.asBoolean() == b.asBoolean(); + case TYPE_F32: + return a.asReal() == b.asReal(); + case TYPE_VEC3: + case TYPE_VEC3D: + return LLVector3d(a) == LLVector3d(b); + case TYPE_RECT: + return LLRect(a) == LLRect(b); + case TYPE_COL4: + return LLColor4(a) == LLColor4(b); + case TYPE_COL3: + return LLColor3(a) == LLColor3(b); + case TYPE_COL4U: + return LLColor4U(a) == LLColor4U(b); + case TYPE_STRING: + return a.asString() == b.asString(); + default: + // no-op + break; + } + + return FALSE; +} + +LLControlBase::~LLControlBase() +{ +} + +// virtual +void LLControlBase::resetToDefault() +{ +} + +LLControlGroup::LLControlGroup(): mNameTable() +{ + //mFreeStringOffset = 0; +} + +LLControlGroup::~LLControlGroup() +{ +} + +LLSD LLControlBase::registerListener(LLSimpleListenerObservable *listener, LLSD userdata) +{ + // Symmetric listener relationship + addListener(listener, "", userdata); + listener->addListener(this, "", userdata); + return getValue(); +} + +void LLControlGroup::cleanup() +{ + mNameTable.clear(); +} + +LLControlBase* LLControlGroup::getControl(const LLString& name) +{ + return mNameTable[name]; +} + +BOOL LLControlGroup::declareControl(const LLString& name, eControlType type, const LLSD initial_val, const LLString& comment, BOOL persist) +{ + if(!mNameTable[name]) + { + // if not, create the control and add it to the name table + LLControl* control = new LLControl(name, type, initial_val, comment, persist); + mNameTable[name] = control; + return TRUE; + } else + { + llwarns << "LLControlGroup::declareControl: Control named " << name << " already exists." << llendl; + return FALSE; + } +} + +BOOL LLControlGroup::declareU32(const LLString& name, const U32 initial_val, const LLString& comment, BOOL persist) +{ + return declareControl(name, TYPE_U32, (LLSD::Integer) initial_val, comment, persist); +} + +BOOL LLControlGroup::declareS32(const LLString& name, const S32 initial_val, const LLString& comment, BOOL persist) +{ + return declareControl(name, TYPE_S32, initial_val, comment, persist); +} + +BOOL LLControlGroup::declareF32(const LLString& name, const F32 initial_val, const LLString& comment, BOOL persist) +{ + return declareControl(name, TYPE_F32, initial_val, comment, persist); +} + +BOOL LLControlGroup::declareBOOL(const LLString& name, const BOOL initial_val, const LLString& comment, BOOL persist) +{ + return declareControl(name, TYPE_BOOLEAN, initial_val, comment, persist); +} + +BOOL LLControlGroup::declareString(const LLString& name, const LLString& initial_val, const LLString& comment, BOOL persist) +{ + return declareControl(name, TYPE_STRING, initial_val, comment, persist); +} + +BOOL LLControlGroup::declareVec3(const LLString& name, const LLVector3 &initial_val, const LLString& comment, BOOL persist) +{ + return declareControl(name, TYPE_VEC3, initial_val.getValue(), comment, persist); +} + +BOOL LLControlGroup::declareVec3d(const LLString& name, const LLVector3d &initial_val, const LLString& comment, BOOL persist) +{ + return declareControl(name, TYPE_VEC3D, initial_val.getValue(), comment, persist); +} + +BOOL LLControlGroup::declareRect(const LLString& name, const LLRect &initial_val, const LLString& comment, BOOL persist) +{ + return declareControl(name, TYPE_RECT, initial_val.getValue(), comment, persist); +} + +BOOL LLControlGroup::declareColor4U(const LLString& name, const LLColor4U &initial_val, const LLString& comment, BOOL persist ) +{ + return declareControl(name, TYPE_COL4U, initial_val.getValue(), comment, persist); +} + +BOOL LLControlGroup::declareColor4(const LLString& name, const LLColor4 &initial_val, const LLString& comment, BOOL persist ) +{ + return declareControl(name, TYPE_COL4, initial_val.getValue(), comment, persist); +} + +BOOL LLControlGroup::declareColor3(const LLString& name, const LLColor3 &initial_val, const LLString& comment, BOOL persist ) +{ + return declareControl(name, TYPE_COL3, initial_val.getValue(), comment, persist); +} + +LLSD LLControlGroup::registerListener(const LLString& name, LLSimpleListenerObservable *listener) +{ + LLControlBase *control = mNameTable[name]; + if (control) + { + return control->registerListener(listener); + } + return LLSD(); +} + +BOOL LLControlGroup::getBOOL(const LLString& name) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_BOOLEAN)) + return control->get().asBoolean(); + else + { + CONTROL_ERRS << "Invalid BOOL control " << name << llendl; + return FALSE; + } +} + +S32 LLControlGroup::getS32(const LLString& name) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_S32)) + return control->get().asInteger(); + else + { + CONTROL_ERRS << "Invalid S32 control " << name << llendl; + return 0; + } +} + +U32 LLControlGroup::getU32(const LLString& name) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_U32)) + return control->get().asInteger(); + else + { + CONTROL_ERRS << "Invalid U32 control " << name << llendl; + return 0; + } +} + +F32 LLControlGroup::getF32(const LLString& name) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_F32)) + return (F32) control->get().asReal(); + else + { + CONTROL_ERRS << "Invalid F32 control " << name << llendl; + return 0.0f; + } +} + +LLString LLControlGroup::findString(const LLString& name) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_STRING)) + return control->get().asString(); + return LLString::null; +} + +LLString LLControlGroup::getString(const LLString& name) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_STRING)) + return control->get().asString(); + else + { + CONTROL_ERRS << "Invalid string control " << name << llendl; + return LLString::null; + } +} + +LLWString LLControlGroup::getWString(const LLString& name) +{ + return utf8str_to_wstring(getString(name)); +} + +LLString LLControlGroup::getText(const LLString& name) +{ + LLString utf8_string = getString(name); + LLString::replaceChar(utf8_string, '^', '\n'); + LLString::replaceChar(utf8_string, '%', ' '); + return (utf8_string); +} + +LLVector3 LLControlGroup::getVector3(const LLString& name) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_VEC3)) + return control->get(); + else + { + CONTROL_ERRS << "Invalid LLVector3 control " << name << llendl; + return LLVector3::zero; + } +} + +LLVector3d LLControlGroup::getVector3d(const LLString& name) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_VEC3D)) + return control->get(); + else + { + CONTROL_ERRS << "Invalid LLVector3d control " << name << llendl; + return LLVector3d::zero; + } +} + +LLRect LLControlGroup::getRect(const LLString& name) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_RECT)) + return control->get(); + else + { + CONTROL_ERRS << "Invalid rect control " << name << llendl; + return LLRect::null; + } +} + + +LLColor4 LLControlGroup::getColor(const LLString& name) +{ + ctrl_name_table_t::const_iterator i = mNameTable.find(name); + + if (i != mNameTable.end()) + { + LLControlBase* control = i->second; + + switch(control->mType) + { + case TYPE_COL4: + { + return LLColor4(control->get()); + } + case TYPE_COL4U: + { + return LLColor4(LLColor4U(control->get())); + } + default: + { + CONTROL_ERRS << "Control " << name << " not a color" << llendl; + return LLColor4::white; + } + } + } + else + { + CONTROL_ERRS << "Invalid getColor control " << name << llendl; + return LLColor4::white; + } +} + +LLColor4U LLControlGroup::getColor4U(const LLString& name) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_COL4U)) + return control->get(); + else + { + CONTROL_ERRS << "Invalid LLColor4 control " << name << llendl; + return LLColor4U::white; + } +} + +LLColor4 LLControlGroup::getColor4(const LLString& name) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_COL4)) + return control->get(); + else + { + CONTROL_ERRS << "Invalid LLColor4 control " << name << llendl; + return LLColor4::white; + } +} + +LLColor3 LLControlGroup::getColor3(const LLString& name) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_COL3)) + return control->get(); + else + { + CONTROL_ERRS << "Invalid LLColor3 control " << name << llendl; + return LLColor3::white; + } +} + +BOOL LLControlGroup::controlExists(const LLString& name) +{ + void *control = mNameTable[name]; + + return (control != 0); +} + +//------------------------------------------------------------------- +// Set functions +//------------------------------------------------------------------- + +void LLControlGroup::setBOOL(const LLString& name, BOOL val) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_BOOLEAN)) + { + control->set(val); + } + else + { + CONTROL_ERRS << "Invalid control " << name << llendl; + } +} + + +void LLControlGroup::setS32(const LLString& name, S32 val) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_S32)) + { + control->set(val); + } + else + { + CONTROL_ERRS << "Invalid control " << name << llendl; + } +} + + +void LLControlGroup::setF32(const LLString& name, F32 val) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_F32)) + { + control->set(val); + } + else + { + CONTROL_ERRS << "Invalid control " << name << llendl; + } +} + + +void LLControlGroup::setU32(const LLString& name, U32 val) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_U32)) + { + control->set((LLSD::Integer) val); + } + else + { + CONTROL_ERRS << "Invalid control " << name << llendl; + } +} + + +void LLControlGroup::setString(const LLString& name, const LLString &val) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_STRING)) + { + control->set(val); + } + else + { + CONTROL_ERRS << "Invalid control " << name << llendl; + } +} + + +void LLControlGroup::setVector3(const LLString& name, const LLVector3 &val) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_VEC3)) + { + control->set(val.getValue()); + } + else + { + CONTROL_ERRS << "Invalid control " << name << llendl; + } +} + +void LLControlGroup::setVector3d(const LLString& name, const LLVector3d &val) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_VEC3D)) + { + control->set(val.getValue()); + } + else + { + CONTROL_ERRS << "Invalid control " << name << llendl; + } +} + +void LLControlGroup::setRect(const LLString& name, const LLRect &val) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_RECT)) + { + control->set(val.getValue()); + } + else + { + CONTROL_ERRS << "Invalid rect control " << name << llendl; + } +} + +void LLControlGroup::setColor4U(const LLString& name, const LLColor4U &val) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_COL4U)) + { + control->set(val.getValue()); + } + else + { + CONTROL_ERRS << "Invalid LLColor4 control " << name << llendl; + } +} + +void LLControlGroup::setColor4(const LLString& name, const LLColor4 &val) +{ + LLControlBase* control = mNameTable[name]; + + if (control && control->isType(TYPE_COL4)) + { + control->set(val.getValue()); + } + else + { + CONTROL_ERRS << "Invalid LLColor4 control " << name << llendl; + } +} + +void LLControlGroup::setValue(const LLString& name, const LLSD& val) +{ + if (name.empty()) + { + return; + } + + LLControlBase* control = mNameTable[name]; + + if (control) + { + control->set(val); + } + else + { + CONTROL_ERRS << "Invalid control " << name << llendl; + } +} + +//--------------------------------------------------------------- +// Load and save +//--------------------------------------------------------------- + +U32 LLControlGroup::loadFromFileLegacy(const LLString& filename, BOOL require_declaration, eControlType declare_as) +{ + U32 item = 0; + U32 validitems = 0; + llifstream file; + S32 version; + + file.open(filename.c_str()); + + if (!file) + { + llinfos << "LLControlGroup::loadFromFile unable to open." << llendl; + return 0; + } + + // Check file version + LLString name; + file >> name; + file >> version; + if (name != "version" || version != CURRENT_VERSION) + { + llinfos << filename << " does not appear to be a version " << CURRENT_VERSION << " controls file" << llendl; + return 0; + } + + while (!file.eof()) + { + file >> name; + + if (name.empty()) + { + continue; + } + + if (name.substr(0,2) == "//") + { + // This is a comment. + char buffer[MAX_STRING]; + file.getline(buffer, MAX_STRING); + continue; + } + + BOOL declared = mNameTable.find(name) != mNameTable.end(); + + if (require_declaration && !declared) + { + // Declaration required, but this name not declared. + // Complain about non-empty names. + if (!name.empty()) + { + //read in to end of line + char buffer[MAX_STRING]; + file.getline(buffer, MAX_STRING); + llwarns << "LLControlGroup::loadFromFile() : Trying to set \"" << name << "\", setting doesn't exist." << llendl; + } + continue; + } + + // Got an item. Load it up. + item++; + + // If not declared, assume it's a string + if (!declared) + { + switch(declare_as) + { + case TYPE_COL4: + declareColor4(name, LLColor4::white, LLString::null, NO_PERSIST); + break; + case TYPE_COL4U: + declareColor4U(name, LLColor4U::white, LLString::null, NO_PERSIST); + break; + case TYPE_STRING: + default: + declareString(name, LLString::null, LLString::null, NO_PERSIST); + break; + } + } + + // Control name has been declared in code. + LLControlBase *control = getControl(name); + + llassert(control); + + switch(control->mType) + { + case TYPE_F32: + { + F32 initial; + + file >> initial; + + control->set(initial); + validitems++; + } + break; + case TYPE_S32: + { + S32 initial; + + file >> initial; + + control->set(initial); + validitems++; + } + break; + case TYPE_U32: + { + U32 initial; + + file >> initial; + control->set((LLSD::Integer) initial); + validitems++; + } + break; + case TYPE_BOOLEAN: + { + char boolstring[256]; + BOOL valid = FALSE; + BOOL initial = FALSE; + + file >> boolstring; + if (!strcmp("TRUE", boolstring)) + { + initial = TRUE; + valid = TRUE; + } + else if (!strcmp("FALSE", boolstring)) + { + initial = FALSE; + valid = TRUE; + } + + if (valid) + { + control->set(initial); + } + else + { + llinfos << filename << "Item " << item << ": Invalid BOOL control " << name << ", " << boolstring << llendl; + } + + validitems++; + } + break; + case TYPE_STRING: + { + LLString string; + + file >> string; + + control->set(string); + validitems++; + } + break; + case TYPE_VEC3: + { + F32 x, y, z; + + file >> x >> y >> z; + + LLVector3 vector(x, y, z); + + control->set(vector.getValue()); + validitems++; + } + break; + case TYPE_VEC3D: + { + F64 x, y, z; + + file >> x >> y >> z; + + LLVector3d vector(x, y, z); + + control->set(vector.getValue()); + validitems++; + } + break; + case TYPE_RECT: + { + S32 left, bottom, width, height; + + file >> left >> bottom >> width >> height; + + LLRect rect; + rect.setOriginAndSize(left, bottom, width, height); + + control->set(rect.getValue()); + validitems++; + } + break; + case TYPE_COL4U: + { + S32 red, green, blue, alpha; + LLColor4U color; + file >> red >> green >> blue >> alpha; + color.setVec(red, green, blue, alpha); + control->set(color.getValue()); + validitems++; + } + break; + case TYPE_COL4: + { + LLColor4 color; + file >> color.mV[VRED] >> color.mV[VGREEN] + >> color.mV[VBLUE] >> color.mV[VALPHA]; + control->set(color.getValue()); + validitems++; + } + break; + case TYPE_COL3: + { + LLColor3 color; + file >> color.mV[VRED] >> color.mV[VGREEN] + >> color.mV[VBLUE]; + control->set(color.getValue()); + validitems++; + } + break; + } + } + + file.close(); + + return validitems; +} + +// Returns number of controls loaded, so 0 if failure +U32 LLControlGroup::loadFromFile(const LLString& filename, BOOL require_declaration, eControlType declare_as) +{ + LLString name; + + LLXmlTree xml_controls; + + if (!xml_controls.parseFile(filename)) + { + llwarns << "Unable to open control file " << filename << llendl; + return 0; + } + + LLXmlTreeNode* rootp = xml_controls.getRoot(); + if (!rootp || !rootp->hasAttribute("version")) + { + llwarns << "No valid settings header found in control file " << filename << llendl; + return 0; + } + + U32 item = 0; + U32 validitems = 0; + S32 version; + + rootp->getAttributeS32("version", version); + + // Check file version + if (version != CURRENT_VERSION) + { + llinfos << filename << " does not appear to be a version " << CURRENT_VERSION << " controls file" << llendl; + return 0; + } + + LLXmlTreeNode* child_nodep = rootp->getFirstChild(); + while(child_nodep) + { + name = child_nodep->getName(); + + BOOL declared = (mNameTable[name].notNull()); + + if (require_declaration && !declared) + { + // Declaration required, but this name not declared. + // Complain about non-empty names. + if (!name.empty()) + { + //read in to end of line + llwarns << "LLControlGroup::loadFromFile() : Trying to set \"" << name << "\", setting doesn't exist." << llendl; + } + child_nodep = rootp->getNextChild(); + continue; + } + + // Got an item. Load it up. + item++; + + // If not declared, assume it's a string + if (!declared) + { + switch(declare_as) + { + case TYPE_COL4: + declareColor4(name, LLColor4::white, "", NO_PERSIST); + break; + case TYPE_COL4U: + declareColor4U(name, LLColor4U::white, "", NO_PERSIST); + break; + case TYPE_STRING: + default: + declareString(name, LLString::null, "", NO_PERSIST); + break; + } + } + + // Control name has been declared in code. + LLControlBase *control = getControl(name); + + llassert(control); + + switch(control->mType) + { + case TYPE_F32: + { + F32 initial = 0.f; + + child_nodep->getAttributeF32("value", initial); + + control->set(initial); + validitems++; + } + break; + case TYPE_S32: + { + S32 initial = 0; + + child_nodep->getAttributeS32("value", initial); + + control->set(initial); + validitems++; + } + break; + case TYPE_U32: + { + U32 initial = 0; + child_nodep->getAttributeU32("value", initial); + control->set((LLSD::Integer) initial); + validitems++; + } + break; + case TYPE_BOOLEAN: + { + BOOL initial = FALSE; + + child_nodep->getAttributeBOOL("value", initial); + control->set(initial); + + validitems++; + } + break; + case TYPE_STRING: + { + LLString string; + child_nodep->getAttributeString("value", string); + if (string == LLString::null) + { + string = ""; + } + control->set(string); + validitems++; + } + break; + case TYPE_VEC3: + { + LLVector3 vector; + + child_nodep->getAttributeVector3("value", vector); + control->set(vector.getValue()); + validitems++; + } + break; + case TYPE_VEC3D: + { + LLVector3d vector; + + child_nodep->getAttributeVector3d("value", vector); + + control->set(vector.getValue()); + validitems++; + } + break; + case TYPE_RECT: + { + //RN: hack to support reading rectangles from a string + LLString rect_string; + + child_nodep->getAttributeString("value", rect_string); + std::istringstream istream(rect_string); + S32 left, bottom, width, height; + + istream >> left >> bottom >> width >> height; + + LLRect rect; + rect.setOriginAndSize(left, bottom, width, height); + + control->set(rect.getValue()); + validitems++; + } + break; + case TYPE_COL4U: + { + LLColor4U color; + + child_nodep->getAttributeColor4U("value", color); + control->set(color.getValue()); + validitems++; + } + break; + case TYPE_COL4: + { + LLColor4 color; + + child_nodep->getAttributeColor4("value", color); + control->set(color.getValue()); + validitems++; + } + break; + case TYPE_COL3: + { + LLVector3 color; + + child_nodep->getAttributeVector3("value", color); + control->set(LLColor3(color.mV).getValue()); + validitems++; + } + break; + } + + child_nodep = rootp->getNextChild(); + } + + return validitems; +} + + +U32 LLControlGroup::saveToFile(const LLString& filename, BOOL nondefault_only) +{ + const char ENDL = '\n'; + + llinfos << "Saving settings to file: " << filename << llendl; + + // place the objects in a temporary container that enforces a sort + // order to ease manual editing of the file + LLLinkedList< LLControlBase > controls; + controls.setInsertBefore( &control_insert_before ); + LLString name; + for (ctrl_name_table_t::iterator iter = mNameTable.begin(); + iter != mNameTable.end(); iter++) + { + name = iter->first; + if (name.empty()) + { + CONTROL_ERRS << "Control with no name found!!!" << llendl; + break; + } + + LLControlBase* control = (LLControlBase *)mNameTable[name]; + + if (!control) + { + llwarns << "Tried to save invalid control: " << name << llendl; + } + + if( control && control->mPersist ) + { + if (!(nondefault_only && (control->mIsDefault))) + { + controls.addDataSorted( control ); + } + else + { + // Debug spam + // llinfos << "Skipping " << control->getName() << llendl; + } + } + } + + llofstream file; + file.open(filename.c_str()); + + if (!file.is_open()) + { + // This is a warning because sometime we want to use settings files which can't be written... + llwarns << "LLControlGroup::saveToFile unable to open file for writing" << llendl; + return 0; + } + + // Write file version + file << "\n"; + file << "\n"; + for( LLControlBase* control = controls.getFirstData(); + control != NULL; + control = controls.getNextData() ) + { + file << "\t" << ENDL; + name = control->name(); + switch (control->type()) + { + case TYPE_U32: + { + file << "\t<" << name << " value=\"" << (U32) control->get().asInteger() << "\"/>\n"; + break; + } + case TYPE_S32: + { + file << "\t<" << name << " value=\"" << (S32) control->get().asInteger() << "\"/>\n"; + break; + } + case TYPE_F32: + { + file << "\t<" << name << " value=\"" << (F32) control->get().asReal() << "\"/>\n"; + break; + } + case TYPE_VEC3: + { + LLVector3 vector(control->get()); + file << "\t<" << name << " value=\"" << vector.mV[VX] << " " << vector.mV[VY] << " " << vector.mV[VZ] << "\"/>\n"; + break; + } + case TYPE_VEC3D: + { + LLVector3d vector(control->get()); + file << "\t<" << name << " value=\"" << vector.mdV[VX] << " " << vector.mdV[VY] << " " << vector.mdV[VZ] << "\"/>\n"; + break; + } + case TYPE_RECT: + { + LLRect rect(control->get()); + file << "\t<" << name << " value=\"" << rect.mLeft << " " << rect.mBottom << " " << rect.getWidth() << " " << rect.getHeight() << "\"/>\n"; + break; + } + case TYPE_COL4: + { + LLColor4 color(control->get()); + file << "\t<" << name << " value=\"" << color.mV[VRED] << ", " << color.mV[VGREEN] << ", " << color.mV[VBLUE] << ", " << color.mV[VALPHA] << "\"/>\n"; + break; + } + case TYPE_COL3: + { + LLColor3 color(control->get()); + file << "\t<" << name << " value=\"" << color.mV[VRED] << ", " << color.mV[VGREEN] << ", " << color.mV[VBLUE] << "\"/>\n"; + break; + } + case TYPE_BOOLEAN: + { + file << "\t<" << name << " value=\"" << (control->get().asBoolean() ? "TRUE" : "FALSE") << "\"/>\n"; + break; + } + case TYPE_STRING: + { + file << "\t<" << name << " value=\"" << LLSDXMLFormatter::escapeString(control->get().asString()) << "\"/>\n"; + break; + } + default: + { + CONTROL_ERRS << "LLControlGroup::saveToFile - unknown control type!" << llendl; + break; + } + } + + // Debug spam + // llinfos << name << " " << control->getValue().asString() << llendl; + }// next + + file << "\n"; + file.close(); + + return controls.getLength(); +} + +void LLControlGroup::applyOverrides(const std::map& overrides) +{ + for (std::map::const_iterator iter = overrides.begin(); + iter != overrides.end(); ++iter) + { + const std::string& command = iter->first; + const std::string& value = iter->second; + LLControlBase* control = (LLControlBase *)mNameTable[command]; + if (control) + { + switch(control->mType) + { + case TYPE_U32: + control->set((LLSD::Integer)atof(value.c_str())); + break; + case TYPE_S32: + control->set((S32)atof(value.c_str())); + break; + case TYPE_F32: + control->set((F32)atof(value.c_str())); + break; + case TYPE_BOOLEAN: + if (!LLString::compareInsensitive(value.c_str(), "TRUE")) + { + control->set(TRUE); + } + else if (!LLString::compareInsensitive(value.c_str(), "FALSE")) + { + control->set(FALSE); + } + else + { + control->set((BOOL)atof(value.c_str())); + } + break; + case TYPE_STRING: + control->set(value); + break; +// // *FIX: implement this given time and need. +// case TYPE_UUID: +// break; + // we don't support command line overrides of vec3 or col4 + // yet - requires parsing of multiple values + case TYPE_VEC3: + case TYPE_VEC3D: + case TYPE_COL4: + case TYPE_COL3: + default: + break; + } + } + else + { + llinfos << "There is no control variable " << command << llendl; + } + } +} + +void LLControlGroup::resetToDefaults() +{ + ctrl_name_table_t::iterator control_iter; + for (control_iter = mNameTable.begin(); + control_iter != mNameTable.end(); + ++control_iter) + { + LLControlBase* control = (*control_iter).second; + control->resetToDefault(); + } +} + +//============================================================================ +// FIrst-use + + +void LLControlGroup::addWarning(const LLString& name) +{ + LLString warnname = "Warn" + name; + if(!mNameTable[warnname]) + { + LLString comment = LLString("Enables ") + name + LLString(" warning dialog"); + declareBOOL(warnname, TRUE, comment); + mWarnings.insert(warnname); + } +} + +BOOL LLControlGroup::getWarning(const LLString& name) +{ + LLString warnname = "Warn" + name; + return getBOOL(warnname); +} + +void LLControlGroup::setWarning(const LLString& name, BOOL val) +{ + LLString warnname = "Warn" + name; + setBOOL(warnname, val); +} + +void LLControlGroup::resetWarnings() +{ + for (std::set::iterator iter = mWarnings.begin(); + iter != mWarnings.end(); ++iter) + { + setBOOL(*iter, TRUE); + } +} + + + +//============================================================================= +// Listener ID generator/management + +void LLControlBase::releaseListenerID(S32 id) +{ + mFreeIDs.push_back(id); +} + +S32 LLControlBase::allocateListenerID() +{ + if(mFreeIDs.size() == 0) + { //Out of IDs so generate some new ones. + for(int t=0;t<32;t++) + { + mFreeIDs.push_back(mTopID++); + } + } + S32 rtn = mFreeIDs.front(); + mFreeIDs.pop_front(); + mUsedIDs.push_back(rtn); + return rtn; +} + +bool LLControlBase::handleEvent(LLPointer event, const LLSD& userdata) +{ + if (event->desc() == "value_changed") + { + setValue(((LLValueChangedEvent*)(LLEvent*)event)->mValue); + return TRUE; + } + return TRUE; +} + +void LLControlBase::firePropertyChanged() +{ + LLValueChangedEvent *evt = new LLValueChangedEvent(this, getValue()); + fireEvent(evt, ""); +} + +//============================================================================ +// Used to add a listener callback that will be called on the frame that the controls value changes + +S32 LLControl::addListener(LLControl::tListenerCallback* cbfn) +{ + S32 id = allocateListenerID(); + mListeners.push_back(cbfn); + mListenerIDs.push_back( id ); + return id; +} + +void LLControl::updateListeners() { + LLControl::tPropertyChangedListIter iter = mChangeEvents.begin(); + while(iter!=mChangeEvents.end()){ + LLControl::tPropertyChangedEvent& evt = *iter; + (*evt.mCBFN)(evt.mNewValue,evt.mID,*this); + iter++; + } + mChangeEvents.clear(); +} + +//static +void LLControlBase::updateAllListeners() +{ + std::set< LLControlBase* >::iterator iter = mChangedControls.begin(); + while(iter != mChangedControls.end()){ + (*iter)->updateListeners(); + iter++; + } + mChangedControls.clear(); +} + +LLControl::LLControl( + const LLString& name, + eControlType type, + LLSD initial, + const LLString& comment, + BOOL persist) : + LLControlBase(name, type, comment, persist), + mCurrent(initial), + mDefault(initial) +{ +} + +//============================================================================ + +#ifdef TEST_HARNESS +void main() +{ + F32_CONTROL foo, getfoo; + + S32_CONTROL bar, getbar; + + BOOL_CONTROL baz; + + U32 count = gGlobals.loadFromFile("controls.ini"); + llinfos << "Loaded " << count << " controls" << llendl; + + // test insertion + foo = new LLControl("gFoo", 5.f, 1.f, 20.f); + gGlobals.addEntry("gFoo", foo); + + bar = new LLControl("gBar", 10, 2, 22); + gGlobals.addEntry("gBar", bar); + + baz = new LLControl("gBaz", FALSE); + gGlobals.addEntry("gBaz", baz); + + // test retrieval + getfoo = (LLControl*) gGlobals.resolveName("gFoo"); + getfoo->dump(); + + getbar = (S32_CONTROL) gGlobals.resolveName("gBar"); + getbar->dump(); + + // change data + getfoo->set(10.f); + getfoo->dump(); + + // Failure modes + + // ...min > max + // badfoo = new LLControl("gFoo2", 100.f, 20.f, 5.f); + + // ...initial > max + // badbar = new LLControl("gBar2", 10, 20, 100000); + + // ...misspelled name + // getfoo = (F32_CONTROL) gGlobals.resolveName("fooMisspelled"); + // getfoo->dump(); + + // ...invalid data type + getfoo = (F32_CONTROL) gGlobals.resolveName("gFoo"); + getfoo->set(TRUE); + getfoo->dump(); + + // ...out of range data + // getfoo->set(100000000.f); + // getfoo->dump(); + + // Clean Up + delete foo; + delete bar; + delete baz; +} +#endif + +BOOL control_insert_before( LLControlBase* first, LLControlBase* second ) +{ + return ( first->getName().compare(second->getName()) < 0 ); +} + diff --git a/indra/llxml/llcontrol.h b/indra/llxml/llcontrol.h new file mode 100644 index 0000000000..b88f388a0b --- /dev/null +++ b/indra/llxml/llcontrol.h @@ -0,0 +1,255 @@ +/** + * @file llcontrol.h + * @brief A mechanism for storing "control state" for a program + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLCONTROL_H +#define LL_LLCONTROL_H + +#include "llevent.h" +#include "llnametable.h" +#include "llmap.h" +#include "llstring.h" +#include "llrect.h" + +class LLVector3; +class LLVector3d; +class LLColor4; +class LLColor3; +class LLColor4U; + +const BOOL NO_PERSIST = FALSE; + +typedef enum e_control_type +{ + TYPE_U32, + TYPE_S32, + TYPE_F32, + TYPE_BOOLEAN, + TYPE_STRING, + TYPE_VEC3, + TYPE_VEC3D, + TYPE_RECT, + TYPE_COL4, + TYPE_COL3, + TYPE_COL4U +} eControlType; + +class LLControlBase : public LLSimpleListenerObservable +{ +friend class LLControlGroup; +protected: + LLString mName; + LLString mComment; + eControlType mType; + BOOL mHasRange; + BOOL mPersist; + BOOL mIsDefault; + static std::set mChangedControls; + static std::list mFreeIDs;//These lists are used to store the ID's of registered event listeners. + static std::list mUsedIDs; + static S32 mTopID;//This is the index of the highest ID event listener ID. When the free pool is exhausted, new IDs are allocated from here. + +public: + static void releaseListenerID(S32 id); + static S32 allocateListenerID(); + static void updateAllListeners(); + virtual void updateListeners() = 0; + + LLControlBase(const LLString& name, eControlType type, const LLString& comment, BOOL persist) + : mName(name), + mComment(comment), + mType(type), + mHasRange(FALSE), + mPersist(persist), + mIsDefault(TRUE) + { + if (mPersist && mComment.empty()) + { + llerrs << "Must supply a comment for control " << mName << llendl; + } + sMaxControlNameLength = llmax((U32)mName.size(), sMaxControlNameLength); + } + + virtual ~LLControlBase(); + + const LLString& getName() const { return mName; } + const LLString& getComment() const { return mComment; } + + eControlType type() { return mType; } + BOOL isType(eControlType tp) { return tp == mType; } + + // Defaults to no-op + virtual void resetToDefault(); + + LLSD registerListener(LLSimpleListenerObservable *listener, LLSD userdata = ""); + + virtual LLSD get() const = 0; + virtual LLSD getValue() const = 0; + virtual void setValue(LLSD value) = 0; + virtual void set(LLSD value) = 0; + + // From LLSimpleListener + virtual bool handleEvent(LLPointer event, const LLSD& userdata); + + void firePropertyChanged(); + + static U32 sMaxControlNameLength; + +protected: + const char* name() { return mName.c_str(); } + const char* comment() { return mComment.c_str(); } +}; + +class LLControl +: public LLControlBase +{ +friend class LLControlGroup; +protected: + LLSD mCurrent; + LLSD mDefault; + +public: + + typedef void tListenerCallback(const LLSD& newValue,S32 listenerID, LLControl& control); + typedef struct{ + S32 mID; + LLSD mNewValue; + tListenerCallback* mCBFN; + }tPropertyChangedEvent; + + typedef std::list::iterator tPropertyChangedListIter; + std::list mChangeEvents; + std::list< tListenerCallback* > mListeners; + std::list< S32 > mListenerIDs; + + virtual void updateListeners(); + S32 addListener(tListenerCallback* cbfn); + + LLControl( + const LLString& name, + eControlType type, + LLSD initial, const + LLString& comment, + BOOL persist = TRUE); + + void set(LLSD val) { setValue(val); } + LLSD get() const { return getValue(); } + LLSD getdefault() const { return mDefault; } + LLSD getValue() const { return mCurrent; } + BOOL llsd_compare(const LLSD& a, const LLSD& b); + + void setValue(LLSD value) + { + if (llsd_compare(mCurrent, value) == FALSE) + { + mCurrent = value; + mIsDefault = llsd_compare(mCurrent, mDefault); + firePropertyChanged(); + } + } + + /*virtual*/ void resetToDefault() { mCurrent = mDefault; mIsDefault = TRUE;} + + virtual ~LLControl() + { + //Remove and deregister all listeners.. + while(mListenerIDs.size()) + { + S32 id = mListenerIDs.front(); + mListenerIDs.pop_front(); + releaseListenerID(id); + } + } +}; + +//const U32 STRING_CACHE_SIZE = 10000; +class LLControlGroup +{ +public: + typedef std::map > ctrl_name_table_t; + ctrl_name_table_t mNameTable; + std::set mWarnings; + +public: + LLControlGroup(); + ~LLControlGroup(); + void cleanup(); + + LLControlBase* getControl(const LLString& name); + LLSD registerListener(const LLString& name, LLSimpleListenerObservable *listener); + + BOOL declareControl(const LLString& name, eControlType type, const LLSD initial_val, const LLString& comment, BOOL persist); + BOOL declareU32(const LLString& name, U32 initial_val, const LLString& comment, BOOL persist = TRUE); + BOOL declareS32(const LLString& name, S32 initial_val, const LLString& comment, BOOL persist = TRUE); + BOOL declareF32(const LLString& name, F32 initial_val, const LLString& comment, BOOL persist = TRUE); + BOOL declareBOOL(const LLString& name, BOOL initial_val, const LLString& comment, BOOL persist = TRUE); + BOOL declareString(const LLString& name, const LLString &initial_val, const LLString& comment, BOOL persist = TRUE); + BOOL declareVec3(const LLString& name, const LLVector3 &initial_val,const LLString& comment, BOOL persist = TRUE); + BOOL declareVec3d(const LLString& name, const LLVector3d &initial_val, const LLString& comment, BOOL persist = TRUE); + BOOL declareRect(const LLString& name, const LLRect &initial_val, const LLString& comment, BOOL persist = TRUE); + BOOL declareColor4U(const LLString& name, const LLColor4U &initial_val, const LLString& comment, BOOL persist = TRUE); + BOOL declareColor4(const LLString& name, const LLColor4 &initial_val, const LLString& comment, BOOL persist = TRUE); + BOOL declareColor3(const LLString& name, const LLColor3 &initial_val, const LLString& comment, BOOL persist = TRUE); + + LLString findString(const LLString& name); + + LLString getString(const LLString& name); + LLWString getWString(const LLString& name); + LLString getText(const LLString& name); + LLVector3 getVector3(const LLString& name); + LLVector3d getVector3d(const LLString& name); + LLRect getRect(const LLString& name); + BOOL getBOOL(const LLString& name); + S32 getS32(const LLString& name); + F32 getF32(const LLString& name); + U32 getU32(const LLString& name); + LLSD getValue(const LLString& name); + + + // Note: If an LLColor4U control exists, it will cast it to the correct + // LLColor4 for you. + LLColor4 getColor(const LLString& name); + LLColor4U getColor4U(const LLString& name); + LLColor4 getColor4(const LLString& name); + LLColor3 getColor3(const LLString& name); + + void setBOOL(const LLString& name, BOOL val); + void setS32(const LLString& name, S32 val); + void setF32(const LLString& name, F32 val); + void setU32(const LLString& name, U32 val); + void setString(const LLString& name, const LLString& val); + void setVector3(const LLString& name, const LLVector3 &val); + void setVector3d(const LLString& name, const LLVector3d &val); + void setRect(const LLString& name, const LLRect &val); + void setColor4U(const LLString& name, const LLColor4U &val); + void setColor4(const LLString& name, const LLColor4 &val); + void setColor3(const LLString& name, const LLColor3 &val); + void setValue(const LLString& name, const LLSD& val); + + BOOL controlExists(const LLString& name); + + // Returns number of controls loaded, 0 if failed + // If require_declaration is false, will auto-declare controls it finds + // as the given type. + U32 loadFromFileLegacy(const LLString& filename, BOOL require_declaration = TRUE, eControlType declare_as = TYPE_STRING); + U32 loadFromFile(const LLString& filename, BOOL require_declaration = TRUE, eControlType declare_as = TYPE_STRING); + U32 saveToFile(const LLString& filename, BOOL skip_if_default); + void applyOverrides(const std::map& overrides); + void resetToDefaults(); + + // Ignorable Warnings + + // Add a config variable to be reset on resetWarnings() + void addWarning(const LLString& name); + BOOL getWarning(const LLString& name); + void setWarning(const LLString& name, BOOL val); + + // Resets all ignorables + void resetWarnings(); +}; + +#endif diff --git a/indra/llxml/llxmlnode.cpp b/indra/llxml/llxmlnode.cpp new file mode 100644 index 0000000000..7d77fa8be7 --- /dev/null +++ b/indra/llxml/llxmlnode.cpp @@ -0,0 +1,3008 @@ +/** + * @file llxmlnode.cpp + * @author Tom Yedwab + * @brief LLXMLNode implementation + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include +#include + +#include "llxmlnode.h" + +#include "v3color.h" +#include "v4color.h" +#include "v4coloru.h" +#include "v3math.h" +#include "v3dmath.h" +#include "v4math.h" +#include "llquaternion.h" +#include "lluuid.h" + +const S32 MAX_COLUMN_WIDTH = 80; + +// static +BOOL LLXMLNode::sStripEscapedStrings = TRUE; +BOOL LLXMLNode::sStripWhitespaceValues = FALSE; + +LLXMLNode::LLXMLNode() : + mID(""), + mIsAttribute(FALSE), + mVersionMajor(0), + mVersionMinor(0), + mLength(0), + mPrecision(64), + mType(TYPE_CONTAINER), + mEncoding(ENCODING_DEFAULT), + mParent(NULL), + mChildren(NULL), + mName(NULL), + mValue(""), + mDefault(NULL) +{ +} + +LLXMLNode::LLXMLNode(const LLString& name, BOOL is_attribute) : + mID(""), + mIsAttribute(is_attribute), + mVersionMajor(0), + mVersionMinor(0), + mLength(0), + mPrecision(64), + mType(TYPE_CONTAINER), + mEncoding(ENCODING_DEFAULT), + mParent(NULL), + mChildren(NULL), + mValue(""), + mDefault(NULL) +{ + mName = gStringTable.addStringEntry(name); +} + +LLXMLNode::LLXMLNode(LLStringTableEntry* name, BOOL is_attribute) : + mID(""), + mIsAttribute(is_attribute), + mVersionMajor(0), + mVersionMinor(0), + mLength(0), + mPrecision(64), + mType(TYPE_CONTAINER), + mEncoding(ENCODING_DEFAULT), + mParent(NULL), + mChildren(NULL), + mName(name), + mValue(""), + mDefault(NULL) +{ +} + +// virtual +LLXMLNode::~LLXMLNode() +{ + // Strictly speaking none of this should be required execept 'delete mChildren'... + if (mChildren) + { + for (LLXMLChildList::iterator iter = mChildren->map.begin(); + iter != mChildren->map.end(); ++iter) + { + LLXMLNodePtr child = iter->second; + child->mParent = NULL; + child->mNext = NULL; + child->mPrev = NULL; + } + mChildren->map.clear(); + mChildren->head = NULL; + mChildren->tail = NULL; + delete mChildren; + } + for (LLXMLAttribList::iterator iter = mAttributes.begin(); + iter != mAttributes.end(); ++iter) + { + LLXMLNodePtr attr = iter->second; + attr->mParent = NULL; + attr->mNext = NULL; + attr->mPrev = NULL; + } + llassert(mParent == NULL); + mDefault = NULL; +} + +BOOL LLXMLNode::isNull() +{ + return (mName == NULL); +} + +// protected +BOOL LLXMLNode::removeChild(LLXMLNode *target_child) +{ + if (!target_child) + { + return FALSE; + } + if (target_child->mIsAttribute) + { + LLXMLAttribList::iterator children_itr = mAttributes.find(target_child->mName); + if (children_itr != mAttributes.end()) + { + target_child->mParent = NULL; + mAttributes.erase(children_itr); + return TRUE; + } + } + else if (mChildren) + { + LLXMLChildList::iterator children_itr = mChildren->map.find(target_child->mName); + while (children_itr != mChildren->map.end()) + { + if (target_child == children_itr->second) + { + if (target_child == mChildren->head) + { + mChildren->head = target_child->mNext; + } + + LLXMLNodePtr prev = target_child->mPrev; + LLXMLNodePtr next = target_child->mNext; + if (prev.notNull()) prev->mNext = next; + if (next.notNull()) next->mPrev = prev; + + target_child->mPrev = NULL; + target_child->mNext = NULL; + target_child->mParent = NULL; + mChildren->map.erase(children_itr); + if (mChildren->map.empty()) + { + delete mChildren; + mChildren = NULL; + } + return TRUE; + } + else if (children_itr->first != target_child->mName) + { + break; + } + else + { + ++children_itr; + } + } + } + return FALSE; +} + +void LLXMLNode::addChild(LLXMLNodePtr new_child) +{ + if (new_child->mParent != NULL) + { + if (new_child->mParent == this) + { + return; + } + new_child->mParent->removeChild(new_child); + } + + new_child->mParent = this; + if (new_child->mIsAttribute) + { + mAttributes.insert(std::pair(new_child->mName, new_child)); + } + else + { + if (!mChildren) + { + mChildren = new LLXMLChildren(); + mChildren->head = new_child; + mChildren->tail = new_child; + } + mChildren->map.insert(std::pair(new_child->mName, new_child)); + + if (mChildren->tail != new_child) + { + mChildren->tail->mNext = new_child; + new_child->mPrev = mChildren->tail; + mChildren->tail = new_child; + } + } + + new_child->updateDefault(); +} + +// virtual +LLXMLNodePtr LLXMLNode::createChild(const LLString& name, BOOL is_attribute) +{ + return createChild(gStringTable.addStringEntry(name), is_attribute); +} + +// virtual +LLXMLNodePtr LLXMLNode::createChild(LLStringTableEntry* name, BOOL is_attribute) +{ + LLXMLNode* ret = new LLXMLNode(name, is_attribute); + ret->mID = ""; + addChild(ret); + return ret; +} + +BOOL LLXMLNode::deleteChild(LLXMLNode *child) +{ + if (removeChild(child)) + { + return TRUE; + } + return FALSE; +} + +void LLXMLNode::setParent(LLXMLNodePtr new_parent) +{ + if (new_parent.notNull()) + { + new_parent->addChild(this); + } + else + { + if (mParent != NULL) + { + LLXMLNodePtr old_parent = mParent; + mParent = NULL; + old_parent->removeChild(this); + } + } +} + + +void LLXMLNode::updateDefault() +{ + if (mParent != NULL && !mParent->mDefault.isNull()) + { + mDefault = NULL; + + // Find default value in parent's default tree + if (!mParent->mDefault.isNull()) + { + findDefault(mParent->mDefault); + } + } + + if (mChildren) + { + LLXMLChildList::const_iterator children_itr; + LLXMLChildList::const_iterator children_end = mChildren->map.end(); + for (children_itr = mChildren->map.begin(); children_itr != children_end; ++children_itr) + { + LLXMLNodePtr child = (*children_itr).second; + child->updateDefault(); + } + } +} + +void XMLCALL StartXMLNode(void *userData, + const XML_Char *name, + const XML_Char **atts) +{ + // Create a new node + LLXMLNode *new_node_ptr = new LLXMLNode(name, FALSE); + LLXMLNodePtr new_node = new_node_ptr; + new_node->mID = ""; + LLXMLNodePtr ptr_new_node = new_node; + + // Set the parent-child relationship with the current active node + LLXMLNode* parent = (LLXMLNode *)userData; + + new_node_ptr->mParser = parent->mParser; + + // Set the current active node to the new node + XML_Parser *parser = parent->mParser; + XML_SetUserData(*parser, (void *)new_node_ptr); + + // Parse attributes + U32 pos = 0; + while (atts[pos] != NULL) + { + LLString attr_name = atts[pos]; + LLString attr_value = atts[pos+1]; + + // Special cases + if ('i' == attr_name[0] && "id" == attr_name) + { + new_node->mID = attr_value; + } + else if ('v' == attr_name[0] && "version" == attr_name) + { + U32 version_major = 0; + U32 version_minor = 0; + if (sscanf(attr_value.c_str(), "%d.%d", &version_major, &version_minor) > 0) + { + new_node->mVersionMajor = version_major; + new_node->mVersionMinor = version_minor; + } + } + else if (('s' == attr_name[0] && "size" == attr_name) || ('l' == attr_name[0] && "length" == attr_name)) + { + U32 length; + if (sscanf(attr_value.c_str(), "%d", &length) > 0) + { + new_node->mLength = length; + } + } + else if ('p' == attr_name[0] && "precision" == attr_name) + { + U32 precision; + if (sscanf(attr_value.c_str(), "%d", &precision) > 0) + { + new_node->mPrecision = precision; + } + } + else if ('t' == attr_name[0] && "type" == attr_name) + { + if ("boolean" == attr_value) + { + new_node->mType = LLXMLNode::TYPE_BOOLEAN; + } + else if ("integer" == attr_value) + { + new_node->mType = LLXMLNode::TYPE_INTEGER; + } + else if ("float" == attr_value) + { + new_node->mType = LLXMLNode::TYPE_FLOAT; + } + else if ("string" == attr_value) + { + new_node->mType = LLXMLNode::TYPE_STRING; + } + else if ("uuid" == attr_value) + { + new_node->mType = LLXMLNode::TYPE_UUID; + } + else if ("noderef" == attr_value) + { + new_node->mType = LLXMLNode::TYPE_NODEREF; + } + } + else if ('e' == attr_name[0] && "encoding" == attr_name) + { + if ("decimal" == attr_value) + { + new_node->mEncoding = LLXMLNode::ENCODING_DECIMAL; + } + else if ("hex" == attr_value) + { + new_node->mEncoding = LLXMLNode::ENCODING_HEX; + } + /*else if (attr_value == "base32") + { + new_node->mEncoding = LLXMLNode::ENCODING_BASE32; + }*/ + } + + // only one attribute child per description + LLXMLNodePtr attr_node; + if (!new_node->getAttribute(attr_name, attr_node, FALSE)) + { + attr_node = new LLXMLNode(attr_name, TRUE); + } + attr_node->setValue(attr_value); + new_node->addChild(attr_node); + + pos += 2; + } + + if (parent) + { + parent->addChild(new_node); + } +} + +void XMLCALL EndXMLNode(void *userData, + const XML_Char *name) +{ + // [FUGLY] Set the current active node to the current node's parent + LLXMLNode *node = (LLXMLNode *)userData; + XML_Parser *parser = node->mParser; + XML_SetUserData(*parser, (void *)node->mParent); + // SJB: total hack: + if (LLXMLNode::sStripWhitespaceValues) + { + LLString value = node->getValue(); + BOOL is_empty = TRUE; + for (std::string::size_type s = 0; s < value.length(); s++) + { + char c = value[s]; + if (c != ' ' && c != '\t' && c != '\n') + { + is_empty = FALSE; + break; + } + } + if (is_empty) + { + value.clear(); + node->setValue(value); + } + } +} + +void XMLCALL XMLData(void *userData, + const XML_Char *s, + int len) +{ + LLXMLNode* current_node = (LLXMLNode *)userData; + LLString value = current_node->getValue(); + if (LLXMLNode::sStripEscapedStrings) + { + if (s[0] == '\"' && s[len-1] == '\"') + { + // Special-case: Escaped string. + LLString unescaped_string; + for (S32 pos=1; possetValue(value); + return; + } + } + value.append(LLString(s, 0, len)); + current_node->setValue(value); +} + + + +// static +bool LLXMLNode::updateNode( + LLXMLNodePtr& node, + LLXMLNodePtr& update_node) +{ + + if (!node || !update_node) + { + llwarns << "Node invalid" << llendl; + return FALSE; + } + + //update the node value + node->mValue = update_node->mValue; + + //update all attribute values + LLXMLAttribList::const_iterator itor; + + for(itor = update_node->mAttributes.begin(); itor != update_node->mAttributes.end(); ++itor) + { + const LLStringTableEntry* attribNameEntry = (*itor).first; + LLXMLNodePtr updateAttribNode = (*itor).second; + + LLXMLNodePtr attribNode; + + node->getAttribute(attribNameEntry, attribNode, 0); + + if (attribNode) + { + attribNode->mValue = updateAttribNode->mValue; + } + } + + //update all of node's children with updateNodes children that match name + LLXMLNodePtr child; + LLXMLNodePtr updateChild; + + for (updateChild = update_node->getFirstChild(); updateChild.notNull(); + updateChild = updateChild->getNextSibling()) + { + for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + { + LLString nodeName; + LLString updateName; + + updateChild->getAttributeString("name", updateName); + child->getAttributeString("name", nodeName); + + + //if it's a combobox there's no name, but there is a value + if (updateName.empty()) + { + updateChild->getAttributeString("value", updateName); + child->getAttributeString("value", nodeName); + } + + if ((nodeName != "") && (updateName == nodeName)) + { + updateNode(child, updateChild); + break; + } + } + } + + return TRUE; +} + + + + +// static +bool LLXMLNode::parseFile( + LLString filename, + LLXMLNodePtr& node, + LLXMLNode* defaults_tree) +{ + // Read file + FILE* fp = LLFile::fopen(filename.c_str(), "rb"); + if (fp == NULL) + { + node = new LLXMLNode(); + return false; + } + fseek(fp, 0, SEEK_END); + U32 length = ftell(fp); + fseek(fp, 0, SEEK_SET); + + U8* buffer = new U8[length+1]; + fread(buffer, 1, length, fp); + buffer[length] = 0; + fclose(fp); + + bool rv = parseBuffer(buffer, length, node, defaults_tree); + delete [] buffer; + return rv; +} + +// static +bool LLXMLNode::parseBuffer( + U8* buffer, + U32 length, + LLXMLNodePtr& node, + LLXMLNode* defaults) +{ + // Init + XML_Parser my_parser = XML_ParserCreate(NULL); + XML_SetElementHandler(my_parser, StartXMLNode, EndXMLNode); + XML_SetCharacterDataHandler(my_parser, XMLData); + + // Create a root node + LLXMLNode *file_node_ptr = new LLXMLNode("XML", FALSE); + LLXMLNodePtr file_node = file_node_ptr; + + file_node->mParser = &my_parser; + + XML_SetUserData(my_parser, (void *)file_node_ptr); + + // Do the parsing + if (XML_Parse(my_parser, (const char *)buffer, length, TRUE) != XML_STATUS_OK) + { + llwarns << "Error parsing xml error code: " + << XML_ErrorString(XML_GetErrorCode(my_parser)) + << " on lne " << XML_GetCurrentLineNumber(my_parser) + << llendl; + } + + // Deinit + XML_ParserFree(my_parser); + + if (!file_node->mChildren || file_node->mChildren->map.size() != 1) + { + llwarns << "Parse failure - wrong number of top-level nodes xml." + << llendl; + node = new LLXMLNode(); + return false; + } + + LLXMLNode *return_node = file_node->mChildren->map.begin()->second; + + return_node->setDefault(defaults); + return_node->updateDefault(); + + node = return_node; + return true; +} + +BOOL LLXMLNode::isFullyDefault() +{ + if (mDefault.isNull()) + { + return FALSE; + } + BOOL has_default_value = (mValue == mDefault->mValue); + BOOL has_default_attribute = (mIsAttribute == mDefault->mIsAttribute); + BOOL has_default_type = mIsAttribute || (mType == mDefault->mType); + BOOL has_default_encoding = mIsAttribute || (mEncoding == mDefault->mEncoding); + BOOL has_default_precision = mIsAttribute || (mPrecision == mDefault->mPrecision); + BOOL has_default_length = mIsAttribute || (mLength == mDefault->mLength); + + if (has_default_value + && has_default_type + && has_default_encoding + && has_default_precision + && has_default_length + && has_default_attribute) + { + if (mChildren) + { + LLXMLChildList::const_iterator children_itr; + LLXMLChildList::const_iterator children_end = mChildren->map.end(); + for (children_itr = mChildren->map.begin(); children_itr != children_end; ++children_itr) + { + LLXMLNodePtr child = (*children_itr).second; + if (!child->isFullyDefault()) + { + return FALSE; + } + } + } + return TRUE; + } + + return FALSE; +} + +// static +void LLXMLNode::writeHeaderToFile(FILE *fOut) +{ + fprintf(fOut, "\n"); +} + +void LLXMLNode::writeToFile(FILE *fOut, LLString indent) +{ + if (isFullyDefault()) + { + // Don't write out nodes that are an exact match to defaults + return; + } + + std::ostringstream ostream; + writeToOstream(ostream, indent); + LLString outstring = ostream.str(); + fwrite(outstring.c_str(), 1, outstring.length(), fOut); +} + +void LLXMLNode::writeToOstream(std::ostream& output_stream, const LLString& indent) +{ + if (isFullyDefault()) + { + // Don't write out nodes that are an exact match to defaults + return; + } + + BOOL has_default_type = mDefault.isNull()?FALSE:(mType == mDefault->mType); + BOOL has_default_encoding = mDefault.isNull()?FALSE:(mEncoding == mDefault->mEncoding); + BOOL has_default_precision = mDefault.isNull()?FALSE:(mPrecision == mDefault->mPrecision); + BOOL has_default_length = mDefault.isNull()?FALSE:(mLength == mDefault->mLength); + + // stream the name + output_stream << indent.c_str() << "<" << mName->mString; + + // ID + if (mID != "") + { + output_stream << " id=\"" << mID.c_str() << "\""; + } + + // Type + if (!has_default_type) + { + switch (mType) + { + case TYPE_BOOLEAN: + output_stream << " type=\"boolean\""; + break; + case TYPE_INTEGER: + output_stream << " type=\"integer\""; + break; + case TYPE_FLOAT: + output_stream << " type=\"float\""; + break; + case TYPE_STRING: + output_stream << " type=\"string\""; + break; + case TYPE_UUID: + output_stream << " type=\"uuid\""; + break; + case TYPE_NODEREF: + output_stream << " type=\"noderef\""; + break; + default: + // default on switch(enum) eliminates a warning on linux + break; + }; + } + + // Encoding + if (!has_default_encoding) + { + switch (mEncoding) + { + case ENCODING_DECIMAL: + output_stream << " encoding=\"decimal\""; + break; + case ENCODING_HEX: + output_stream << " encoding=\"hex\""; + break; + /*case ENCODING_BASE32: + output_stream << " encoding=\"base32\""; + break;*/ + default: + // default on switch(enum) eliminates a warning on linux + break; + }; + } + + // Precision + if (!has_default_precision && (mType == TYPE_INTEGER || mType == TYPE_FLOAT)) + { + output_stream << " precision=\"" << mPrecision << "\""; + } + + // Version + if (mVersionMajor > 0 || mVersionMinor > 0) + { + output_stream << " version=\"" << mVersionMajor << "." << mVersionMinor << "\""; + } + + // Array length + if (!has_default_length && mLength > 0) + { + output_stream << " length=\"" << mLength << "\""; + } + + { + // Write out attributes + S32 col_pos = 0; + LLXMLAttribList::const_iterator attr_itr; + LLXMLAttribList::const_iterator attr_end = mAttributes.end(); + for (attr_itr = mAttributes.begin(); attr_itr != attr_end; ++attr_itr) + { + LLXMLNodePtr child = (*attr_itr).second; + if (child->mDefault.isNull() || child->mDefault->mValue != child->mValue) + { + LLString attr = child->mName->mString; + if (attr == "id" || + attr == "type" || + attr == "encoding" || + attr == "precision" || + attr == "version" || + attr == "length") + { + continue; // skip built-in attributes + } + + LLString attr_str = llformat(" %s=\"%s\"", + attr.c_str(), + escapeXML(child->mValue).c_str()); + if (col_pos + (S32)attr_str.length() > MAX_COLUMN_WIDTH) + { + output_stream << "\n" << indent << " "; + col_pos = 4; + } + col_pos += attr_str.length(); + output_stream << attr_str; + } + } + } + + if (!mChildren && mValue == "") + { + output_stream << " />\n"; + return; + } + else + { + output_stream << ">\n"; + if (mChildren) + { + // stream non-attributes + LLString next_indent = indent + "\t"; + for (LLXMLNode* child = getFirstChild(); child; child = child->getNextSibling()) + { + child->writeToOstream(output_stream, next_indent); + } + } + if (!mValue.empty()) + { + LLString contents = getTextContents(); + output_stream << indent.c_str() << "\t" << escapeXML(contents) << "\n"; + } + output_stream << indent.c_str() << "mString << ">\n"; + } +} + +void LLXMLNode::findName(const LLString& name, LLXMLNodeList &results) +{ + LLStringTableEntry* name_entry = gStringTable.checkStringEntry(name); + if (name_entry == mName) + { + results.insert(std::pair(this->mName->mString, this)); + return; + } + if (mChildren) + { + LLXMLChildList::const_iterator children_itr; + LLXMLChildList::const_iterator children_end = mChildren->map.end(); + for (children_itr = mChildren->map.begin(); children_itr != children_end; ++children_itr) + { + LLXMLNodePtr child = (*children_itr).second; + child->findName(name_entry, results); + } + } +} + +void LLXMLNode::findName(LLStringTableEntry* name, LLXMLNodeList &results) +{ + if (name == mName) + { + results.insert(std::pair(this->mName->mString, this)); + return; + } + if (mChildren) + { + LLXMLChildList::const_iterator children_itr; + LLXMLChildList::const_iterator children_end = mChildren->map.end(); + for (children_itr = mChildren->map.begin(); children_itr != children_end; ++children_itr) + { + LLXMLNodePtr child = (*children_itr).second; + child->findName(name, results); + } + } +} + +void LLXMLNode::findID(const LLString& id, LLXMLNodeList &results) +{ + if (id == mID) + { + results.insert(std::pair(this->mName->mString, this)); + return; + } + if (mChildren) + { + LLXMLChildList::const_iterator children_itr; + LLXMLChildList::const_iterator children_end = mChildren->map.end(); + for (children_itr = mChildren->map.begin(); children_itr != children_end; ++children_itr) + { + LLXMLNodePtr child = (*children_itr).second; + child->findID(id, results); + } + } +} + +void LLXMLNode::scrubToTree(LLXMLNode *tree) +{ + if (!tree || !tree->mChildren) + { + return; + } + if (mChildren) + { + std::vector to_delete_list; + LLXMLChildList::iterator itor = mChildren->map.begin(); + while (itor != mChildren->map.end()) + { + LLXMLNodePtr child = itor->second; + LLXMLNodePtr child_tree = NULL; + // Look for this child in the default's children + bool found = false; + LLXMLChildList::iterator itor2 = tree->mChildren->map.begin(); + while (itor2 != tree->mChildren->map.end()) + { + if (child->mName == itor2->second->mName) + { + child_tree = itor2->second; + found = true; + } + ++itor2; + } + if (!found) + { + to_delete_list.push_back(child); + } + else + { + child->scrubToTree(child_tree); + } + ++itor; + } + std::vector::iterator itor3; + for (itor3=to_delete_list.begin(); itor3!=to_delete_list.end(); ++itor3) + { + (*itor3)->setParent(NULL); + } + } +} + +bool LLXMLNode::getChild(const LLString& name, LLXMLNodePtr& node, BOOL use_default_if_missing) +{ + return getChild(gStringTable.checkStringEntry(name), node, use_default_if_missing); +} + +bool LLXMLNode::getChild(const LLStringTableEntry* name, LLXMLNodePtr& node, BOOL use_default_if_missing) +{ + if (mChildren) + { + LLXMLChildList::const_iterator child_itr = mChildren->map.find(name); + if (child_itr != mChildren->map.end()) + { + node = (*child_itr).second; + return true; + } + } + if (use_default_if_missing && !mDefault.isNull()) + { + return mDefault->getChild(name, node, FALSE); + } + node = new LLXMLNode(); + return false; +} + +void LLXMLNode::getChildren(const LLString& name, LLXMLNodeList &children, BOOL use_default_if_missing) const +{ + getChildren(gStringTable.checkStringEntry(name), children, use_default_if_missing); +} + +void LLXMLNode::getChildren(const LLStringTableEntry* name, LLXMLNodeList &children, BOOL use_default_if_missing) const +{ + if (mChildren) + { + LLXMLChildList::const_iterator child_itr = mChildren->map.find(name); + if (child_itr != mChildren->map.end()) + { + LLXMLChildList::const_iterator children_end = mChildren->map.end(); + while (child_itr != children_end) + { + LLXMLNodePtr child = (*child_itr).second; + if (name != child->mName) + { + break; + } + children.insert(std::pair(child->mName->mString, child)); + child_itr++; + } + } + } + if (children.size() == 0 && use_default_if_missing && !mDefault.isNull()) + { + mDefault->getChildren(name, children, FALSE); + } +} + +bool LLXMLNode::getAttribute(const LLString& name, LLXMLNodePtr& node, BOOL use_default_if_missing) +{ + return getAttribute(gStringTable.checkStringEntry(name), node, use_default_if_missing); +} + +bool LLXMLNode::getAttribute(const LLStringTableEntry* name, LLXMLNodePtr& node, BOOL use_default_if_missing) +{ + LLXMLAttribList::const_iterator child_itr = mAttributes.find(name); + if (child_itr != mAttributes.end()) + { + node = (*child_itr).second; + return true; + } + if (use_default_if_missing && !mDefault.isNull()) + { + return mDefault->getAttribute(name, node, FALSE); + } + node = new LLXMLNode(); + return false; +} + +bool LLXMLNode::setAttributeString(const LLString& attr, const LLString& value) +{ + LLStringTableEntry* name = gStringTable.checkStringEntry(attr); + LLXMLAttribList::const_iterator child_itr = mAttributes.find(name); + if (child_itr != mAttributes.end()) + { + LLXMLNodePtr node = (*child_itr).second; + node->setValue(value); + return true; + } + return false; +} + +BOOL LLXMLNode::hasAttribute(const LLString& name ) +{ + LLXMLNodePtr node; + return getAttribute(name, node); +} + +BOOL LLXMLNode::getAttributeBOOL(const LLString& name, BOOL& value ) +{ + LLXMLNodePtr node; + return (getAttribute(name, node) && node->getBoolValue(1, &value)); +} + +BOOL LLXMLNode::getAttributeU8(const LLString& name, U8& value ) +{ + LLXMLNodePtr node; + return (getAttribute(name, node) && node->getByteValue(1, &value)); +} + +BOOL LLXMLNode::getAttributeS8(const LLString& name, S8& value ) +{ + LLXMLNodePtr node; + S32 val; + if (!(getAttribute(name, node) && node->getIntValue(1, &val))) + { + return false; + } + value = val; + return true; +} + +BOOL LLXMLNode::getAttributeU16(const LLString& name, U16& value ) +{ + LLXMLNodePtr node; + U32 val; + if (!(getAttribute(name, node) && node->getUnsignedValue(1, &val))) + { + return false; + } + value = val; + return true; +} + +BOOL LLXMLNode::getAttributeS16(const LLString& name, S16& value ) +{ + LLXMLNodePtr node; + S32 val; + if (!(getAttribute(name, node) && node->getIntValue(1, &val))) + { + return false; + } + value = val; + return true; +} + +BOOL LLXMLNode::getAttributeU32(const LLString& name, U32& value ) +{ + LLXMLNodePtr node; + return (getAttribute(name, node) && node->getUnsignedValue(1, &value)); +} + +BOOL LLXMLNode::getAttributeS32(const LLString& name, S32& value ) +{ + LLXMLNodePtr node; + return (getAttribute(name, node) && node->getIntValue(1, &value)); +} + +BOOL LLXMLNode::getAttributeF32(const LLString& name, F32& value ) +{ + LLXMLNodePtr node; + return (getAttribute(name, node) && node->getFloatValue(1, &value)); +} + +BOOL LLXMLNode::getAttributeF64(const LLString& name, F64& value ) +{ + LLXMLNodePtr node; + return (getAttribute(name, node) && node->getDoubleValue(1, &value)); +} + +BOOL LLXMLNode::getAttributeColor(const LLString& name, LLColor4& value ) +{ + LLXMLNodePtr node; + return (getAttribute(name, node) && node->getFloatValue(4, value.mV)); +} + +BOOL LLXMLNode::getAttributeColor4(const LLString& name, LLColor4& value ) +{ + LLXMLNodePtr node; + return (getAttribute(name, node) && node->getFloatValue(4, value.mV)); +} + +BOOL LLXMLNode::getAttributeColor4U(const LLString& name, LLColor4U& value ) +{ + LLXMLNodePtr node; + return (getAttribute(name, node) && node->getByteValue(4, value.mV)); +} + +BOOL LLXMLNode::getAttributeVector3(const LLString& name, LLVector3& value ) +{ + LLXMLNodePtr node; + return (getAttribute(name, node) && node->getFloatValue(3, value.mV)); +} + +BOOL LLXMLNode::getAttributeVector3d(const LLString& name, LLVector3d& value ) +{ + LLXMLNodePtr node; + return (getAttribute(name, node) && node->getDoubleValue(3, value.mdV)); +} + +BOOL LLXMLNode::getAttributeQuat(const LLString& name, LLQuaternion& value ) +{ + LLXMLNodePtr node; + return (getAttribute(name, node) && node->getFloatValue(4, value.mQ)); +} + +BOOL LLXMLNode::getAttributeUUID(const LLString& name, LLUUID& value ) +{ + LLXMLNodePtr node; + return (getAttribute(name, node) && node->getUUIDValue(1, &value)); +} + +BOOL LLXMLNode::getAttributeString(const LLString& name, LLString& value ) +{ + LLXMLNodePtr node; + if (!getAttribute(name, node)) + { + return false; + } + value = node->getValue(); + return true; +} + +LLXMLNodePtr LLXMLNode::getRoot() +{ + if (mParent == NULL) + { + return this; + } + return mParent->getRoot(); +} + +/*static */ +const char *LLXMLNode::skipWhitespace(const char *str) +{ + // skip whitespace characters + while (str[0] == ' ' || str[0] == '\t' || str[0] == '\n') ++str; + return str; +} + +/*static */ +const char *LLXMLNode::skipNonWhitespace(const char *str) +{ + // skip non-whitespace characters + while (str[0] != ' ' && str[0] != '\t' && str[0] != '\n' && str[0] != 0) ++str; + return str; +} + +/*static */ +const char *LLXMLNode::parseInteger(const char *str, U64 *dest, BOOL *is_negative, U32 precision, Encoding encoding) +{ + *dest = 0; + *is_negative = FALSE; + + str = skipWhitespace(str); + + if (str[0] == 0) return NULL; + + if (encoding == ENCODING_DECIMAL || encoding == ENCODING_DEFAULT) + { + if (str[0] == '+') + { + ++str; + } + if (str[0] == '-') + { + *is_negative = TRUE; + ++str; + } + + str = skipWhitespace(str); + + U64 ret = 0; + while (str[0] >= '0' && str[0] <= '9') + { + ret *= 10; + ret += str[0] - '0'; + ++str; + } + + if (str[0] == '.') + { + // If there is a fractional part, skip it + str = skipNonWhitespace(str); + } + + *dest = ret; + return str; + } + if (encoding == ENCODING_HEX) + { + U64 ret = 0; + str = skipWhitespace(str); + for (U32 pos=0; pos<(precision/4); ++pos) + { + ret <<= 4; + str = skipWhitespace(str); + if (str[0] >= '0' && str[0] <= '9') + { + ret += str[0] - '0'; + } + else if (str[0] >= 'a' && str[0] <= 'f') + { + ret += str[0] - 'a' + 10; + } + else if (str[0] >= 'A' && str[0] <= 'F') + { + ret += str[0] - 'A' + 10; + } + else + { + return NULL; + } + ++str; + } + + *dest = ret; + return str; + } + return NULL; +} + +// 25 elements - decimal expansions of 1/(2^n), multiplied by 10 each iteration +const U64 float_coeff_table[] = + { 5, 25, 125, 625, 3125, + 15625, 78125, 390625, 1953125, 9765625, + 48828125, 244140625, 1220703125, 6103515625LL, 30517578125LL, + 152587890625LL, 762939453125LL, 3814697265625LL, 19073486328125LL, 95367431640625LL, + 476837158203125LL, 2384185791015625LL, 11920928955078125LL, 59604644775390625LL, 298023223876953125LL }; + +// 36 elements - decimal expansions of 1/(2^n) after the last 28, truncated, no multiply each iteration +const U64 float_coeff_table_2[] = + { 149011611938476562LL,74505805969238281LL, + 37252902984619140LL, 18626451492309570LL, 9313225746154785LL, 4656612873077392LL, + 2328306436538696LL, 1164153218269348LL, 582076609134674LL, 291038304567337LL, + 145519152283668LL, 72759576141834LL, 36379788070917LL, 18189894035458LL, + 9094947017729LL, 4547473508864LL, 2273736754432LL, 1136868377216LL, + 568434188608LL, 284217094304LL, 142108547152LL, 71054273576LL, + 35527136788LL, 17763568394LL, 8881784197LL, 4440892098LL, + 2220446049LL, 1110223024LL, 555111512LL, 277555756LL, + 138777878, 69388939, 34694469, 17347234, + 8673617, 4336808, 2168404, 1084202, + 542101, 271050, 135525, 67762, + }; + +/*static */ +const char *LLXMLNode::parseFloat(const char *str, F64 *dest, U32 precision, Encoding encoding) +{ + str = skipWhitespace(str); + + if (str[0] == 0) return NULL; + + if (encoding == ENCODING_DECIMAL || encoding == ENCODING_DEFAULT) + { + str = skipWhitespace(str); + + if (memcmp(str, "inf", 3) == 0) + { + *(U64 *)dest = 0x7FF0000000000000ll; + return str + 3; + } + if (memcmp(str, "-inf", 4) == 0) + { + *(U64 *)dest = 0xFFF0000000000000ll; + return str + 4; + } + if (memcmp(str, "1.#INF", 6) == 0) + { + *(U64 *)dest = 0x7FF0000000000000ll; + return str + 6; + } + if (memcmp(str, "-1.#INF", 7) == 0) + { + *(U64 *)dest = 0xFFF0000000000000ll; + return str + 7; + } + + F64 negative = 1.0f; + if (str[0] == '+') + { + ++str; + } + if (str[0] == '-') + { + negative = -1.0f; + ++str; + } + + const char* base_str = str; + str = skipWhitespace(str); + + // Parse the integer part of the expression + U64 int_part = 0; + while (str[0] >= '0' && str[0] <= '9') + { + int_part *= 10; + int_part += U64(str[0] - '0'); + ++str; + } + + U64 f_part = 0;//, f_decimal = 1; + if (str[0] == '.') + { + ++str; + U64 remainder = 0; + U32 pos = 0; + // Parse the decimal part of the expression + while (str[0] >= '0' && str[0] <= '9' && pos < 25) + { + remainder = (remainder*10) + U64(str[0] - '0'); + f_part <<= 1; + //f_decimal <<= 1; + // Check the n'th bit + if (remainder >= float_coeff_table[pos]) + { + remainder -= float_coeff_table[pos]; + f_part |= 1; + } + ++pos; + ++str; + } + if (pos == 25) + { + // Drop any excessive digits + while (str[0] >= '0' && str[0] <= '9') + { + ++str; + } + } + else + { + while (pos < 25) + { + remainder *= 10; + f_part <<= 1; + //f_decimal <<= 1; + // Check the n'th bit + if (remainder >= float_coeff_table[pos]) + { + remainder -= float_coeff_table[pos]; + f_part |= 1; + } + ++pos; + } + } + pos = 0; + while (pos < 36) + { + f_part <<= 1; + //f_decimal <<= 1; + if (remainder >= float_coeff_table_2[pos]) + { + remainder -= float_coeff_table_2[pos]; + f_part |= 1; + } + ++pos; + } + } + + F64 ret = F64(int_part) + (F64(f_part)/F64(1LL<<61)); + + F64 exponent = 1.f; + if (str[0] == 'e') + { + // Scientific notation! + ++str; + U64 exp; + BOOL is_negative; + str = parseInteger(str, &exp, &is_negative, 64, ENCODING_DECIMAL); + if (str == NULL) + { + exp = 1; + } + F64 exp_d = F64(exp) * (is_negative?-1:1); + exponent = pow(10.0, exp_d); + } + + if (str == base_str) + { + // no digits parsed + return NULL; + } + else + { + *dest = ret*negative*exponent; + return str; + } + } + if (encoding == ENCODING_HEX) + { + U64 bytes_dest; + BOOL is_negative; + str = parseInteger(str, (U64 *)&bytes_dest, &is_negative, precision, ENCODING_HEX); + // Upcast to F64 + switch (precision) + { + case 32: + { + U32 short_dest = (U32)bytes_dest; + F32 ret_val = *(F32 *)&short_dest; + *dest = ret_val; + } + break; + case 64: + *dest = *(F64 *)&bytes_dest; + break; + default: + return NULL; + } + return str; + } + return NULL; +} + +U32 LLXMLNode::getBoolValue(U32 expected_length, BOOL *array) +{ + llassert(array); + + // Check type - accept booleans or strings + if (mType != TYPE_BOOLEAN && mType != TYPE_STRING && mType != TYPE_UNKNOWN) + { + return 0; + } + + LLString *str_array = new LLString[expected_length]; + + U32 length = getStringValue(expected_length, str_array); + + U32 ret_length = 0; + for (U32 i=0; imString << "' -- expected " << expected_length << " but " + << "only found " << ret_length << llendl; + } +#endif + return ret_length; +} + +U32 LLXMLNode::getByteValue(U32 expected_length, U8 *array, Encoding encoding) +{ + llassert(array); + + // Check type - accept bytes or integers (below 256 only) + if (mType != TYPE_INTEGER + && mType != TYPE_UNKNOWN) + { + return 0; + } + + if (mLength > 0 && mLength != expected_length) + { + llwarns << "XMLNode::getByteValue asked for " << expected_length + << " elements, while node has " << mLength << llendl; + return 0; + } + + if (encoding == ENCODING_DEFAULT) + { + encoding = mEncoding; + } + + const char *value_string = mValue.c_str(); + + U32 i; + for (i=0; i 255 || is_negative) + { + llwarns << "getByteValue: Value outside of valid range." << llendl; + break; + } + array[i] = U8(value); + } +#if LL_DEBUG + if (i != expected_length) + { + lldebugs << "LLXMLNode::getByteValue() failed for node named '" + << mName->mString << "' -- expected " << expected_length << " but " + << "only found " << i << llendl; + } +#endif + return i; +} + +U32 LLXMLNode::getIntValue(U32 expected_length, S32 *array, Encoding encoding) +{ + llassert(array); + + // Check type - accept bytes or integers + if (mType != TYPE_INTEGER && mType != TYPE_UNKNOWN) + { + return 0; + } + + if (mLength > 0 && mLength != expected_length) + { + llwarns << "XMLNode::getIntValue asked for " << expected_length + << " elements, while node has " << mLength << llendl; + return 0; + } + + if (encoding == ENCODING_DEFAULT) + { + encoding = mEncoding; + } + + const char *value_string = mValue.c_str(); + + U32 i = 0; + for (i=0; i 0x7fffffff) + { + llwarns << "getIntValue: Value outside of valid range." << llendl; + break; + } + array[i] = S32(value) * (is_negative?-1:1); + } + +#if LL_DEBUG + if (i != expected_length) + { + lldebugs << "LLXMLNode::getIntValue() failed for node named '" + << mName->mString << "' -- expected " << expected_length << " but " + << "only found " << i << llendl; + } +#endif + return i; +} + +U32 LLXMLNode::getUnsignedValue(U32 expected_length, U32 *array, Encoding encoding) +{ + llassert(array); + + // Check type - accept bytes or integers + if (mType != TYPE_INTEGER && mType != TYPE_UNKNOWN) + { + return 0; + } + + if (mLength > 0 && mLength != expected_length) + { + llwarns << "XMLNode::getUnsignedValue asked for " << expected_length + << " elements, while node has " << mLength << llendl; + return 0; + } + + if (encoding == ENCODING_DEFAULT) + { + encoding = mEncoding; + } + + const char *value_string = mValue.c_str(); + + U32 i = 0; + // Int type + for (i=0; i 0xffffffff) + { + llwarns << "getUnsignedValue: Value outside of valid range." << llendl; + break; + } + array[i] = U32(value); + } + +#if LL_DEBUG + if (i != expected_length) + { + lldebugs << "LLXMLNode::getUnsignedValue() failed for node named '" + << mName->mString << "' -- expected " << expected_length << " but " + << "only found " << i << llendl; + } +#endif + + return i; +} + +U32 LLXMLNode::getLongValue(U32 expected_length, U64 *array, Encoding encoding) +{ + llassert(array); + + // Check type - accept bytes or integers + if (mType != TYPE_INTEGER && mType != TYPE_UNKNOWN) + { + return 0; + } + + if (mLength > 0 && mLength != expected_length) + { + llwarns << "XMLNode::getLongValue asked for " << expected_length << " elements, while node has " << mLength << llendl; + return 0; + } + + if (encoding == ENCODING_DEFAULT) + { + encoding = mEncoding; + } + + const char *value_string = mValue.c_str(); + + U32 i = 0; + // Int type + for (i=0; imString << "' -- expected " << expected_length << " but " + << "only found " << i << llendl; + } +#endif + + return i; +} + +U32 LLXMLNode::getFloatValue(U32 expected_length, F32 *array, Encoding encoding) +{ + llassert(array); + + // Check type - accept only floats or doubles + if (mType != TYPE_FLOAT && mType != TYPE_UNKNOWN) + { + return 0; + } + + if (mLength > 0 && mLength != expected_length) + { + llwarns << "XMLNode::getFloatValue asked for " << expected_length << " elements, while node has " << mLength << llendl; + return 0; + } + + if (encoding == ENCODING_DEFAULT) + { + encoding = mEncoding; + } + + const char *value_string = mValue.c_str(); + + U32 i; + for (i=0; imString << "' -- expected " << expected_length << " but " + << "only found " << i << llendl; + } +#endif + return i; +} + +U32 LLXMLNode::getDoubleValue(U32 expected_length, F64 *array, Encoding encoding) +{ + llassert(array); + + // Check type - accept only floats or doubles + if (mType != TYPE_FLOAT && mType != TYPE_UNKNOWN) + { + return 0; + } + + if (mLength > 0 && mLength != expected_length) + { + llwarns << "XMLNode::getDoubleValue asked for " << expected_length << " elements, while node has " << mLength << llendl; + return 0; + } + + if (encoding == ENCODING_DEFAULT) + { + encoding = mEncoding; + } + + const char *value_string = mValue.c_str(); + + U32 i; + for (i=0; imString << "' -- expected " << expected_length << " but " + << "only found " << i << llendl; + } +#endif + return i; +} + +U32 LLXMLNode::getStringValue(U32 expected_length, LLString *array) +{ + llassert(array); + + // Can always return any value as a string + + if (mLength > 0 && mLength != expected_length) + { + llwarns << "XMLNode::getStringValue asked for " << expected_length << " elements, while node has " << mLength << llendl; + return 0; + } + + U32 num_returned_strings = 0; + + // Array of strings is whitespace-separated + const std::string sep(" \n\t"); + + std::string::size_type n = 0; + std::string::size_type m = 0; + while(1) + { + if (num_returned_strings >= expected_length) + { + break; + } + n = mValue.find_first_not_of(sep, m); + m = mValue.find_first_of(sep, n); + if (m == std::string::npos) + { + break; + } + array[num_returned_strings++] = mValue.substr(n,m-n); + } + if (n != std::string::npos && num_returned_strings < expected_length) + { + array[num_returned_strings++] = mValue.substr(n); + } +#if LL_DEBUG + if (num_returned_strings != expected_length) + { + lldebugs << "LLXMLNode::getStringValue() failed for node named '" + << mName->mString << "' -- expected " << expected_length << " but " + << "only found " << num_returned_strings << llendl; + } +#endif + + return num_returned_strings; +} + +U32 LLXMLNode::getUUIDValue(U32 expected_length, LLUUID *array) +{ + llassert(array); + + // Check type + if (mType != TYPE_UUID && mType != TYPE_UNKNOWN) + { + return 0; + } + + const char *value_string = mValue.c_str(); + + U32 i; + for (i=0; imString << "' -- expected " << expected_length << " but " + << "only found " << i << llendl; + } +#endif + return i; +} + +U32 LLXMLNode::getNodeRefValue(U32 expected_length, LLXMLNode **array) +{ + llassert(array); + + // Check type + if (mType != TYPE_NODEREF && mType != TYPE_UNKNOWN) + { + return 0; + } + + LLString *string_array = new LLString[expected_length]; + + U32 num_strings = getStringValue(expected_length, string_array); + + U32 num_returned_refs = 0; + + LLXMLNodePtr root = getRoot(); + for (U32 strnum=0; strnumfindID(string_array[strnum], node_list); + if (node_list.empty()) + { + llwarns << "XML: Could not find node ID: " << string_array[strnum] << llendl; + } + else if (node_list.size() > 1) + { + llwarns << "XML: Node ID not unique: " << string_array[strnum] << llendl; + } + else + { + LLXMLNodeList::const_iterator list_itr = node_list.begin(); + if (list_itr != node_list.end()) + { + LLXMLNode* child = (*list_itr).second; + + array[num_returned_refs++] = child; + } + } + } + + delete[] string_array; + + return num_returned_refs; +} + +void LLXMLNode::setBoolValue(U32 length, const BOOL *array) +{ + if (length == 0) return; + + LLString new_value; + for (U32 pos=0; pos 0) + { + new_value = llformat("%s %s", new_value.c_str(), array[pos]?"true":"false"); + } + else + { + new_value = array[pos]?"true":"false"; + } + } + + mValue = new_value; + mEncoding = ENCODING_DEFAULT; + mLength = length; + mType = TYPE_BOOLEAN; +} + +void LLXMLNode::setByteValue(U32 length, const U8* const array, Encoding encoding) +{ + if (length == 0) return; + + LLString new_value; + if (encoding == ENCODING_DEFAULT || encoding == ENCODING_DECIMAL) + { + for (U32 pos=0; pos 0) + { + new_value.append(llformat(" %u", array[pos])); + } + else + { + new_value = llformat("%u", array[pos]); + } + } + } + if (encoding == ENCODING_HEX) + { + for (U32 pos=0; pos 0 && pos % 16 == 0) + { + new_value.append(llformat(" %02X", array[pos])); + } + else + { + new_value.append(llformat("%02X", array[pos])); + } + } + } + // TODO -- Handle Base32 + + mValue = new_value; + mEncoding = encoding; + mLength = length; + mType = TYPE_INTEGER; + mPrecision = 8; +} + + +void LLXMLNode::setIntValue(U32 length, const S32 *array, Encoding encoding) +{ + if (length == 0) return; + + LLString new_value; + if (encoding == ENCODING_DEFAULT || encoding == ENCODING_DECIMAL) + { + for (U32 pos=0; pos 0) + { + new_value.append(llformat(" %d", array[pos])); + } + else + { + new_value = llformat("%d", array[pos]); + } + } + mValue = new_value; + } + else if (encoding == ENCODING_HEX) + { + for (U32 pos=0; pos 0 && pos % 16 == 0) + { + new_value.append(llformat(" %08X", ((U32 *)array)[pos])); + } + else + { + new_value.append(llformat("%08X", ((U32 *)array)[pos])); + } + } + mValue = new_value; + } + else + { + mValue = new_value; + } + // TODO -- Handle Base32 + + mEncoding = encoding; + mLength = length; + mType = TYPE_INTEGER; + mPrecision = 32; +} + +void LLXMLNode::setUnsignedValue(U32 length, const U32* array, Encoding encoding) +{ + if (length == 0) return; + + LLString new_value; + if (encoding == ENCODING_DEFAULT || encoding == ENCODING_DECIMAL) + { + for (U32 pos=0; pos 0) + { + new_value.append(llformat(" %u", array[pos])); + } + else + { + new_value = llformat("%u", array[pos]); + } + } + } + if (encoding == ENCODING_HEX) + { + for (U32 pos=0; pos 0 && pos % 16 == 0) + { + new_value.append(llformat(" %08X", array[pos])); + } + else + { + new_value.append(llformat("%08X", array[pos])); + } + } + mValue = new_value; + } + // TODO -- Handle Base32 + + mValue = new_value; + mEncoding = encoding; + mLength = length; + mType = TYPE_INTEGER; + mPrecision = 32; +} + +#if LL_WINDOWS +#define PU64 "I64u" +#else +#define PU64 "llu" +#endif + +void LLXMLNode::setLongValue(U32 length, const U64* array, Encoding encoding) +{ + if (length == 0) return; + + LLString new_value; + if (encoding == ENCODING_DEFAULT || encoding == ENCODING_DECIMAL) + { + for (U32 pos=0; pos 0) + { + new_value.append(llformat(" %" PU64, array[pos])); + } + else + { + new_value = llformat("%" PU64, array[pos]); + } + } + mValue = new_value; + } + if (encoding == ENCODING_HEX) + { + for (U32 pos=0; pos>32); + U32 lower_32 = U32(array[pos]&0xffffffff); + if (pos > 0 && pos % 8 == 0) + { + new_value.append(llformat(" %08X%08X", upper_32, lower_32)); + } + else + { + new_value.append(llformat("%08X%08X", upper_32, lower_32)); + } + } + mValue = new_value; + } + else + { + mValue = new_value; + } + // TODO -- Handle Base32 + + mEncoding = encoding; + mLength = length; + mType = TYPE_INTEGER; + mPrecision = 64; +} + +void LLXMLNode::setFloatValue(U32 length, const F32 *array, Encoding encoding, U32 precision) +{ + if (length == 0) return; + + LLString new_value; + if (encoding == ENCODING_DEFAULT || encoding == ENCODING_DECIMAL) + { + char format_string[10]; + if (precision > 0) + { + if (precision > 25) + { + precision = 25; + } + sprintf(format_string, "%%.%dg", precision); + } + else + { + sprintf(format_string, "%%g"); + } + + for (U32 pos=0; pos 0) + { + new_value.append(" "); + new_value.append(llformat(format_string, array[pos])); + } + else + { + new_value.assign(llformat(format_string, array[pos])); + } + } + mValue = new_value; + } + else if (encoding == ENCODING_HEX) + { + U32 *byte_array = (U32 *)array; + setUnsignedValue(length, byte_array, ENCODING_HEX); + } + else + { + mValue = new_value; + } + + mEncoding = encoding; + mLength = length; + mType = TYPE_FLOAT; + mPrecision = 32; +} + +void LLXMLNode::setDoubleValue(U32 length, const F64 *array, Encoding encoding, U32 precision) +{ + if (length == 0) return; + + LLString new_value; + if (encoding == ENCODING_DEFAULT || encoding == ENCODING_DECIMAL) + { + char format_string[10]; + if (precision > 0) + { + if (precision > 25) + { + precision = 25; + } + sprintf(format_string, "%%.%dg", precision); + } + else + { + sprintf(format_string, "%%g"); + } + for (U32 pos=0; pos 0) + { + new_value.append(" "); + new_value.append(llformat(format_string, array[pos])); + } + else + { + new_value.assign(llformat(format_string, array[pos])); + } + } + mValue = new_value; + } + if (encoding == ENCODING_HEX) + { + U64 *byte_array = (U64 *)array; + setLongValue(length, byte_array, ENCODING_HEX); + } + else + { + mValue = new_value; + } + // TODO -- Handle Base32 + + mEncoding = encoding; + mLength = length; + mType = TYPE_FLOAT; + mPrecision = 64; +} + +// static +LLString LLXMLNode::escapeXML(const LLString& xml) +{ + LLString out; + for (LLString::size_type i = 0; i < xml.size(); ++i) + { + char c = xml[i]; + switch(c) + { + case '"': out.append("""); break; + case '\'': out.append("'"); break; + case '&': out.append("&"); break; + case '<': out.append("<"); break; + case '>': out.append(">"); break; + default: out.push_back(c); break; + } + } + return out; +} + +void LLXMLNode::setStringValue(U32 length, const LLString *array) +{ + if (length == 0) return; + + LLString new_value; + for (U32 pos=0; posmID != "") + { + new_value.append(array[pos]->mID); + } + else + { + new_value.append("(null)"); + } + if (pos < length-1) new_value.append(" "); + } + + mValue = new_value; + mEncoding = ENCODING_DEFAULT; + mLength = length; + mType = TYPE_NODEREF; +} + +void LLXMLNode::setValue(const LLString& value) +{ + if (TYPE_CONTAINER == mType) + { + mType = TYPE_UNKNOWN; + } + mValue = value; +} + +void LLXMLNode::setDefault(LLXMLNode *default_node) +{ + mDefault = default_node; +} + +void LLXMLNode::findDefault(LLXMLNode *defaults_list) +{ + if (defaults_list) + { + LLXMLNodeList children; + defaults_list->getChildren(mName->mString, children); + + LLXMLNodeList::const_iterator children_itr; + LLXMLNodeList::const_iterator children_end = children.end(); + for (children_itr = children.begin(); children_itr != children_end; ++children_itr) + { + LLXMLNode* child = (*children_itr).second; + if (child->mVersionMajor == mVersionMajor && + child->mVersionMinor == mVersionMinor) + { + mDefault = child; + return; + } + } + } + mDefault = NULL; +} + +BOOL LLXMLNode::deleteChildren(const LLString& name) +{ + U32 removed_count = 0; + LLXMLNodeList node_list; + findName(name, node_list); + if (!node_list.empty()) + { + // TODO -- use multimap::find() + // TODO -- need to watch out for invalid iterators + LLXMLNodeList::iterator children_itr; + for (children_itr = node_list.begin(); children_itr != node_list.end(); ++children_itr) + { + LLXMLNode* child = (*children_itr).second; + if (deleteChild(child)) + { + removed_count++; + } + } + } + return removed_count > 0 ? TRUE : FALSE; +} + +BOOL LLXMLNode::deleteChildren(LLStringTableEntry* name) +{ + U32 removed_count = 0; + LLXMLNodeList node_list; + findName(name, node_list); + if (!node_list.empty()) + { + // TODO -- use multimap::find() + // TODO -- need to watch out for invalid iterators + LLXMLNodeList::iterator children_itr; + for (children_itr = node_list.begin(); children_itr != node_list.end(); ++children_itr) + { + LLXMLNode* child = (*children_itr).second; + if (deleteChild(child)) + { + removed_count++; + } + } + } + return removed_count > 0 ? TRUE : FALSE; +} + +void LLXMLNode::setAttributes(LLXMLNode::ValueType type, U32 precision, LLXMLNode::Encoding encoding, U32 length) +{ + mType = type; + mEncoding = encoding; + mPrecision = precision; + mLength = length; +} + +void LLXMLNode::setName(const LLString& name) +{ + setName(gStringTable.addStringEntry(name)); +} + +void LLXMLNode::setName(LLStringTableEntry* name) +{ + LLXMLNode* old_parent = mParent; + if (mParent) + { + // we need to remove and re-add to the parent so that + // the multimap key agrees with this node's name + mParent->removeChild(this); + } + mName = name; + if (old_parent) + { + old_parent->addChild(this); + } +} + +void LLXMLNode::appendValue(const LLString& value) +{ + mValue.append(value); +} + +U32 LLXMLNode::getChildCount() const +{ + if (mChildren) + { + return mChildren->map.size(); + } + return 0; +} + +//*************************************************** +// UNIT TESTING +//*************************************************** + +U32 get_rand(U32 max_value) +{ + U32 random_num = rand() + ((U32)rand() << 16); + return (random_num % max_value); +} + +LLXMLNode *get_rand_node(LLXMLNode *node) +{ + if (node->mChildren) + { + U32 num_children = node->mChildren->map.size(); + if (get_rand(2) == 0) + { + while (true) + { + S32 child_num = S32(get_rand(num_children*2)) - num_children; + LLXMLChildList::iterator itor = node->mChildren->map.begin(); + while (child_num > 0) + { + --child_num; + ++itor; + } + if (!itor->second->mIsAttribute) + { + return get_rand_node(itor->second); + } + } + } + } + return node; +} + +void LLXMLNode::createUnitTest(S32 max_num_children) +{ + // Random ID + char rand_id[20]; + U32 rand_id_len = get_rand(10)+5; + U32 pos = 0; + for (; posmID = child_id; + + // Random Length + U32 array_size = get_rand(28)+1; + + // Random Encoding + Encoding new_encoding = get_rand(2)?ENCODING_DECIMAL:ENCODING_HEX; + + // Random Type + int type = get_rand(8); + switch (type) + { + case 0: // TYPE_CONTAINER + new_child->createUnitTest(max_num_children/2); + break; + case 1: // TYPE_BOOLEAN + { + BOOL random_bool_values[30]; + for (U32 value=0; valuesetBoolValue(array_size, random_bool_values); + } + break; + case 2: // TYPE_INTEGER (32-bit) + { + U32 random_int_values[30]; + for (U32 value=0; valuesetUnsignedValue(array_size, random_int_values, new_encoding); + } + break; + case 3: // TYPE_INTEGER (64-bit) + { + U64 random_int_values[30]; + for (U64 value=0; valuesetLongValue(array_size, random_int_values, new_encoding); + } + break; + case 4: // TYPE_FLOAT (32-bit) + { + F32 random_float_values[30]; + for (U32 value=0; valuesetFloatValue(array_size, random_float_values, new_encoding, 12); + } + break; + case 5: // TYPE_FLOAT (64-bit) + { + F64 random_float_values[30]; + for (U32 value=0; value> 32); + } + new_child->setDoubleValue(array_size, random_float_values, new_encoding, 12); + } + break; + case 6: // TYPE_UUID + { + LLUUID random_uuid_values[30]; + for (U32 value=0; valuesetUUIDValue(array_size, random_uuid_values); + } + break; + case 7: // TYPE_NODEREF + { + LLXMLNode *random_node_array[30]; + LLXMLNode *root = getRoot(); + for (U32 value=0; valuemName->mString; + for (U32 pos=0; possetNodeRefValue(array_size, (const LLXMLNode **)random_node_array); + } + break; + } + } + + createChild("integer_checksum", TRUE)->setUnsignedValue(1, &integer_checksum, LLXMLNode::ENCODING_HEX); + createChild("long_checksum", TRUE)->setLongValue(1, &long_checksum, LLXMLNode::ENCODING_HEX); + createChild("bool_true_count", TRUE)->setUnsignedValue(1, &bool_true_count, LLXMLNode::ENCODING_HEX); + createChild("uuid_checksum", TRUE)->setUUIDValue(1, &uuid_checksum); + createChild("noderef_checksum", TRUE)->setUnsignedValue(1, &noderef_checksum, LLXMLNode::ENCODING_HEX); + createChild("float_checksum", TRUE)->setUnsignedValue(1, &float_checksum, LLXMLNode::ENCODING_HEX); +} + +BOOL LLXMLNode::performUnitTest(LLString &error_buffer) +{ + if (!mChildren) + { + error_buffer.append(llformat("ERROR Node %s: No children found.\n", mName->mString)); + return FALSE; + } + + // Checksums + U32 integer_checksum = 0; + U32 bool_true_count = 0; + LLUUID uuid_checksum; + U32 noderef_checksum = 0; + U32 float_checksum = 0; + U64 long_checksum = 0; + + LLXMLChildList::iterator itor; + for (itor=mChildren->map.begin(); itor!=mChildren->map.end(); ++itor) + { + LLXMLNode *node = itor->second; + if (node->mIsAttribute) + { + continue; + } + if (node->mType == TYPE_CONTAINER) + { + if (!node->performUnitTest(error_buffer)) + { + error_buffer.append(llformat("Child test failed for %s.\n", mName->mString)); + //return FALSE; + } + continue; + } + if (node->mLength < 1 || node->mLength > 30) + { + error_buffer.append(llformat("ERROR Node %s: Invalid array length %d, child %s.\n", mName->mString, node->mLength, node->mName->mString)); + return FALSE; + } + switch (node->mType) + { + case TYPE_CONTAINER: + case TYPE_UNKNOWN: + break; + case TYPE_BOOLEAN: + { + BOOL bool_array[30]; + if (node->getBoolValue(node->mLength, bool_array) < node->mLength) + { + error_buffer.append(llformat("ERROR Node %s: Could not read boolean array, child %s.\n", mName->mString, node->mName->mString)); + return FALSE; + } + for (U32 pos=0; pos<(U32)node->mLength; ++pos) + { + if (bool_array[pos]) + { + ++bool_true_count; + } + } + } + break; + case TYPE_INTEGER: + { + if (node->mPrecision == 32) + { + U32 integer_array[30]; + if (node->getUnsignedValue(node->mLength, integer_array, node->mEncoding) < node->mLength) + { + error_buffer.append(llformat("ERROR Node %s: Could not read integer array, child %s.\n", mName->mString, node->mName->mString)); + return FALSE; + } + for (U32 pos=0; pos<(U32)node->mLength; ++pos) + { + integer_checksum ^= integer_array[pos]; + } + } + else + { + U64 integer_array[30]; + if (node->getLongValue(node->mLength, integer_array, node->mEncoding) < node->mLength) + { + error_buffer.append(llformat("ERROR Node %s: Could not read long integer array, child %s.\n", mName->mString, node->mName->mString)); + return FALSE; + } + for (U32 pos=0; pos<(U32)node->mLength; ++pos) + { + long_checksum ^= integer_array[pos]; + } + } + } + break; + case TYPE_FLOAT: + { + if (node->mPrecision == 32) + { + F32 float_array[30]; + if (node->getFloatValue(node->mLength, float_array, node->mEncoding) < node->mLength) + { + error_buffer.append(llformat("ERROR Node %s: Could not read float array, child %s.\n", mName->mString, node->mName->mString)); + return FALSE; + } + for (U32 pos=0; pos<(U32)node->mLength; ++pos) + { + U32 float_bits = ((U32 *)float_array)[pos]; + float_checksum ^= (float_bits & 0xfffff000); + } + } + else + { + F64 float_array[30]; + if (node->getDoubleValue(node->mLength, float_array, node->mEncoding) < node->mLength) + { + error_buffer.append(llformat("ERROR Node %s: Could not read float array, child %s.\n", mName->mString, node->mName->mString)); + return FALSE; + } + for (U32 pos=0; pos<(U32)node->mLength; ++pos) + { + U64 float_bits = ((U64 *)float_array)[pos]; + float_checksum ^= ((float_bits & 0xfffffff000000000ll) >> 32); + } + } + } + break; + case TYPE_STRING: + break; + case TYPE_UUID: + { + LLUUID uuid_array[30]; + if (node->getUUIDValue(node->mLength, uuid_array) < node->mLength) + { + error_buffer.append(llformat("ERROR Node %s: Could not read uuid array, child %s.\n", mName->mString, node->mName->mString)); + return FALSE; + } + for (U32 pos=0; pos<(U32)node->mLength; ++pos) + { + for (S32 byte=0; bytegetNodeRefValue(node->mLength, node_array) < node->mLength) + { + error_buffer.append(llformat("ERROR Node %s: Could not read node ref array, child %s.\n", mName->mString, node->mName->mString)); + return FALSE; + } + for (U32 pos=0; posmLength; ++pos) + { + const char *node_name = node_array[pos]->mName->mString; + for (U32 pos2=0; pos2getUnsignedValue(1, &node_integer_checksum, ENCODING_HEX) != 1) + { + error_buffer.append(llformat("ERROR Node %s: Integer checksum missing.\n", mName->mString)); + return FALSE; + } + if (node_integer_checksum != integer_checksum) + { + error_buffer.append(llformat("ERROR Node %s: Integer checksum mismatch: read %X / calc %X.\n", mName->mString, node_integer_checksum, integer_checksum)); + return FALSE; + } + } + + { + U64 node_long_checksum = 0; + if (!getAttribute("long_checksum", checksum_node, FALSE) || + checksum_node->getLongValue(1, &node_long_checksum, ENCODING_HEX) != 1) + { + error_buffer.append(llformat("ERROR Node %s: Long Integer checksum missing.\n", mName->mString)); + return FALSE; + } + if (node_long_checksum != long_checksum) + { + U32 *pp1 = (U32 *)&node_long_checksum; + U32 *pp2 = (U32 *)&long_checksum; + error_buffer.append(llformat("ERROR Node %s: Long Integer checksum mismatch: read %08X%08X / calc %08X%08X.\n", mName->mString, pp1[1], pp1[0], pp2[1], pp2[0])); + return FALSE; + } + } + + { + U32 node_bool_true_count = 0; + if (!getAttribute("bool_true_count", checksum_node, FALSE) || + checksum_node->getUnsignedValue(1, &node_bool_true_count, ENCODING_HEX) != 1) + { + error_buffer.append(llformat("ERROR Node %s: Boolean checksum missing.\n", mName->mString)); + return FALSE; + } + if (node_bool_true_count != bool_true_count) + { + error_buffer.append(llformat("ERROR Node %s: Boolean checksum mismatch: read %X / calc %X.\n", mName->mString, node_bool_true_count, bool_true_count)); + return FALSE; + } + } + + { + LLUUID node_uuid_checksum; + if (!getAttribute("uuid_checksum", checksum_node, FALSE) || + checksum_node->getUUIDValue(1, &node_uuid_checksum) != 1) + { + error_buffer.append(llformat("ERROR Node %s: UUID checksum missing.\n", mName->mString)); + return FALSE; + } + if (node_uuid_checksum != uuid_checksum) + { + error_buffer.append(llformat("ERROR Node %s: UUID checksum mismatch: read %s / calc %s.\n", mName->mString, node_uuid_checksum.getString().c_str(), uuid_checksum.getString().c_str())); + return FALSE; + } + } + + { + U32 node_noderef_checksum = 0; + if (!getAttribute("noderef_checksum", checksum_node, FALSE) || + checksum_node->getUnsignedValue(1, &node_noderef_checksum, ENCODING_HEX) != 1) + { + error_buffer.append(llformat("ERROR Node %s: Node Ref checksum missing.\n", mName->mString)); + return FALSE; + } + if (node_noderef_checksum != noderef_checksum) + { + error_buffer.append(llformat("ERROR Node %s: Node Ref checksum mismatch: read %X / calc %X.\n", mName->mString, node_noderef_checksum, noderef_checksum)); + return FALSE; + } + } + + { + U32 node_float_checksum = 0; + if (!getAttribute("float_checksum", checksum_node, FALSE) || + checksum_node->getUnsignedValue(1, &node_float_checksum, ENCODING_HEX) != 1) + { + error_buffer.append(llformat("ERROR Node %s: Float checksum missing.\n", mName->mString)); + return FALSE; + } + if (node_float_checksum != float_checksum) + { + error_buffer.append(llformat("ERROR Node %s: Float checksum mismatch: read %X / calc %X.\n", mName->mString, node_float_checksum, float_checksum)); + return FALSE; + } + } + + return TRUE; +} + +LLXMLNodePtr LLXMLNode::getFirstChild() +{ + if (!mChildren) return NULL; + LLXMLNodePtr ret = mChildren->head; + return ret; +} + +LLXMLNodePtr LLXMLNode::getNextSibling() +{ + LLXMLNodePtr ret = mNext; + return ret; +} + +LLString LLXMLNode::getTextContents() const +{ + std::string msg; + LLXMLNodeList p_children; + getChildren("p", p_children); + if (p_children.size() > 0) + { + // Case 1: node has

text

tags + LLXMLNodeList::iterator itor; + for (itor = p_children.begin(); itor != p_children.end(); ++itor) + { + LLXMLNodePtr p = itor->second; + msg += p->getValue() + "\n"; + } + } + else + { + LLString contents = mValue; + std::string::size_type n = contents.find_first_not_of(" \t\n"); + if (n != std::string::npos && contents[n] == '\"') + { + // Case 2: node has quoted text + S32 num_lines = 0; + while(1) + { + // mContents[n] == '"' + ++n; + std::string::size_type t = n; + std::string::size_type m = 0; + // fix-up escaped characters + while(1) + { + m = contents.find_first_of("\\\"", t); // find first \ or " + if ((m == std::string::npos) || (contents[m] == '\"')) + { + break; + } + contents.erase(m,1); + t = m+1; + } + if (m == std::string::npos) + { + break; + } + // mContents[m] == '"' + num_lines++; + msg += contents.substr(n,m-n) + "\n"; + n = contents.find_first_of("\"", m+1); + if (n == std::string::npos) + { + if (num_lines == 1) + { + msg.erase(msg.size()-1); // remove "\n" if only one line + } + break; + } + } + } + else + { + // Case 3: node has embedded text (beginning and trailing whitespace trimmed) + LLString::size_type start = mValue.find_first_not_of(" \t\n"); + if (start != mValue.npos) + { + LLString::size_type end = mValue.find_last_not_of(" \t\n"); + if (end != mValue.npos) + { + msg = mValue.substr(start, end+1-start); + } + else + { + msg = mValue.substr(start); + } + } + } + } + return msg; +} diff --git a/indra/llxml/llxmlnode.h b/indra/llxml/llxmlnode.h new file mode 100644 index 0000000000..ca8c1d176f --- /dev/null +++ b/indra/llxml/llxmlnode.h @@ -0,0 +1,266 @@ +/** + * @file llxmlnode.h + * @brief LLXMLNode definition + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLXMLNODE_H +#define LL_LLXMLNODE_H + +#define XML_STATIC +#include "expat/expat.h" +#include + +#include "indra_constants.h" +#include "llmemory.h" +#include "llstring.h" +#include "llstringtable.h" + + + +struct CompareAttributes +{ + bool operator()(const LLStringTableEntry* const lhs, const LLStringTableEntry* const rhs) const + { + if (lhs == NULL) + return TRUE; + if (rhs == NULL) + return FALSE; + + return strcmp(lhs->mString, rhs->mString) < 0; + } +}; + + +// Defines a simple node hierarchy for reading and writing task objects + +class LLXMLNode; +typedef LLPointer LLXMLNodePtr; +typedef std::multimap LLXMLNodeList; +typedef std::multimap LLXMLChildList; +typedef std::map LLXMLAttribList; + +struct LLXMLChildren +{ + LLXMLChildList map; // Map of children names->pointers + LLXMLNodePtr head; // Head of the double-linked list + LLXMLNodePtr tail; // Tail of the double-linked list +}; + +class LLXMLNode : public LLThreadSafeRefCount +{ +public: + enum ValueType + { + TYPE_CONTAINER, // A node which contains nodes + TYPE_UNKNOWN, // A node loaded from file without a specified type + TYPE_BOOLEAN, // "true" or "false" + TYPE_INTEGER, // any integer type: U8, U32, S32, U64, etc. + TYPE_FLOAT, // any floating point type: F32, F64 + TYPE_STRING, // a string + TYPE_UUID, // a UUID + TYPE_NODEREF, // the ID of another node in the hierarchy to reference + }; + + enum Encoding + { + ENCODING_DEFAULT = 0, + ENCODING_DECIMAL, + ENCODING_HEX, + // ENCODING_BASE32, // Not implemented yet + }; + +protected: + virtual ~LLXMLNode(); + +public: + LLXMLNode(); + LLXMLNode(const LLString& name, BOOL is_attribute); + LLXMLNode(LLStringTableEntry* name, BOOL is_attribute); + + BOOL isNull(); + + BOOL deleteChild(LLXMLNode* child); + void addChild(LLXMLNodePtr new_parent); + void setParent(LLXMLNodePtr new_parent); // reparent if necessary + + // Serialization + static bool parseFile( + LLString filename, + LLXMLNodePtr& node, + LLXMLNode* defaults_tree); + static bool parseBuffer( + U8* buffer, + U32 length, + LLXMLNodePtr& node, + LLXMLNode* defaults); + static bool updateNode( + LLXMLNodePtr& node, + LLXMLNodePtr& update_node); + static void writeHeaderToFile(FILE *fOut); + void writeToFile(FILE *fOut, LLString indent = ""); + void writeToOstream(std::ostream& output_stream, const LLString& indent = ""); + + // Utility + void findName(const LLString& name, LLXMLNodeList &results); + void findName(LLStringTableEntry* name, LLXMLNodeList &results); + void findID(const LLString& id, LLXMLNodeList &results); + + + virtual LLXMLNodePtr createChild(const LLString& name, BOOL is_attribute); + virtual LLXMLNodePtr createChild(LLStringTableEntry* name, BOOL is_attribute); + + + // Getters + U32 getBoolValue(U32 expected_length, BOOL *array); + U32 getByteValue(U32 expected_length, U8 *array, Encoding encoding = ENCODING_DEFAULT); + U32 getIntValue(U32 expected_length, S32 *array, Encoding encoding = ENCODING_DEFAULT); + U32 getUnsignedValue(U32 expected_length, U32 *array, Encoding encoding = ENCODING_DEFAULT); + U32 getLongValue(U32 expected_length, U64 *array, Encoding encoding = ENCODING_DEFAULT); + U32 getFloatValue(U32 expected_length, F32 *array, Encoding encoding = ENCODING_DEFAULT); + U32 getDoubleValue(U32 expected_length, F64 *array, Encoding encoding = ENCODING_DEFAULT); + U32 getStringValue(U32 expected_length, LLString *array); + U32 getUUIDValue(U32 expected_length, LLUUID *array); + U32 getNodeRefValue(U32 expected_length, LLXMLNode **array); + + BOOL hasAttribute(const LLString& name ); + + BOOL getAttributeBOOL(const LLString& name, BOOL& value ); + BOOL getAttributeU8(const LLString& name, U8& value ); + BOOL getAttributeS8(const LLString& name, S8& value ); + BOOL getAttributeU16(const LLString& name, U16& value ); + BOOL getAttributeS16(const LLString& name, S16& value ); + BOOL getAttributeU32(const LLString& name, U32& value ); + BOOL getAttributeS32(const LLString& name, S32& value ); + BOOL getAttributeF32(const LLString& name, F32& value ); + BOOL getAttributeF64(const LLString& name, F64& value ); + BOOL getAttributeColor(const LLString& name, LLColor4& value ); + BOOL getAttributeColor4(const LLString& name, LLColor4& value ); + BOOL getAttributeColor4U(const LLString& name, LLColor4U& value ); + BOOL getAttributeVector3(const LLString& name, LLVector3& value ); + BOOL getAttributeVector3d(const LLString& name, LLVector3d& value ); + BOOL getAttributeQuat(const LLString& name, LLQuaternion& value ); + BOOL getAttributeUUID(const LLString& name, LLUUID& value ); + BOOL getAttributeString(const LLString& name, LLString& value ); + + const ValueType& getType() const { return mType; } + const U32 getLength() const { return mLength; } + const U32 getPrecision() const { return mPrecision; } + const LLString& getValue() const { return mValue; } + LLString getTextContents() const; + const LLStringTableEntry* getName() const { return mName; } + const BOOL hasName(LLString name) const { return mName == gStringTable.checkStringEntry(name); } + const LLString& getID() const { return mID; } + + U32 getChildCount() const; + // getChild returns a Null LLXMLNode (not a NULL pointer) if there is no such child. + // This child has no value so any getTYPEValue() calls on it will return 0. + bool getChild(const LLString& name, LLXMLNodePtr& node, BOOL use_default_if_missing = TRUE); + bool getChild(const LLStringTableEntry* name, LLXMLNodePtr& node, BOOL use_default_if_missing = TRUE); + void getChildren(const LLString& name, LLXMLNodeList &children, BOOL use_default_if_missing = TRUE) const; + void getChildren(const LLStringTableEntry* name, LLXMLNodeList &children, BOOL use_default_if_missing = TRUE) const; + + bool getAttribute(const LLString& name, LLXMLNodePtr& node, BOOL use_default_if_missing = TRUE); + bool getAttribute(const LLStringTableEntry* name, LLXMLNodePtr& node, BOOL use_default_if_missing = TRUE); + + // The following skip over attributes + LLXMLNodePtr getFirstChild(); + LLXMLNodePtr getNextSibling(); + + LLXMLNodePtr getRoot(); + + // Setters + + bool setAttributeString(const LLString& attr, const LLString& value); + + void setBoolValue(const BOOL value) { setBoolValue(1, &value); } + void setByteValue(const U8 value, Encoding encoding = ENCODING_DEFAULT) { setByteValue(1, &value, encoding); } + void setIntValue(const S32 value, Encoding encoding = ENCODING_DEFAULT) { setIntValue(1, &value, encoding); } + void setUnsignedValue(const U32 value, Encoding encoding = ENCODING_DEFAULT) { setUnsignedValue(1, &value, encoding); } + void setLongValue(const U64 value, Encoding encoding = ENCODING_DEFAULT) { setLongValue(1, &value, encoding); } + void setFloatValue(const F32 value, Encoding encoding = ENCODING_DEFAULT, U32 precision = 0) { setFloatValue(1, &value, encoding); } + void setDoubleValue(const F64 value, Encoding encoding = ENCODING_DEFAULT, U32 precision = 0) { setDoubleValue(1, &value, encoding); } + void setStringValue(const LLString value) { setStringValue(1, &value); } + void setUUIDValue(const LLUUID value) { setUUIDValue(1, &value); } + void setNodeRefValue(const LLXMLNode *value) { setNodeRefValue(1, &value); } + + void setBoolValue(U32 length, const BOOL *array); + void setByteValue(U32 length, const U8 *array, Encoding encoding = ENCODING_DEFAULT); + void setIntValue(U32 length, const S32 *array, Encoding encoding = ENCODING_DEFAULT); + void setUnsignedValue(U32 length, const U32* array, Encoding encoding = ENCODING_DEFAULT); + void setLongValue(U32 length, const U64 *array, Encoding encoding = ENCODING_DEFAULT); + void setFloatValue(U32 length, const F32 *array, Encoding encoding = ENCODING_DEFAULT, U32 precision = 0); + void setDoubleValue(U32 length, const F64 *array, Encoding encoding = ENCODING_DEFAULT, U32 precision = 0); + void setStringValue(U32 length, const LLString *array); + void setUUIDValue(U32 length, const LLUUID *array); + void setNodeRefValue(U32 length, const LLXMLNode **array); + void setValue(const LLString& value); + void setName(const LLString& name); + void setName(LLStringTableEntry* name); + + // Escapes " (quot) ' (apos) & (amp) < (lt) > (gt) + // TomY TODO: Make this private + static LLString escapeXML(const LLString& xml); + + // Set the default node corresponding to this default node + void setDefault(LLXMLNode *default_node); + + // Find the node within defaults_list which corresponds to this node + void findDefault(LLXMLNode *defaults_list); + + void updateDefault(); + + // Delete any child nodes that aren't among the tree's children, recursive + void scrubToTree(LLXMLNode *tree); + + BOOL deleteChildren(const LLString& name); + BOOL deleteChildren(LLStringTableEntry* name); + void setAttributes(ValueType type, U32 precision, Encoding encoding, U32 length); + void appendValue(const LLString& value); + + // Unit Testing + void createUnitTest(S32 max_num_children); + BOOL performUnitTest(LLString &error_buffer); + +protected: + BOOL removeChild(LLXMLNode* child); + +public: + LLString mID; // The ID attribute of this node + + XML_Parser *mParser; // Temporary pointer while loading + + BOOL mIsAttribute; // Flag is only used for output formatting + U32 mVersionMajor; // Version of this tag to use + U32 mVersionMinor; + U32 mLength; // If the length is nonzero, then only return arrays of this length + U32 mPrecision; // The number of BITS per array item + ValueType mType; // The value type + Encoding mEncoding; // The value encoding + + LLXMLNode* mParent; // The parent node + LLXMLChildren* mChildren; // The child nodes + LLXMLAttribList mAttributes; // The attribute nodes + LLXMLNodePtr mPrev; // Double-linked list previous node + LLXMLNodePtr mNext; // Double-linked list next node + + static BOOL sStripEscapedStrings; + static BOOL sStripWhitespaceValues; + +protected: + LLStringTableEntry *mName; // The name of this node + LLString mValue; // The value of this node (use getters/setters only) + + LLXMLNodePtr mDefault; // Mirror node in the default tree + + static const char *skipWhitespace(const char *str); + static const char *skipNonWhitespace(const char *str); + static const char *parseInteger(const char *str, U64 *dest, BOOL *is_negative, U32 precision, Encoding encoding); + static const char *parseFloat(const char *str, F64 *dest, U32 precision, Encoding encoding); + + BOOL isFullyDefault(); +}; + +#endif // LL_LLXMLNODE diff --git a/indra/llxml/llxmlparser.cpp b/indra/llxml/llxmlparser.cpp new file mode 100644 index 0000000000..baaeedf586 --- /dev/null +++ b/indra/llxml/llxmlparser.cpp @@ -0,0 +1,398 @@ +/** + * @file llxmlparser.cpp + * @brief LLXmlParser implementation + * + * Copyright (c) 2002-$CurrentYear$`, Linden Research, Inc. + * $License$ + */ + +// llxmlparser.cpp +// +// copyright 2002, linden research inc + + +#include "linden_common.h" + +#include "llxmlparser.h" +#include "llerror.h" + + +LLXmlParser::LLXmlParser() + : + mParser( NULL ), + mDepth( 0 ) +{ + strcpy( mAuxErrorString, "no error" ); + + // Override the document's declared encoding. + mParser = XML_ParserCreate(NULL); + + XML_SetUserData(mParser, this); + XML_SetElementHandler( mParser, startElementHandler, endElementHandler); + XML_SetCharacterDataHandler( mParser, characterDataHandler); + XML_SetProcessingInstructionHandler( mParser, processingInstructionHandler); + XML_SetCommentHandler( mParser, commentHandler); + + XML_SetCdataSectionHandler( mParser, startCdataSectionHandler, endCdataSectionHandler); + + // This sets the default handler but does not inhibit expansion of internal entities. + // The entity reference will not be passed to the default handler. + XML_SetDefaultHandlerExpand( mParser, defaultDataHandler); + + XML_SetUnparsedEntityDeclHandler( mParser, unparsedEntityDeclHandler); +} + +LLXmlParser::~LLXmlParser() +{ + XML_ParserFree( mParser ); +} + + +BOOL LLXmlParser::parseFile(const std::string &path) +{ + llassert( !mDepth ); + + BOOL success = TRUE; + + FILE *file = LLFile::fopen(path.c_str(), "rb"); + if( !file ) + { + sprintf( mAuxErrorString, "Couldn't open file %s", path.c_str()); + success = FALSE; + } + else + { + S32 bytes_read = 0; + + fseek(file, 0L, SEEK_END); + S32 buffer_size = ftell(file); + fseek(file, 0L, SEEK_SET); + + void* buffer = XML_GetBuffer(mParser, buffer_size); + if( !buffer ) + { + sprintf( mAuxErrorString, "Unable to allocate XML buffer while reading file %s", path.c_str() ); + success = FALSE; + goto exit_label; + } + + bytes_read = (S32)fread(buffer, 1, buffer_size, file); + if( bytes_read <= 0 ) + { + sprintf( mAuxErrorString, "Error while reading file %s", path.c_str() ); + success = FALSE; + goto exit_label; + } + + if( !XML_ParseBuffer(mParser, bytes_read, TRUE ) ) + { + sprintf( mAuxErrorString, "Error while parsing file %s", path.c_str() ); + success = FALSE; + } + +exit_label: + fclose( file ); + } + + + if( success ) + { + llassert( !mDepth ); + } + mDepth = 0; + + if( !success ) + { + llwarns << mAuxErrorString << llendl; + } + + return success; +} + + +// Parses some input. Returns 0 if a fatal error is detected. +// The last call must have isFinal true; +// len may be zero for this call (or any other). +S32 LLXmlParser::parse( const char* buf, int len, int isFinal ) +{ + return XML_Parse(mParser, buf, len, isFinal); +} + +const char* LLXmlParser::getErrorString() +{ + const char* error_string = XML_ErrorString(XML_GetErrorCode( mParser )); + if( !error_string ) + { + error_string = mAuxErrorString; + } + return error_string; +} + +S32 LLXmlParser::getCurrentLineNumber() +{ + return XML_GetCurrentLineNumber( mParser ); +} + +S32 LLXmlParser::getCurrentColumnNumber() +{ + return XML_GetCurrentColumnNumber(mParser); +} + +/////////////////////////////////////////////////////////////////////////////// +// Pseudo-private methods. These are only used by internal callbacks. + +// static +void LLXmlParser::startElementHandler( + void *userData, + const XML_Char *name, + const XML_Char **atts) +{ + LLXmlParser* self = (LLXmlParser*) userData; + self->startElement( name, atts ); + self->mDepth++; +} + +// static +void LLXmlParser::endElementHandler( + void *userData, + const XML_Char *name) +{ + LLXmlParser* self = (LLXmlParser*) userData; + self->mDepth--; + self->endElement( name ); +} + +// s is not 0 terminated. +// static +void LLXmlParser::characterDataHandler( + void *userData, + const XML_Char *s, + int len) +{ + LLXmlParser* self = (LLXmlParser*) userData; + self->characterData( s, len ); +} + +// target and data are 0 terminated +// static +void LLXmlParser::processingInstructionHandler( + void *userData, + const XML_Char *target, + const XML_Char *data) +{ + LLXmlParser* self = (LLXmlParser*) userData; + self->processingInstruction( target, data ); +} + +// data is 0 terminated +// static +void LLXmlParser::commentHandler(void *userData, const XML_Char *data) +{ + LLXmlParser* self = (LLXmlParser*) userData; + self->comment( data ); +} + +// static +void LLXmlParser::startCdataSectionHandler(void *userData) +{ + LLXmlParser* self = (LLXmlParser*) userData; + self->mDepth++; + self->startCdataSection(); +} + +// static +void LLXmlParser::endCdataSectionHandler(void *userData) +{ + LLXmlParser* self = (LLXmlParser*) userData; + self->endCdataSection(); + self->mDepth++; +} + +// This is called for any characters in the XML document for +// which there is no applicable handler. This includes both +// characters that are part of markup which is of a kind that is +// not reported (comments, markup declarations), or characters +// that are part of a construct which could be reported but +// for which no handler has been supplied. The characters are passed +// exactly as they were in the XML document except that +// they will be encoded in UTF-8. Line boundaries are not normalized. +// Note that a byte order mark character is not passed to the default handler. +// There are no guarantees about how characters are divided between calls +// to the default handler: for example, a comment might be split between +// multiple calls. + +// static +void LLXmlParser::defaultDataHandler( + void *userData, + const XML_Char *s, + int len) +{ + LLXmlParser* self = (LLXmlParser*) userData; + self->defaultData( s, len ); +} + +// This is called for a declaration of an unparsed (NDATA) +// entity. The base argument is whatever was set by XML_SetBase. +// The entityName, systemId and notationName arguments will never be null. +// The other arguments may be. +// static +void LLXmlParser::unparsedEntityDeclHandler( + void *userData, + const XML_Char *entityName, + const XML_Char *base, + const XML_Char *systemId, + const XML_Char *publicId, + const XML_Char *notationName) +{ + LLXmlParser* self = (LLXmlParser*) userData; + self->unparsedEntityDecl( entityName, base, systemId, publicId, notationName ); +} + + + + +//////////////////////////////////////////////////////////////////// +// Test code. + +/* +class LLXmlDOMParser : public LLXmlParser +{ +public: + + LLXmlDOMParser() {} + virtual ~LLXmlDOMParser() {} + + void tabs() + { + for ( int i = 0; i < getDepth(); i++) + { + putchar(' '); + } + } + + virtual void startElement(const char *name, const char **atts) + { + tabs(); + printf("startElement %s\n", name); + + S32 i = 0; + while( atts[i] && atts[i+1] ) + { + tabs(); + printf( "\t%s=%s\n", atts[i], atts[i+1] ); + i += 2; + } + + if( atts[i] ) + { + tabs(); + printf( "\ttrailing attribute: %s\n", atts[i] ); + } + } + + virtual void endElement(const char *name) + { + tabs(); + printf("endElement %s\n", name); + } + + virtual void characterData(const char *s, int len) + { + tabs(); + + char* str = new char[len+1]; + strncpy( str, s, len ); + str[len] = '\0'; + printf("CharacterData %s\n", str); + delete str; + } + + virtual void processingInstruction(const char *target, const char *data) + { + tabs(); + printf("processingInstruction %s\n", data); + } + virtual void comment(const char *data) + { + tabs(); + printf("comment %s\n", data); + } + + virtual void startCdataSection() + { + tabs(); + printf("startCdataSection\n"); + } + + virtual void endCdataSection() + { + tabs(); + printf("endCdataSection\n"); + } + + virtual void defaultData(const char *s, int len) + { + tabs(); + + char* str = new char[len+1]; + strncpy( str, s, len ); + str[len] = '\0'; + printf("defaultData %s\n", str); + delete str; + } + + virtual void unparsedEntityDecl( + const char *entityName, + const char *base, + const char *systemId, + const char *publicId, + const char *notationName) + { + tabs(); + + printf( + "unparsed entity:\n" + "\tentityName %s\n" + "\tbase %s\n" + "\tsystemId %s\n" + "\tpublicId %s\n" + "\tnotationName %s\n", + entityName, + base, + systemId, + publicId, + notationName ); + } +}; + + +int main() +{ + char buf[1024]; + + FILE* file = LLFile::fopen("test.xml", "rb"); + if( !file ) + { + return 1; + } + + LLXmlDOMParser parser; + int done; + do { + size_t len = fread(buf, 1, sizeof(buf), file); + done = len < sizeof(buf); + if( 0 == parser.parse( buf, len, done) ) + { + fprintf(stderr, + "%s at line %d\n", + parser.getErrorString(), + parser.getCurrentLineNumber() ); + return 1; + } + } while (!done); + + fclose( file ); + return 0; +} +*/ + diff --git a/indra/llxml/llxmlparser.h b/indra/llxml/llxmlparser.h new file mode 100644 index 0000000000..2cb75591fb --- /dev/null +++ b/indra/llxml/llxmlparser.h @@ -0,0 +1,109 @@ +/** + * @file llxmlparser.h + * @brief LLXmlParser class definition + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLXMLPARSER_H +#define LL_LLXMLPARSER_H + +#define XML_STATIC +#include "expat/expat.h" + +class LLXmlParser +{ +public: + LLXmlParser(); + virtual ~LLXmlParser(); + + // Parses entire file + BOOL parseFile(const std::string &path); + + // Parses some input. Returns 0 if a fatal error is detected. + // The last call must have isFinal true; + // len may be zero for this call (or any other). + S32 parse( const char* buf, int len, int isFinal ); + + const char* getErrorString(); + + S32 getCurrentLineNumber(); + + S32 getCurrentColumnNumber(); + + S32 getDepth() { return mDepth; } + +protected: + // atts is array of name/value pairs, terminated by 0; + // names and values are 0 terminated. + virtual void startElement(const char *name, const char **atts) {} + + virtual void endElement(const char *name) {} + + // s is not 0 terminated. + virtual void characterData(const char *s, int len) {} + + // target and data are 0 terminated + virtual void processingInstruction(const char *target, const char *data) {} + + // data is 0 terminated + virtual void comment(const char *data) {} + + virtual void startCdataSection() {} + + virtual void endCdataSection() {} + + // This is called for any characters in the XML document for + // which there is no applicable handler. This includes both + // characters that are part of markup which is of a kind that is + // not reported (comments, markup declarations), or characters + // that are part of a construct which could be reported but + // for which no handler has been supplied. The characters are passed + // exactly as they were in the XML document except that + // they will be encoded in UTF-8. Line boundaries are not normalized. + // Note that a byte order mark character is not passed to the default handler. + // There are no guarantees about how characters are divided between calls + // to the default handler: for example, a comment might be split between + // multiple calls. + virtual void defaultData(const char *s, int len) {} + + // This is called for a declaration of an unparsed (NDATA) + // entity. The base argument is whatever was set by XML_SetBase. + // The entityName, systemId and notationName arguments will never be null. + // The other arguments may be. + virtual void unparsedEntityDecl( + const char *entityName, + const char *base, + const char *systemId, + const char *publicId, + const char *notationName) {} + +public: + /////////////////////////////////////////////////////////////////////////////// + // Pseudo-private methods. These are only used by internal callbacks. + + static void startElementHandler(void *userData, const XML_Char *name, const XML_Char **atts); + static void endElementHandler(void *userData, const XML_Char *name); + static void characterDataHandler(void *userData, const XML_Char *s, int len); + static void processingInstructionHandler(void *userData, const XML_Char *target, const XML_Char *data); + static void commentHandler(void *userData, const XML_Char *data); + static void startCdataSectionHandler(void *userData); + static void endCdataSectionHandler(void *userData); + static void defaultDataHandler( void *userData, const XML_Char *s, int len); + static void unparsedEntityDeclHandler( + void *userData, + const XML_Char *entityName, + const XML_Char *base, + const XML_Char *systemId, + const XML_Char *publicId, + const XML_Char *notationName); + + +protected: + XML_Parser mParser; + int mDepth; + char mAuxErrorString[1024]; +}; + +#endif // LL_LLXMLPARSER_H diff --git a/indra/llxml/llxmltree.cpp b/indra/llxml/llxmltree.cpp new file mode 100644 index 0000000000..3d0d1ba379 --- /dev/null +++ b/indra/llxml/llxmltree.cpp @@ -0,0 +1,671 @@ +/** + * @file llxmltree.cpp + * @brief LLXmlTree implementation + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llxmltree.h" +#include "v3color.h" +#include "v4color.h" +#include "v4coloru.h" +#include "v3math.h" +#include "v3dmath.h" +#include "v4math.h" +#include "llquaternion.h" +#include "lluuid.h" + +////////////////////////////////////////////////////////////// +// LLXmlTree + +// static +LLStdStringTable LLXmlTree::sAttributeKeys(1024); + +LLXmlTree::LLXmlTree() + : mRoot( NULL ), + mNodeNames(512) +{ +} + +LLXmlTree::~LLXmlTree() +{ + cleanup(); +} + +void LLXmlTree::cleanup() +{ + delete mRoot; + mRoot = NULL; + mNodeNames.cleanup(); +} + + +BOOL LLXmlTree::parseFile(const std::string &path, BOOL keep_contents) +{ + delete mRoot; + mRoot = NULL; + + LLXmlTreeParser parser(this); + BOOL success = parser.parseFile( path, &mRoot, keep_contents ); + if( !success ) + { + S32 line_number = parser.getCurrentLineNumber(); + const char* error = parser.getErrorString(); + llwarns << "LLXmlTree parse failed. Line " << line_number << ": " << error << llendl; + } + return success; +} + +void LLXmlTree::dump() +{ + if( mRoot ) + { + dumpNode( mRoot, " " ); + } +} + +void LLXmlTree::dumpNode( LLXmlTreeNode* node, const LLString& prefix ) +{ + node->dump( prefix ); + + LLString new_prefix = prefix + " "; + for( LLXmlTreeNode* child = node->getFirstChild(); child; child = node->getNextChild() ) + { + dumpNode( child, new_prefix ); + } +} + +////////////////////////////////////////////////////////////// +// LLXmlTreeNode + +LLXmlTreeNode::LLXmlTreeNode( const std::string& name, LLXmlTreeNode* parent, LLXmlTree* tree ) + : mName(name), + mParent(parent), + mTree(tree) +{ +} + +LLXmlTreeNode::~LLXmlTreeNode() +{ + attribute_map_t::iterator iter; + for (iter=mAttributes.begin(); iter != mAttributes.end(); iter++) + delete iter->second; + child_list_t::iterator child_iter; + for (child_iter=mChildList.begin(); child_iter != mChildList.end(); child_iter++) + delete *child_iter; +} + +void LLXmlTreeNode::dump( const LLString& prefix ) +{ + llinfos << prefix << mName ; + if( !mContents.empty() ) + { + llcont << " contents = \"" << mContents << "\""; + } + attribute_map_t::iterator iter; + for (iter=mAttributes.begin(); iter != mAttributes.end(); iter++) + { + LLStdStringHandle key = iter->first; + const LLString* value = iter->second; + llcont << prefix << " " << key << "=" << (value->empty() ? "NULL" : *value); + } + llcont << llendl; +} + +BOOL LLXmlTreeNode::hasAttribute(const std::string& name) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + attribute_map_t::iterator iter = mAttributes.find(canonical_name); + return (iter == mAttributes.end()) ? false : true; +} + +void LLXmlTreeNode::addAttribute(const std::string& name, const std::string& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + const LLString *newstr = new LLString(value); + mAttributes[canonical_name] = newstr; // insert + copy +} + +LLXmlTreeNode* LLXmlTreeNode::getFirstChild() +{ + mChildListIter = mChildList.begin(); + return getNextChild(); +} +LLXmlTreeNode* LLXmlTreeNode::getNextChild() +{ + if (mChildListIter == mChildList.end()) + return 0; + else + return *mChildListIter++; +} + +LLXmlTreeNode* LLXmlTreeNode::getChildByName(const std::string& name) +{ + LLStdStringHandle tableptr = mTree->mNodeNames.checkString(name); + mChildMapIter = mChildMap.lower_bound(tableptr); + mChildMapEndIter = mChildMap.upper_bound(tableptr); + return getNextNamedChild(); +} + +LLXmlTreeNode* LLXmlTreeNode::getNextNamedChild() +{ + if (mChildMapIter == mChildMapEndIter) + return NULL; + else + return (mChildMapIter++)->second; +} + +void LLXmlTreeNode::appendContents(const std::string& str) +{ + mContents.append( str ); +} + +void LLXmlTreeNode::addChild(LLXmlTreeNode* child) +{ + llassert( child ); + mChildList.push_back( child ); + + // Add a name mapping to this node + LLStdStringHandle tableptr = mTree->mNodeNames.insert(child->mName); + mChildMap.insert( child_map_t::value_type(tableptr, child)); + + child->mParent = this; +} + +////////////////////////////////////////////////////////////// + +// These functions assume that name is already in mAttritrubteKeys + +BOOL LLXmlTreeNode::getFastAttributeBOOL(LLStdStringHandle canonical_name, BOOL& value) +{ + const LLString *s = getAttribute( canonical_name ); + return s && LLString::convertToBOOL( *s, value ); +} + +BOOL LLXmlTreeNode::getFastAttributeU8(LLStdStringHandle canonical_name, U8& value) +{ + const LLString *s = getAttribute( canonical_name ); + return s && LLString::convertToU8( *s, value ); +} + +BOOL LLXmlTreeNode::getFastAttributeS8(LLStdStringHandle canonical_name, S8& value) +{ + const LLString *s = getAttribute( canonical_name ); + return s && LLString::convertToS8( *s, value ); +} + +BOOL LLXmlTreeNode::getFastAttributeS16(LLStdStringHandle canonical_name, S16& value) +{ + const LLString *s = getAttribute( canonical_name ); + return s && LLString::convertToS16( *s, value ); +} + +BOOL LLXmlTreeNode::getFastAttributeU16(LLStdStringHandle canonical_name, U16& value) +{ + const LLString *s = getAttribute( canonical_name ); + return s && LLString::convertToU16( *s, value ); +} + +BOOL LLXmlTreeNode::getFastAttributeU32(LLStdStringHandle canonical_name, U32& value) +{ + const LLString *s = getAttribute( canonical_name ); + return s && LLString::convertToU32( *s, value ); +} + +BOOL LLXmlTreeNode::getFastAttributeS32(LLStdStringHandle canonical_name, S32& value) +{ + const LLString *s = getAttribute( canonical_name ); + return s && LLString::convertToS32( *s, value ); +} + +BOOL LLXmlTreeNode::getFastAttributeF32(LLStdStringHandle canonical_name, F32& value) +{ + const LLString *s = getAttribute( canonical_name ); + return s && LLString::convertToF32( *s, value ); +} + +BOOL LLXmlTreeNode::getFastAttributeF64(LLStdStringHandle canonical_name, F64& value) +{ + const LLString *s = getAttribute( canonical_name ); + return s && LLString::convertToF64( *s, value ); +} + +BOOL LLXmlTreeNode::getFastAttributeColor(LLStdStringHandle canonical_name, LLColor4& value) +{ + const LLString *s = getAttribute( canonical_name ); + return s ? LLColor4::parseColor(s->c_str(), &value) : FALSE; +} + +BOOL LLXmlTreeNode::getFastAttributeColor4(LLStdStringHandle canonical_name, LLColor4& value) +{ + const LLString *s = getAttribute( canonical_name ); + return s ? LLColor4::parseColor4(s->c_str(), &value) : FALSE; +} + +BOOL LLXmlTreeNode::getFastAttributeColor4U(LLStdStringHandle canonical_name, LLColor4U& value) +{ + const LLString *s = getAttribute( canonical_name ); + return s ? LLColor4U::parseColor4U(s->c_str(), &value ) : FALSE; +} + +BOOL LLXmlTreeNode::getFastAttributeVector3(LLStdStringHandle canonical_name, LLVector3& value) +{ + const LLString *s = getAttribute( canonical_name ); + return s ? LLVector3::parseVector3(s->c_str(), &value ) : FALSE; +} + +BOOL LLXmlTreeNode::getFastAttributeVector3d(LLStdStringHandle canonical_name, LLVector3d& value) +{ + const LLString *s = getAttribute( canonical_name ); + return s ? LLVector3d::parseVector3d(s->c_str(), &value ) : FALSE; +} + +BOOL LLXmlTreeNode::getFastAttributeQuat(LLStdStringHandle canonical_name, LLQuaternion& value) +{ + const LLString *s = getAttribute( canonical_name ); + return s ? LLQuaternion::parseQuat(s->c_str(), &value ) : FALSE; +} + +BOOL LLXmlTreeNode::getFastAttributeUUID(LLStdStringHandle canonical_name, LLUUID& value) +{ + const LLString *s = getAttribute( canonical_name ); + return s ? LLUUID::parseUUID(s->c_str(), &value ) : FALSE; +} + +BOOL LLXmlTreeNode::getFastAttributeString(LLStdStringHandle canonical_name, LLString& value) +{ + const LLString *s = getAttribute( canonical_name ); + if( !s ) + { + return FALSE; + } + + value = *s; + return TRUE; +} + + +////////////////////////////////////////////////////////////// + +BOOL LLXmlTreeNode::getAttributeBOOL(const std::string& name, BOOL& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeBOOL(canonical_name, value); +} + +BOOL LLXmlTreeNode::getAttributeU8(const std::string& name, U8& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeU8(canonical_name, value); +} + +BOOL LLXmlTreeNode::getAttributeS8(const std::string& name, S8& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeS8(canonical_name, value); +} + +BOOL LLXmlTreeNode::getAttributeS16(const std::string& name, S16& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeS16(canonical_name, value); +} + +BOOL LLXmlTreeNode::getAttributeU16(const std::string& name, U16& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeU16(canonical_name, value); +} + +BOOL LLXmlTreeNode::getAttributeU32(const std::string& name, U32& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeU32(canonical_name, value); +} + +BOOL LLXmlTreeNode::getAttributeS32(const std::string& name, S32& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeS32(canonical_name, value); +} + +BOOL LLXmlTreeNode::getAttributeF32(const std::string& name, F32& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeF32(canonical_name, value); +} + +BOOL LLXmlTreeNode::getAttributeF64(const std::string& name, F64& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeF64(canonical_name, value); +} + +BOOL LLXmlTreeNode::getAttributeColor(const std::string& name, LLColor4& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeColor(canonical_name, value); +} + +BOOL LLXmlTreeNode::getAttributeColor4(const std::string& name, LLColor4& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeColor4(canonical_name, value); +} + +BOOL LLXmlTreeNode::getAttributeColor4U(const std::string& name, LLColor4U& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeColor4U(canonical_name, value); +} + +BOOL LLXmlTreeNode::getAttributeVector3(const std::string& name, LLVector3& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeVector3(canonical_name, value); +} + +BOOL LLXmlTreeNode::getAttributeVector3d(const std::string& name, LLVector3d& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeVector3d(canonical_name, value); +} + +BOOL LLXmlTreeNode::getAttributeQuat(const std::string& name, LLQuaternion& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeQuat(canonical_name, value); +} + +BOOL LLXmlTreeNode::getAttributeUUID(const std::string& name, LLUUID& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeUUID(canonical_name, value); +} + +BOOL LLXmlTreeNode::getAttributeString(const std::string& name, LLString& value) +{ + LLStdStringHandle canonical_name = LLXmlTree::sAttributeKeys.addString( name ); + return getFastAttributeString(canonical_name, value); +} + +/* + The following xml nodes will all return the string from getTextContents(): + "The quick brown fox\n Jumps over the lazy dog" + + 1. HTML paragraph format: + +

The quick brown fox

+

Jumps over the lazy dog

+
+ 2. Each quoted section -> paragraph: + + "The quick brown fox" + " Jumps over the lazy dog" + + 3. Literal text with beginning and trailing whitespace removed: + +The quick brown fox + Jumps over the lazy dog + + +*/ + +LLString LLXmlTreeNode::getTextContents() +{ + std::string msg; + LLXmlTreeNode* p = getChildByName("p"); + if (p) + { + // Case 1: node has

text

tags + while (p) + { + msg += p->getContents() + "\n"; + p = getNextNamedChild(); + } + } + else + { + std::string::size_type n = mContents.find_first_not_of(" \t\n"); + if (n != std::string::npos && mContents[n] == '\"') + { + // Case 2: node has quoted text + S32 num_lines = 0; + while(1) + { + // mContents[n] == '"' + ++n; + std::string::size_type t = n; + std::string::size_type m = 0; + // fix-up escaped characters + while(1) + { + m = mContents.find_first_of("\\\"", t); // find first \ or " + if ((m == std::string::npos) || (mContents[m] == '\"')) + { + break; + } + mContents.erase(m,1); + t = m+1; + } + if (m == std::string::npos) + { + break; + } + // mContents[m] == '"' + num_lines++; + msg += mContents.substr(n,m-n) + "\n"; + n = mContents.find_first_of("\"", m+1); + if (n == std::string::npos) + { + if (num_lines == 1) + { + msg.erase(msg.size()-1); // remove "\n" if only one line + } + break; + } + } + } + else + { + // Case 3: node has embedded text (beginning and trailing whitespace trimmed) + msg = mContents; + } + } + return msg; +} + + +////////////////////////////////////////////////////////////// +// LLXmlTreeParser + +LLXmlTreeParser::LLXmlTreeParser(LLXmlTree* tree) + : mTree(tree), + mRoot( NULL ), + mCurrent( NULL ), + mDump( FALSE ) +{ +} + +LLXmlTreeParser::~LLXmlTreeParser() +{ +} + +BOOL LLXmlTreeParser::parseFile(const std::string &path, LLXmlTreeNode** root, BOOL keep_contents) +{ + llassert( !mRoot ); + llassert( !mCurrent ); + + mKeepContents = keep_contents; + + BOOL success = LLXmlParser::parseFile(path); + + *root = mRoot; + mRoot = NULL; + + if( success ) + { + llassert( !mCurrent ); + } + mCurrent = NULL; + + return success; +} + + +const std::string& LLXmlTreeParser::tabs() +{ + static LLString s; + s = ""; + S32 num_tabs = getDepth() - 1; + for( S32 i = 0; i < num_tabs; i++) + { + s += " "; + } + return s; +} + +void LLXmlTreeParser::startElement(const char* name, const char **atts) +{ + if( mDump ) + { + llinfos << tabs() << "startElement " << name << llendl; + + S32 i = 0; + while( atts[i] && atts[i+1] ) + { + llinfos << tabs() << "attribute: " << atts[i] << "=" << atts[i+1] << llendl; + i += 2; + } + } + + LLXmlTreeNode* child = CreateXmlTreeNode( std::string(name), mCurrent ); + + S32 i = 0; + while( atts[i] && atts[i+1] ) + { + child->addAttribute( atts[i], atts[i+1] ); + i += 2; + } + + if( mCurrent ) + { + mCurrent->addChild( child ); + + } + else + { + llassert( !mRoot ); + mRoot = child; + } + mCurrent = child; +} + +LLXmlTreeNode* LLXmlTreeParser::CreateXmlTreeNode(const std::string& name, LLXmlTreeNode* parent) +{ + return new LLXmlTreeNode(name, parent, mTree); +} + + +void LLXmlTreeParser::endElement(const char* name) +{ + if( mDump ) + { + llinfos << tabs() << "endElement " << name << llendl; + } + + if( !mCurrent->mContents.empty() ) + { + LLString::trim(mCurrent->mContents); + LLString::removeCRLF(mCurrent->mContents); + } + + mCurrent = mCurrent->getParent(); +} + +void LLXmlTreeParser::characterData(const char *s, int len) +{ + LLString str(s, len); + if( mDump ) + { + llinfos << tabs() << "CharacterData " << str << llendl; + } + + if (mKeepContents) + { + mCurrent->appendContents( str ); + } +} + +void LLXmlTreeParser::processingInstruction(const char *target, const char *data) +{ + if( mDump ) + { + llinfos << tabs() << "processingInstruction " << data << llendl; + } +} + +void LLXmlTreeParser::comment(const char *data) +{ + if( mDump ) + { + llinfos << tabs() << "comment " << data << llendl; + } +} + +void LLXmlTreeParser::startCdataSection() +{ + if( mDump ) + { + llinfos << tabs() << "startCdataSection" << llendl; + } +} + +void LLXmlTreeParser::endCdataSection() +{ + if( mDump ) + { + llinfos << tabs() << "endCdataSection" << llendl; + } +} + +void LLXmlTreeParser::defaultData(const char *s, int len) +{ + if( mDump ) + { + LLString str(s, len); + llinfos << tabs() << "defaultData " << str << llendl; + } +} + +void LLXmlTreeParser::unparsedEntityDecl( + const char* entity_name, + const char* base, + const char* system_id, + const char* public_id, + const char* notation_name) +{ + if( mDump ) + { + llinfos << tabs() << "unparsed entity:" << llendl; + llinfos << tabs() << " entityName " << entity_name << llendl; + llinfos << tabs() << " base " << base << llendl; + llinfos << tabs() << " systemId " << system_id << llendl; + llinfos << tabs() << " publicId " << public_id << llendl; + llinfos << tabs() << " notationName " << notation_name<< llendl; + } +} + +void test_llxmltree() +{ + LLXmlTree tree; + BOOL success = tree.parseFile( "test.xml" ); + if( success ) + { + tree.dump(); + } +} + diff --git a/indra/llxml/llxmltree.h b/indra/llxml/llxmltree.h new file mode 100644 index 0000000000..8cee425dd9 --- /dev/null +++ b/indra/llxml/llxmltree.h @@ -0,0 +1,216 @@ +/** + * @file llxmltree.h + * @author Aaron Yonas, Richard Nelson + * @brief LLXmlTree class definition + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLXMLTREE_H +#define LL_LLXMLTREE_H + +#include +#include +#include "llstring.h" +#include "llxmlparser.h" +#include "string_table.h" + +class LLColor4; +class LLColor4U; +class LLQuaternion; +class LLUUID; +class LLVector3; +class LLVector3d; +class LLXmlTreeNode; +class LLXmlTreeParser; + +////////////////////////////////////////////////////////////// +// LLXmlTree + +class LLXmlTree +{ + friend class LLXmlTreeNode; + +public: + LLXmlTree(); + virtual ~LLXmlTree(); + void cleanup(); + + virtual BOOL parseFile(const std::string &path, BOOL keep_contents = TRUE); + + LLXmlTreeNode* getRoot() { return mRoot; } + + void dump(); + void dumpNode( LLXmlTreeNode* node, const LLString &prefix ); + + static LLStdStringHandle addAttributeString( const std::string& name) + { + return sAttributeKeys.addString( name ); + } + +public: + // global + static LLStdStringTable sAttributeKeys; + +protected: + LLXmlTreeNode* mRoot; + + // local + LLStdStringTable mNodeNames; +}; + +////////////////////////////////////////////////////////////// +// LLXmlTreeNode + +class LLXmlTreeNode +{ + friend class LLXmlTree; + friend class LLXmlTreeParser; + +protected: + // Protected since nodes are only created and destroyed by friend classes and other LLXmlTreeNodes + LLXmlTreeNode( const std::string& name, LLXmlTreeNode* parent, LLXmlTree* tree ); + +public: + virtual ~LLXmlTreeNode(); + + const std::string& getName() + { + return mName; + } + BOOL hasName( const std::string& name ) + { + return mName == name; + } + + BOOL hasAttribute( const std::string& name ); + + // Fast versions use cannonical_name handlee to entru in LLXmlTree::sAttributeKeys string table + BOOL getFastAttributeBOOL( LLStdStringHandle cannonical_name, BOOL& value ); + BOOL getFastAttributeU8( LLStdStringHandle cannonical_name, U8& value ); + BOOL getFastAttributeS8( LLStdStringHandle cannonical_name, S8& value ); + BOOL getFastAttributeU16( LLStdStringHandle cannonical_name, U16& value ); + BOOL getFastAttributeS16( LLStdStringHandle cannonical_name, S16& value ); + BOOL getFastAttributeU32( LLStdStringHandle cannonical_name, U32& value ); + BOOL getFastAttributeS32( LLStdStringHandle cannonical_name, S32& value ); + BOOL getFastAttributeF32( LLStdStringHandle cannonical_name, F32& value ); + BOOL getFastAttributeF64( LLStdStringHandle cannonical_name, F64& value ); + BOOL getFastAttributeColor( LLStdStringHandle cannonical_name, LLColor4& value ); + BOOL getFastAttributeColor4( LLStdStringHandle cannonical_name, LLColor4& value ); + BOOL getFastAttributeColor4U( LLStdStringHandle cannonical_name, LLColor4U& value ); + BOOL getFastAttributeVector3( LLStdStringHandle cannonical_name, LLVector3& value ); + BOOL getFastAttributeVector3d( LLStdStringHandle cannonical_name, LLVector3d& value ); + BOOL getFastAttributeQuat( LLStdStringHandle cannonical_name, LLQuaternion& value ); + BOOL getFastAttributeUUID( LLStdStringHandle cannonical_name, LLUUID& value ); + BOOL getFastAttributeString( LLStdStringHandle cannonical_name, LLString& value ); + + // Normal versions find 'name' in LLXmlTree::sAttributeKeys then call fast versions + virtual BOOL getAttributeBOOL( const std::string& name, BOOL& value ); + virtual BOOL getAttributeU8( const std::string& name, U8& value ); + virtual BOOL getAttributeS8( const std::string& name, S8& value ); + virtual BOOL getAttributeU16( const std::string& name, U16& value ); + virtual BOOL getAttributeS16( const std::string& name, S16& value ); + virtual BOOL getAttributeU32( const std::string& name, U32& value ); + virtual BOOL getAttributeS32( const std::string& name, S32& value ); + virtual BOOL getAttributeF32( const std::string& name, F32& value ); + virtual BOOL getAttributeF64( const std::string& name, F64& value ); + virtual BOOL getAttributeColor( const std::string& name, LLColor4& value ); + virtual BOOL getAttributeColor4( const std::string& name, LLColor4& value ); + virtual BOOL getAttributeColor4U( const std::string& name, LLColor4U& value ); + virtual BOOL getAttributeVector3( const std::string& name, LLVector3& value ); + virtual BOOL getAttributeVector3d( const std::string& name, LLVector3d& value ); + virtual BOOL getAttributeQuat( const std::string& name, LLQuaternion& value ); + virtual BOOL getAttributeUUID( const std::string& name, LLUUID& value ); + virtual BOOL getAttributeString( const std::string& name, LLString& value ); + + const LLString& getContents() + { + return mContents; + } + LLString getTextContents(); + + LLXmlTreeNode* getParent() { return mParent; } + LLXmlTreeNode* getFirstChild(); + LLXmlTreeNode* getNextChild(); + S32 getChildCount() { return (S32)mChildList.size(); } + LLXmlTreeNode* getChildByName( const std::string& name ); // returns first child with name, NULL if none + LLXmlTreeNode* getNextNamedChild(); // returns next child with name, NULL if none + +protected: + const LLString* getAttribute( LLStdStringHandle name) + { + attribute_map_t::iterator iter = mAttributes.find(name); + return (iter == mAttributes.end()) ? 0 : iter->second; + } + +private: + void addAttribute( const std::string& name, const std::string& value ); + void appendContents( const std::string& str ); + void addChild( LLXmlTreeNode* child ); + + void dump( const LLString& prefix ); + +protected: + typedef std::map attribute_map_t; + attribute_map_t mAttributes; + +private: + LLString mName; + LLString mContents; + + typedef std::list child_list_t; + child_list_t mChildList; + child_list_t::iterator mChildListIter; + + typedef std::multimap child_map_t; + child_map_t mChildMap; // for fast name lookups + child_map_t::iterator mChildMapIter; + child_map_t::iterator mChildMapEndIter; + + LLXmlTreeNode* mParent; + LLXmlTree* mTree; +}; + +////////////////////////////////////////////////////////////// +// LLXmlTreeParser + +class LLXmlTreeParser : public LLXmlParser +{ +public: + LLXmlTreeParser(LLXmlTree* tree); + virtual ~LLXmlTreeParser(); + + BOOL parseFile(const std::string &path, LLXmlTreeNode** root, BOOL keep_contents ); + +protected: + const std::string& tabs(); + + // Overrides from LLXmlParser + virtual void startElement(const char *name, const char **attributes); + virtual void endElement(const char *name); + virtual void characterData(const char *s, int len); + virtual void processingInstruction(const char *target, const char *data); + virtual void comment(const char *data); + virtual void startCdataSection(); + virtual void endCdataSection(); + virtual void defaultData(const char *s, int len); + virtual void unparsedEntityDecl( + const char* entity_name, + const char* base, + const char* system_id, + const char* public_id, + const char* notation_name); + + //template method pattern + virtual LLXmlTreeNode* CreateXmlTreeNode(const std::string& name, LLXmlTreeNode* parent); + +protected: + LLXmlTree* mTree; + LLXmlTreeNode* mRoot; + LLXmlTreeNode* mCurrent; + BOOL mDump; // Dump parse tree to llinfos as it is read. + BOOL mKeepContents; +}; + +#endif // LL_LLXMLTREE_H diff --git a/indra/lscript/lscript_alloc.h b/indra/lscript/lscript_alloc.h new file mode 100644 index 0000000000..f0761c0afd --- /dev/null +++ b/indra/lscript/lscript_alloc.h @@ -0,0 +1,344 @@ +/** + * @file lscript_alloc.h + * @brief General heap management for scripting system + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LSCRIPT_ALLOC_H +#define LL_LSCRIPT_ALLOC_H +// #define at top of file accelerates gcc compiles +// Under gcc 2.9, the manual is unclear if comments can appear above #ifndef +// Under gcc 3, the manual explicitly states comments can appear above the #ifndef + +#include "stdtypes.h" +#include "lscript_byteconvert.h" +#include "lscript_library.h" +#include "llrand.h" +#include + +void reset_hp_to_safe_spot(const U8 *buffer); + + +// supported data types + +// basic types +// integer 4 bytes of integer data +// float 4 bytes of float data +// string data null terminated 1 byte string +// key data null terminated 1 byte string +// vector data 12 bytes of 3 floats +// quaternion data 16 bytes of 4 floats + +// list type +// list data 4 bytes of number of entries followed by followed by pointer + +// string pointer 4 bytes of address of string data on the heap (only used in list data) +// key pointer 4 bytes of address of key data on the heap (only used in list data) + +// heap format +// +// 4 byte offset to next block (in bytes) +// 1 byte of type of variable or empty +// 2 bytes of reference count +// nn bytes of data + +const S32 MAX_HEAP_SIZE = TOP_OF_MEMORY; + +class LLScriptAllocEntry +{ +public: + LLScriptAllocEntry() : mSize(0), mType(LST_NULL), mReferenceCount(0) {} + LLScriptAllocEntry(S32 offset, U8 type) : mSize(offset), mType(type), mReferenceCount(1) {} + friend std::ostream& operator<<(std::ostream& s, const LLScriptAllocEntry &a) + { + s << "Size: " << a.mSize << " Type: " << LSCRIPTTypeNames[a.mType] << " Count: " << a.mReferenceCount; + return s; + } + + S32 mSize; + U8 mType; + S16 mReferenceCount; +}; + +// this is only OK because we only load/save via accessors below +const S32 SIZEOF_SCRIPT_ALLOC_ENTRY = 7; + +inline void alloc_entry2bytestream(U8 *buffer, S32 &offset, const LLScriptAllocEntry &entry) +{ + if ( (offset < 0) + ||(offset > MAX_HEAP_SIZE)) + { + set_fault(buffer, LSRF_BOUND_CHECK_ERROR); + } + else + { + integer2bytestream(buffer, offset, entry.mSize); + byte2bytestream(buffer, offset, entry.mType); + s162bytestream(buffer, offset, entry.mReferenceCount); + } +} + +inline void bytestream2alloc_entry(LLScriptAllocEntry &entry, U8 *buffer, S32 &offset) +{ + if ( (offset < 0) + ||(offset > MAX_HEAP_SIZE)) + { + set_fault(buffer, LSRF_BOUND_CHECK_ERROR); + reset_hp_to_safe_spot(buffer); + } + else + { + entry.mSize = bytestream2integer(buffer, offset); + entry.mType = bytestream2byte(buffer, offset); + entry.mReferenceCount = bytestream2s16(buffer, offset); + } +} + +// create a heap from the HR to TM +BOOL lsa_create_heap(U8 *heap_start, S32 size); +void lsa_fprint_heap(U8 *buffer, FILE *fp); + +void lsa_print_heap(U8 *buffer); + +// adding to heap +// if block is empty +// if block is at least block size + 4 larger than data +// split block +// insert data into first part +// return address +// else +// insert data into block +// return address +// else +// if next block is >= SP +// set Stack-Heap collision +// return NULL +// if next block is empty +// merge next block with current block +// go to start of algorithm +// else +// move to next block +// go to start of algorithm + +S32 lsa_heap_add_data(U8 *buffer, LLScriptLibData *data, S32 heapsize, BOOL b_delete); + +S32 lsa_heap_top(U8 *heap_start, S32 maxsize); + +// split block +// set offset to point to new block +// set offset of new block to point to original offset - block size - data size +// set new block to empty +// set new block reference count to 0 +void lsa_split_block(U8 *buffer, S32 &offset, S32 size, LLScriptAllocEntry &entry); + +// insert data +// if data is non-list type +// set type to basic type, set reference count to 1, copy data, return address +// else +// set type to list data type, set reference count to 1 +// for each list entry +// insert data +// return address + +void lsa_insert_data(U8 *buffer, S32 &offset, LLScriptLibData *data, LLScriptAllocEntry &entry, S32 heapsize); + +S32 lsa_create_data_block(U8 **buffer, LLScriptLibData *data, S32 base_offset); + +// increase reference count +// increase reference count by 1 + +void lsa_increase_ref_count(U8 *buffer, S32 offset); + +// decrease reference count +// decrease reference count by 1 +// if reference count == 0 +// set type to empty + +void lsa_decrease_ref_count(U8 *buffer, S32 offset); + +inline S32 get_max_heap_size(U8 *buffer) +{ + return get_register(buffer, LREG_SP) - get_register(buffer, LREG_HR); +} + + +LLScriptLibData *lsa_get_data(U8 *buffer, S32 &offset, BOOL b_dec_ref); +LLScriptLibData *lsa_get_list_ptr(U8 *buffer, S32 &offset, BOOL b_dec_ref); + +S32 lsa_cat_strings(U8 *buffer, S32 offset1, S32 offset2, S32 heapsize); +S32 lsa_cmp_strings(U8 *buffer, S32 offset1, S32 offset2); + +S32 lsa_cat_lists(U8 *buffer, S32 offset1, S32 offset2, S32 heapsize); +S32 lsa_cmp_lists(U8 *buffer, S32 offset1, S32 offset2); +S32 lsa_preadd_lists(U8 *buffer, LLScriptLibData *data, S32 offset2, S32 heapsize); +S32 lsa_postadd_lists(U8 *buffer, S32 offset1, LLScriptLibData *data, S32 heapsize); + +// modifying a list +// insert new list that is modified +// store returned address in original list's variable +// decrease reference count on old list + +// list l1 = [10]; +// list l2 = l1; +// l1 = [11]; + +// we want l2 == [10]; + +// more complicated example: +// list l1 = [10, 11]; +// list l2 = l1; +// l1[0] = 12 + +// I think that we want l2 = [10, 11]; + +// one option would be to use syntax like: +// l1 = llSetList(l1, 0, 12); +// but this would require variable argument list matching +// which maybe is ok, but would be work +// the other option would be changes to lists that have multiple references causes a copy to occur + +// popl @l1, 0, integer, 12 +// +// would cause l1 to be copied, 12 to replace the 0th entry, and the address of the new list to be saved in l1 +// + +inline LLScriptLibData *lsa_bubble_sort(LLScriptLibData *src, S32 stride, S32 ascending) +{ + S32 number = src->getListLength(); + + if (number <= 0) + { + return NULL; + } + + if (stride <= 0) + { + stride = 1; + } + + S32 i = 0; + + if (number % stride) + { + LLScriptLibData *retval = src->mListp; + src->mListp = NULL; + return retval; + } + + LLScriptLibData **sortarray = (LLScriptLibData **)new U32[number]; + + LLScriptLibData *temp = src->mListp; + while (temp) + { + sortarray[i] = temp; + i++; + temp = temp->mListp; + } + + S32 j, s; + + for (i = 0; i < number; i += stride) + { + for (j = i; j < number; j += stride) + { + if ( ((*sortarray[i]) <= (*sortarray[j])) + != (ascending == TRUE)) + { + for (s = 0; s < stride; s++) + { + temp = sortarray[i + s]; + sortarray[i + s] = sortarray[j + s]; + sortarray[j + s] = temp; + } + } + } + } + + i = 1; + temp = sortarray[0]; + while (i < number) + { + temp->mListp = sortarray[i++]; + temp = temp->mListp; + } + temp->mListp = NULL; + + src->mListp = NULL; + + return sortarray[0]; +} + + +inline LLScriptLibData *lsa_randomize(LLScriptLibData *src, S32 stride) +{ + S32 number = src->getListLength(); + + if (number <= 0) + { + return NULL; + } + + if (stride <= 0) + { + stride = 1; + } + + if (number % stride) + { + LLScriptLibData *retval = src->mListp; + src->mListp = NULL; + return retval; + } + + LLScriptLibData **sortarray = (LLScriptLibData **)new U32[number]; + + LLScriptLibData *temp = src->mListp; + S32 i = 0; + while (temp) + { + sortarray[i] = temp; + i++; + temp = temp->mListp; + } + + S32 k, j, s; + + for (k = 0; k < 20; k++) + { + for (i = 0; i < number; i += stride) + { + for (j = i; j < number; j += stride) + { + if (frand(1.f) > 0.5) + { + for (s = 0; s < stride; s++) + { + temp = sortarray[i + s]; + sortarray[i + s] = sortarray[j + s]; + sortarray[j + s] = temp; + } + } + } + } + } + + i = 1; + temp = sortarray[0]; + while (i < number) + { + temp->mListp = sortarray[i++]; + temp = temp->mListp; + } + temp->mListp = NULL; + + src->mListp = NULL; + + LLScriptLibData *ret_value = sortarray[0]; + delete [] sortarray; + + return ret_value; +} + +#endif diff --git a/indra/lscript/lscript_byteconvert.h b/indra/lscript/lscript_byteconvert.h new file mode 100644 index 0000000000..d30c84b28c --- /dev/null +++ b/indra/lscript/lscript_byteconvert.h @@ -0,0 +1,1087 @@ +/** + * @file lscript_byteconvert.h + * @brief Shared code for compiler and assembler for LSL + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// data shared between compiler/assembler +// used to convert data between byte stream and outside data types + +#ifndef LL_LSCRIPT_BYTECONVERT_H +#define LL_LSCRIPT_BYTECONVERT_H + +#include "stdtypes.h" +#include "v3math.h" +#include "llquaternion.h" +#include "lscript_byteformat.h" +#include "lluuid.h" + +void reset_hp_to_safe_spot(const U8 *buffer); + +// remember that LScript byte stream is BigEndian +void set_fault(const U8 *stream, LSCRIPTRunTimeFaults fault); + +inline S32 bytestream2integer(const U8 *stream, S32 &offset) +{ + stream += offset; + offset += 4; + return (*stream<<24) | (*(stream + 1)<<16) | (*(stream + 2)<<8) | *(stream + 3); +} + +inline U32 bytestream2unsigned_integer(const U8 *stream, S32 &offset) +{ + stream += offset; + offset += 4; + return (*stream<<24) | (*(stream + 1)<<16) | (*(stream + 2)<<8) | *(stream + 3); +} + +inline U64 bytestream2u64(const U8 *stream, S32 &offset) +{ + stream += offset; + offset += 8; + return ((U64)(*stream)<<56)| ((U64)(*(stream + 1))<<48) | ((U64)(*(stream + 2))<<40) | ((U64)(*(stream + 3))<<32) | + ((U64)(*(stream + 4))<<24) | ((U64)(*(stream + 5))<<16) | ((U64)(*(stream + 6))<<8) | (U64)(*(stream + 7)); +} + +inline void integer2bytestream(U8 *stream, S32 &offset, S32 integer) +{ + stream += offset; + offset += 4; + *(stream) = (integer >> 24); + *(stream + 1) = (integer >> 16) & 0xff; + *(stream + 2) = (integer >> 8) & 0xff; + *(stream + 3) = (integer) & 0xff; +} + +inline void unsigned_integer2bytestream(U8 *stream, S32 &offset, U32 integer) +{ + stream += offset; + offset += 4; + *(stream) = (integer >> 24); + *(stream + 1) = (integer >> 16) & 0xff; + *(stream + 2) = (integer >> 8) & 0xff; + *(stream + 3) = (integer) & 0xff; +} +inline void u642bytestream(U8 *stream, S32 &offset, U64 integer) +{ + stream += offset; + offset += 8; + *(stream) = (U8)(integer >> 56); + *(stream + 1) = (U8)((integer >> 48) & 0xff); + *(stream + 2) = (U8)((integer >> 40) & 0xff); + *(stream + 3) = (U8)((integer >> 32) & 0xff); + *(stream + 4) = (U8)((integer >> 24) & 0xff); + *(stream + 5) = (U8)((integer >> 16) & 0xff); + *(stream + 6) = (U8)((integer >> 8) & 0xff); + *(stream + 7) = (U8)((integer) & 0xff); +} + +inline S16 bytestream2s16(const U8 *stream, S32 &offset) +{ + stream += offset; + offset += 2; + return (*stream<<8) | *(stream + 1); +} + +inline void s162bytestream(U8 *stream, S32 &offset, S16 integer) +{ + stream += offset; + offset += 2; + *(stream) = (integer >> 8); + *(stream + 1) = (integer) & 0xff; +} + +inline U16 bytestream2u16(const U8 *stream, S32 &offset) +{ + stream += offset; + offset += 2; + return (*stream<<8) | *(stream + 1); +} + +inline void u162bytestream(U8 *stream, S32 &offset, U16 integer) +{ + stream += offset; + offset += 2; + *(stream) = (integer >> 8); + *(stream + 1) = (integer) & 0xff; +} + +inline F32 bytestream2float(const U8 *stream, S32 &offset) +{ + S32 value = bytestream2integer(stream, offset); + F32 fpvalue = *(F32 *)&value; + if (!llfinite(fpvalue)) + { + fpvalue = 0; + set_fault(stream, LSRF_MATH); + } + return fpvalue; +} + +inline void float2bytestream(U8 *stream, S32 &offset, F32 floatingpoint) +{ + S32 value = *(S32 *)&floatingpoint; + integer2bytestream(stream, offset, value); +} + +inline void bytestream_int2float(U8 *stream, S32 &offset) +{ + S32 value = bytestream2integer(stream, offset); + offset -= 4; + F32 fpvalue = (F32)value; + if (!llfinite(fpvalue)) + { + fpvalue = 0; + set_fault(stream, LSRF_MATH); + } + float2bytestream(stream, offset, fpvalue); +} + +inline void bytestream2char(char *buffer, const U8 *stream, S32 &offset) +{ + while ((*buffer++ = *(stream + offset++))) + ; +} + +inline void char2bytestream(U8 *stream, S32 &offset, char *buffer) +{ + while ((*(stream + offset++) = *buffer++)) + ; +} + +inline U8 bytestream2byte(const U8 *stream, S32 &offset) +{ + return *(stream + offset++); +} + +inline void byte2bytestream(U8 *stream, S32 &offset, U8 byte) +{ + *(stream + offset++) = byte; +} + +inline void bytestream2bytestream(U8 *dest, S32 &dest_offset, const U8 *src, S32 &src_offset, S32 count) +{ + while (count) + { + (*(dest + dest_offset++)) = (*(src + src_offset++)); + count--; + } +} + +inline void uuid2bytestream(U8 *stream, S32 &offset, const LLUUID &uuid) +{ + S32 i; + for (i = 0; i < UUID_BYTES; i++) + { + *(stream + offset++) = uuid.mData[i]; + } +} + +inline void bytestream2uuid(U8 *stream, S32 &offset, LLUUID &uuid) +{ + S32 i; + for (i = 0; i < UUID_BYTES; i++) + { + uuid.mData[i] = *(stream + offset++); + } +} + +// vectors and quaternions and encoded in backwards order to match the way in which they are stored on the stack +inline void bytestream2vector(LLVector3 &vector, const U8 *stream, S32 &offset) +{ + S32 value = bytestream2integer(stream, offset); + vector.mV[VZ] = *(F32 *)&value; + if (!llfinite(vector.mV[VZ])) + { + vector.mV[VZ] = 0; + set_fault(stream, LSRF_MATH); + } + value = bytestream2integer(stream, offset); + vector.mV[VY] = *(F32 *)&value; + if (!llfinite(vector.mV[VY])) + { + vector.mV[VY] = 0; + set_fault(stream, LSRF_MATH); + } + value = bytestream2integer(stream, offset); + vector.mV[VX] = *(F32 *)&value; + if (!llfinite(vector.mV[VX])) + { + vector.mV[VX] = 0; + set_fault(stream, LSRF_MATH); + } +} + +inline void vector2bytestream(U8 *stream, S32 &offset, LLVector3 &vector) +{ + S32 value = *(S32 *)&vector.mV[VZ]; + integer2bytestream(stream, offset, value); + value = *(S32 *)&vector.mV[VY]; + integer2bytestream(stream, offset, value); + value = *(S32 *)&vector.mV[VX]; + integer2bytestream(stream, offset, value); +} + +inline void bytestream2quaternion(LLQuaternion &quat, const U8 *stream, S32 &offset) +{ + S32 value = bytestream2integer(stream, offset); + quat.mQ[VS] = *(F32 *)&value; + if (!llfinite(quat.mQ[VS])) + { + quat.mQ[VS] = 0; + set_fault(stream, LSRF_MATH); + } + value = bytestream2integer(stream, offset); + quat.mQ[VZ] = *(F32 *)&value; + if (!llfinite(quat.mQ[VZ])) + { + quat.mQ[VZ] = 0; + set_fault(stream, LSRF_MATH); + } + value = bytestream2integer(stream, offset); + quat.mQ[VY] = *(F32 *)&value; + if (!llfinite(quat.mQ[VY])) + { + quat.mQ[VY] = 0; + set_fault(stream, LSRF_MATH); + } + value = bytestream2integer(stream, offset); + quat.mQ[VX] = *(F32 *)&value; + if (!llfinite(quat.mQ[VX])) + { + quat.mQ[VX] = 0; + set_fault(stream, LSRF_MATH); + } +} + +inline void quaternion2bytestream(U8 *stream, S32 &offset, LLQuaternion &quat) +{ + S32 value = *(S32 *)&quat.mQ[VS]; + integer2bytestream(stream, offset, value); + value = *(S32 *)&quat.mQ[VZ]; + integer2bytestream(stream, offset, value); + value = *(S32 *)&quat.mQ[VY]; + integer2bytestream(stream, offset, value); + value = *(S32 *)&quat.mQ[VX]; + integer2bytestream(stream, offset, value); +} + +inline S32 get_register(const U8 *stream, LSCRIPTRegisters reg) +{ + S32 offset = gLSCRIPTRegisterAddresses[reg]; + return bytestream2integer(stream, offset); +} + +inline F32 get_register_fp(U8 *stream, LSCRIPTRegisters reg) +{ + S32 offset = gLSCRIPTRegisterAddresses[reg]; + F32 value = bytestream2float(stream, offset); + if (!llfinite(value)) + { + value = 0; + set_fault(stream, LSRF_MATH); + } + return value; +} +inline U64 get_register_u64(U8 *stream, LSCRIPTRegisters reg) +{ + S32 offset = gLSCRIPTRegisterAddresses[reg]; + return bytestream2u64(stream, offset); +} + +inline U64 get_event_register(U8 *stream, LSCRIPTRegisters reg, S32 major_version) +{ + if (major_version == 1) + { + S32 offset = gLSCRIPTRegisterAddresses[reg]; + return (U64)bytestream2integer(stream, offset); + } + else if (major_version == 2) + { + S32 offset = gLSCRIPTRegisterAddresses[reg + (LREG_NCE - LREG_CE)]; + return bytestream2u64(stream, offset); + } + else + { + S32 offset = gLSCRIPTRegisterAddresses[reg]; + return (U64)bytestream2integer(stream, offset); + } +} + +inline void set_register(U8 *stream, LSCRIPTRegisters reg, S32 value) +{ + S32 offset = gLSCRIPTRegisterAddresses[reg]; + integer2bytestream(stream, offset, value); +} + +inline void set_register_fp(U8 *stream, LSCRIPTRegisters reg, F32 value) +{ + S32 offset = gLSCRIPTRegisterAddresses[reg]; + float2bytestream(stream, offset, value); +} + +inline void set_register_u64(U8 *stream, LSCRIPTRegisters reg, U64 value) +{ + S32 offset = gLSCRIPTRegisterAddresses[reg]; + u642bytestream(stream, offset, value); +} + +inline void set_event_register(U8 *stream, LSCRIPTRegisters reg, U64 value, S32 major_version) +{ + if (major_version == 1) + { + S32 offset = gLSCRIPTRegisterAddresses[reg]; + integer2bytestream(stream, offset, (S32)value); + } + else if (major_version == 2) + { + S32 offset = gLSCRIPTRegisterAddresses[reg + (LREG_NCE - LREG_CE)]; + u642bytestream(stream, offset, value); + } + else + { + S32 offset = gLSCRIPTRegisterAddresses[reg]; + integer2bytestream(stream, offset, (S32)value); + } +} + + +inline F32 add_register_fp(U8 *stream, LSCRIPTRegisters reg, F32 value) +{ + S32 offset = gLSCRIPTRegisterAddresses[reg]; + F32 newvalue = bytestream2float(stream, offset); + newvalue += value; + if (!llfinite(newvalue)) + { + newvalue = 0; + set_fault(stream, LSRF_MATH); + } + offset = gLSCRIPTRegisterAddresses[reg]; + float2bytestream(stream, offset, newvalue); + return newvalue; +} + +void lsa_print_heap(U8 *buffer); + + +inline void set_fault(const U8 *stream, LSCRIPTRunTimeFaults fault) +{ + S32 fr = get_register(stream, LREG_FR); + // record the first error + if (!fr) + { + if ( (fault == LSRF_HEAP_ERROR) + ||(fault == LSRF_STACK_HEAP_COLLISION) + ||(fault == LSRF_BOUND_CHECK_ERROR)) + { + reset_hp_to_safe_spot(stream); +// lsa_print_heap((U8 *)stream); + } + fr = LSCRIPTRunTimeFaultBits[fault]; + set_register((U8 *)stream, LREG_FR, fr); + } +} + +inline BOOL set_ip(U8 *stream, S32 ip) +{ + // Verify that the Instruction Pointer is in a valid + // code area (between the Global Function Register + // and Heap Register). + S32 gfr = get_register(stream, LREG_GFR); + if (ip == 0) + { + set_register(stream, LREG_IP, ip); + return TRUE; + } + if (ip < gfr) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + S32 hr = get_register(stream, LREG_HR); + if (ip >= hr) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + set_register(stream, LREG_IP, ip); + return TRUE; +} + +inline BOOL set_bp(U8 *stream, S32 bp) +{ + // Verify that the Base Pointer is in a valid + // data area (between the Heap Pointer and + // the Top of Memory, and below the + // Stack Pointer). + S32 hp = get_register(stream, LREG_HP); + if (bp <= hp) + { + set_fault(stream, LSRF_STACK_HEAP_COLLISION); + return FALSE; + } + S32 tm = get_register(stream, LREG_TM); + if (bp >= tm) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + S32 sp = get_register(stream, LREG_SP); + if (bp < sp) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + set_register(stream, LREG_BP, bp); + return TRUE; +} + +inline BOOL set_sp(U8 *stream, S32 sp) +{ + // Verify that the Stack Pointer is in a valid + // data area (between the Heap Pointer and + // the Top of Memory). + S32 hp = get_register(stream, LREG_HP); + if (sp <= hp) + { + set_fault(stream, LSRF_STACK_HEAP_COLLISION); + return FALSE; + } + S32 tm = get_register(stream, LREG_TM); + if (sp >= tm) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + set_register(stream, LREG_SP, sp); + return TRUE; +} + +inline void lscript_push(U8 *stream, U8 value) +{ + S32 sp = get_register(stream, LREG_SP); + sp -= 1; + + if (set_sp(stream, sp)) + { + *(stream + sp) = value; + } +} + +inline void lscript_push(U8 *stream, S32 value) +{ + S32 sp = get_register(stream, LREG_SP); + sp -= LSCRIPTDataSize[LST_INTEGER]; + + if (set_sp(stream, sp)) + { + integer2bytestream(stream, sp, value); + } +} + +inline void lscript_push(U8 *stream, F32 value) +{ + S32 sp = get_register(stream, LREG_SP); + sp -= LSCRIPTDataSize[LST_FLOATINGPOINT]; + + if (set_sp(stream, sp)) + { + float2bytestream(stream, sp, value); + } +} + +inline void lscript_push(U8 *stream, LLVector3 &value) +{ + S32 sp = get_register(stream, LREG_SP); + sp -= LSCRIPTDataSize[LST_VECTOR]; + + if (set_sp(stream, sp)) + { + vector2bytestream(stream, sp, value); + } +} + +inline void lscript_push(U8 *stream, LLQuaternion &value) +{ + S32 sp = get_register(stream, LREG_SP); + sp -= LSCRIPTDataSize[LST_QUATERNION]; + + if (set_sp(stream, sp)) + { + quaternion2bytestream(stream, sp, value); + } +} + +inline void lscript_pusharg(U8 *stream, S32 arg) +{ + S32 sp = get_register(stream, LREG_SP); + sp -= arg; + + set_sp(stream, sp); +} + +inline void lscript_poparg(U8 *stream, S32 arg) +{ + S32 sp = get_register(stream, LREG_SP); + sp += arg; + + set_sp(stream, sp); +} + +inline U8 lscript_pop_char(U8 *stream) +{ + S32 sp = get_register(stream, LREG_SP); + U8 value = *(stream + sp++); + set_sp(stream, sp); + return value; +} + +inline S32 lscript_pop_int(U8 *stream) +{ + S32 sp = get_register(stream, LREG_SP); + S32 value = bytestream2integer(stream, sp); + set_sp(stream, sp); + return value; +} + +inline F32 lscript_pop_float(U8 *stream) +{ + S32 sp = get_register(stream, LREG_SP); + F32 value = bytestream2float(stream, sp); + if (!llfinite(value)) + { + value = 0; + set_fault(stream, LSRF_MATH); + } + set_sp(stream, sp); + return value; +} + +inline void lscript_pop_vector(U8 *stream, LLVector3 &value) +{ + S32 sp = get_register(stream, LREG_SP); + bytestream2vector(value, stream, sp); + set_sp(stream, sp); +} + +inline void lscript_pop_quaternion(U8 *stream, LLQuaternion &value) +{ + S32 sp = get_register(stream, LREG_SP); + bytestream2quaternion(value, stream, sp); + set_sp(stream, sp); +} + +inline void lscript_pusharge(U8 *stream, S32 value) +{ + S32 sp = get_register(stream, LREG_SP); + sp -= value; + if (set_sp(stream, sp)) + { + S32 i; + for (i = 0; i < value; i++) + { + *(stream + sp++) = 0; + } + } +} + +inline BOOL lscript_check_local(U8 *stream, S32 &address, S32 size) +{ + S32 sp = get_register(stream, LREG_SP); + S32 bp = get_register(stream, LREG_BP); + + address += size; + address = bp - address; + + if (address < sp - size) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + S32 tm = get_register(stream, LREG_TM); + if (address + size > tm) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + return TRUE; +} + +inline BOOL lscript_check_global(U8 *stream, S32 &address, S32 size) +{ + S32 gvr = get_register(stream, LREG_GVR); + + // Possibility of overwriting registers? -- DK 09/07/04 + if (address < 0) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + + address += gvr; + S32 gfr = get_register(stream, LREG_GFR); + + if (address + size > gfr) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + return TRUE; +} + +inline void lscript_local_store(U8 *stream, S32 address, S32 value) +{ + if (lscript_check_local(stream, address, LSCRIPTDataSize[LST_INTEGER])) + integer2bytestream(stream, address, value); +} + +inline void lscript_local_store(U8 *stream, S32 address, F32 value) +{ + if (lscript_check_local(stream, address, LSCRIPTDataSize[LST_FLOATINGPOINT])) + float2bytestream(stream, address, value); +} + +inline void lscript_local_store(U8 *stream, S32 address, LLVector3 value) +{ + if (lscript_check_local(stream, address, LSCRIPTDataSize[LST_VECTOR])) + vector2bytestream(stream, address, value); +} + +inline void lscript_local_store(U8 *stream, S32 address, LLQuaternion value) +{ + if (lscript_check_local(stream, address, LSCRIPTDataSize[LST_QUATERNION])) + quaternion2bytestream(stream, address, value); +} + +inline void lscript_global_store(U8 *stream, S32 address, S32 value) +{ + if (lscript_check_global(stream, address, LSCRIPTDataSize[LST_INTEGER])) + integer2bytestream(stream, address, value); +} + +inline void lscript_global_store(U8 *stream, S32 address, F32 value) +{ + if (lscript_check_global(stream, address, LSCRIPTDataSize[LST_FLOATINGPOINT])) + float2bytestream(stream, address, value); +} + +inline void lscript_global_store(U8 *stream, S32 address, LLVector3 value) +{ + if (lscript_check_global(stream, address, LSCRIPTDataSize[LST_VECTOR])) + vector2bytestream(stream, address, value); +} + +inline void lscript_global_store(U8 *stream, S32 address, LLQuaternion value) +{ + if (lscript_check_global(stream, address, LSCRIPTDataSize[LST_QUATERNION])) + quaternion2bytestream(stream, address, value); +} + +inline S32 lscript_local_get(U8 *stream, S32 address) +{ + if (lscript_check_local(stream, address, LSCRIPTDataSize[LST_INTEGER])) + return bytestream2integer(stream, address); + return 0; +} + +inline void lscript_local_get(U8 *stream, S32 address, F32 &value) +{ + if (lscript_check_local(stream, address, LSCRIPTDataSize[LST_FLOATINGPOINT])) + value = bytestream2float(stream, address); + if (!llfinite(value)) + { + value = 0; + set_fault(stream, LSRF_MATH); + } +} + +inline void lscript_local_get(U8 *stream, S32 address, LLVector3 &value) +{ + if (lscript_check_local(stream, address, LSCRIPTDataSize[LST_VECTOR])) + bytestream2vector(value, stream, address); +} + +inline void lscript_local_get(U8 *stream, S32 address, LLQuaternion &value) +{ + if (lscript_check_local(stream, address, LSCRIPTDataSize[LST_QUATERNION])) + bytestream2quaternion(value, stream, address); +} + +inline S32 lscript_global_get(U8 *stream, S32 address) +{ + if (lscript_check_global(stream, address, LSCRIPTDataSize[LST_INTEGER])) + return bytestream2integer(stream, address); + return 0; +} + +inline void lscript_global_get(U8 *stream, S32 address, F32 &value) +{ + if (lscript_check_global(stream, address, LSCRIPTDataSize[LST_FLOATINGPOINT])) + value = bytestream2float(stream, address); + if (!llfinite(value)) + { + value = 0; + set_fault(stream, LSRF_MATH); + } +} + +inline void lscript_global_get(U8 *stream, S32 address, LLVector3 &value) +{ + if (lscript_check_global(stream, address, LSCRIPTDataSize[LST_VECTOR])) + bytestream2vector(value, stream, address); +} + +inline void lscript_global_get(U8 *stream, S32 address, LLQuaternion &value) +{ + if (lscript_check_global(stream, address, LSCRIPTDataSize[LST_QUATERNION])) + bytestream2quaternion(value, stream, address); +} + + + +inline S32 get_state_event_opcoode_start(U8 *stream, S32 state, LSCRIPTStateEventType event) +{ + // get the start of the state table + S32 sr = get_register(stream, LREG_SR); + + // get the position of the jump to the desired state + S32 value = get_register(stream, LREG_VN); + + S32 state_offset_offset = 0; + S32 major_version = 0; + if (value == LSL2_VERSION1_END_NUMBER) + { + major_version = LSL2_MAJOR_VERSION_ONE; + state_offset_offset = sr + LSCRIPTDataSize[LST_INTEGER] + LSCRIPTDataSize[LST_INTEGER]*2*state; + } + else if (value == LSL2_VERSION_NUMBER) + { + major_version = LSL2_MAJOR_VERSION_TWO; + state_offset_offset = sr + LSCRIPTDataSize[LST_INTEGER] + LSCRIPTDataSize[LST_INTEGER]*3*state; + } + + // get the actual position in memory of the desired state + S32 state_offset = sr + bytestream2integer(stream, state_offset_offset); + + // save that value + S32 state_offset_base = state_offset; + + // jump past the state name + S32 event_jump_offset = state_offset_base + bytestream2integer(stream, state_offset); + + // get the location of the event offset + S32 event_offset = event_jump_offset + LSCRIPTDataSize[LST_INTEGER]*2*get_event_handler_jump_position(get_event_register(stream, LREG_ER, major_version), event); + + // now, jump to the event + S32 event_start = bytestream2integer(stream, event_offset); + event_start += event_jump_offset; + + S32 event_start_original = event_start; + + // now skip past the parameters + S32 opcode_offset = bytestream2integer(stream, event_start); + return opcode_offset + event_start_original; +} + +inline U64 get_handled_events(U8 *stream, S32 state) +{ + U64 retvalue = 0; + // get the start of the state table + S32 sr = get_register(stream, LREG_SR); + + // get the position of the jump to the desired state + S32 value = get_register(stream, LREG_VN); + S32 state_handled_offset = 0; + if (value == LSL2_VERSION1_END_NUMBER) + { + state_handled_offset = sr + LSCRIPTDataSize[LST_INTEGER]*2*state + 2*LSCRIPTDataSize[LST_INTEGER]; + retvalue = bytestream2integer(stream, state_handled_offset); + } + else if (value == LSL2_VERSION_NUMBER) + { + state_handled_offset = sr + LSCRIPTDataSize[LST_INTEGER]*3*state + 2*LSCRIPTDataSize[LST_INTEGER]; + retvalue = bytestream2u64(stream, state_handled_offset); + } + + // get the handled events + return retvalue; +} + +inline S32 get_event_stack_size(U8 *stream, S32 state, LSCRIPTStateEventType event) +{ + // get the start of the state table + S32 sr = get_register(stream, LREG_SR); + + // get state offset + S32 value = get_register(stream, LREG_VN); + S32 state_offset_offset = 0; + S32 major_version = 0; + if (value == LSL2_VERSION1_END_NUMBER) + { + major_version = LSL2_MAJOR_VERSION_ONE; + state_offset_offset = sr + LSCRIPTDataSize[LST_INTEGER] + LSCRIPTDataSize[LST_INTEGER]*2*state; + } + else if (value == LSL2_VERSION_NUMBER) + { + major_version = LSL2_MAJOR_VERSION_TWO; + state_offset_offset = sr + LSCRIPTDataSize[LST_INTEGER] + LSCRIPTDataSize[LST_INTEGER]*3*state; + } + + S32 state_offset = bytestream2integer(stream, state_offset_offset); + state_offset += sr; + + state_offset_offset = state_offset; + + // skip to jump table + S32 jump_table = bytestream2integer(stream, state_offset_offset); + + jump_table += state_offset; + + // get the position of the jump to the desired state + S32 stack_size_offset = jump_table + LSCRIPTDataSize[LST_INTEGER]*2*get_event_handler_jump_position(get_event_register(stream, LREG_ER, major_version), event) + LSCRIPTDataSize[LST_INTEGER]; + + // get the handled events + S32 stack_size = bytestream2integer(stream, stack_size_offset); + return stack_size; +} + +inline LSCRIPTStateEventType return_first_event(S32 event) +{ + S32 count = 1; + while (count < LSTT_EOF) + { + if (event & 0x1) + { + return (LSCRIPTStateEventType) count; + } + else + { + event >>= 1; + count++; + } + } + return LSTT_NULL; +} + + +// the safe instruction versions of these commands will only work if offset is between +// GFR and HR, meaning that it is an instruction (more or less) in global functions or event handlers + +inline BOOL safe_instruction_check_address(U8 *stream, S32 offset, S32 size) +{ + S32 gfr = get_register(stream, LREG_GFR); + if (offset < gfr) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + else + { + S32 hr = get_register(stream, LREG_HR); + if (offset + size > hr) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + else + { + return TRUE; + } + } +} + +inline BOOL safe_heap_check_address(U8 *stream, S32 offset, S32 size) +{ + S32 hr = get_register(stream, LREG_HR); + if (offset < hr) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + else + { + S32 hp = get_register(stream, LREG_HP); + if (offset + size > hp) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + else + { + return TRUE; + } + } +} + +inline U8 safe_instruction_bytestream2byte(U8 *stream, S32 &offset) +{ + if (safe_instruction_check_address(stream, offset, 1)) + { + return *(stream + offset++); + } + else + { + return 0; + } +} + +inline void safe_instruction_byte2bytestream(U8 *stream, S32 &offset, U8 byte) +{ + if (safe_instruction_check_address(stream, offset, 1)) + { + *(stream + offset++) = byte; + } +} + +inline S32 safe_instruction_bytestream2integer(U8 *stream, S32 &offset) +{ + if (safe_instruction_check_address(stream, offset, LSCRIPTDataSize[LST_INTEGER])) + { + return (bytestream2integer(stream, offset)); + } + else + { + return 0; + } +} + +inline void safe_instruction_integer2bytestream(U8 *stream, S32 &offset, S32 value) +{ + if (safe_instruction_check_address(stream, offset, LSCRIPTDataSize[LST_INTEGER])) + { + integer2bytestream(stream, offset, value); + } +} + +inline U16 safe_instruction_bytestream2u16(U8 *stream, S32 &offset) +{ + if (safe_instruction_check_address(stream, offset, 2)) + { + return (bytestream2u16(stream, offset)); + } + else + { + return 0; + } +} + +inline void safe_instruction_u162bytestream(U8 *stream, S32 &offset, U16 value) +{ + if (safe_instruction_check_address(stream, offset, 2)) + { + u162bytestream(stream, offset, value); + } +} + +inline F32 safe_instruction_bytestream2float(U8 *stream, S32 &offset) +{ + if (safe_instruction_check_address(stream, offset, LSCRIPTDataSize[LST_INTEGER])) + { + F32 value = bytestream2float(stream, offset); + if (!llfinite(value)) + { + value = 0; + set_fault(stream, LSRF_MATH); + } + return value; + } + else + { + return 0; + } +} + +inline void safe_instruction_float2bytestream(U8 *stream, S32 &offset, F32 value) +{ + if (safe_instruction_check_address(stream, offset, LSCRIPTDataSize[LST_FLOATINGPOINT])) + { + float2bytestream(stream, offset, value); + } +} + +inline void safe_instruction_bytestream2char(char *buffer, U8 *stream, S32 &offset) +{ + while ( (safe_instruction_check_address(stream, offset, 1)) + &&(*buffer++ = *(stream + offset++))) + ; +} + +inline void safe_instruction_bytestream_count_char(U8 *stream, S32 &offset) +{ + while ( (safe_instruction_check_address(stream, offset, 1)) + &&(*(stream + offset++))) + ; +} + +inline void safe_heap_bytestream_count_char(U8 *stream, S32 &offset) +{ + while ( (safe_heap_check_address(stream, offset, 1)) + &&(*(stream + offset++))) + ; +} + +inline void safe_instruction_char2bytestream(U8 *stream, S32 &offset, char *buffer) +{ + while ( (safe_instruction_check_address(stream, offset, 1)) + &&(*(stream + offset++) = *buffer++)) + ; +} + +inline void safe_instruction_bytestream2vector(LLVector3 &value, U8 *stream, S32 &offset) +{ + if (safe_instruction_check_address(stream, offset, LSCRIPTDataSize[LST_VECTOR])) + { + bytestream2vector(value, stream, offset); + } +} + +inline void safe_instruction_vector2bytestream(U8 *stream, S32 &offset, LLVector3 &value) +{ + if (safe_instruction_check_address(stream, offset, LSCRIPTDataSize[LST_VECTOR])) + { + vector2bytestream(stream, offset, value); + } +} + +inline void safe_instruction_bytestream2quaternion(LLQuaternion &value, U8 *stream, S32 &offset) +{ + if (safe_instruction_check_address(stream, offset, LSCRIPTDataSize[LST_QUATERNION])) + { + bytestream2quaternion(value, stream, offset); + } +} + +inline void safe_instruction_quaternion2bytestream(U8 *stream, S32 &offset, LLQuaternion &value) +{ + if (safe_instruction_check_address(stream, offset, LSCRIPTDataSize[LST_QUATERNION])) + { + quaternion2bytestream(stream, offset, value); + } +} + +static inline LSCRIPTType char2type(char type) +{ + switch(type) + { + case 'i': + return LST_INTEGER; + case 'f': + return LST_FLOATINGPOINT; + case 's': + return LST_STRING; + case 'k': + return LST_KEY; + case 'v': + return LST_VECTOR; + case 'q': + return LST_QUATERNION; + case 'l': + return LST_LIST; + default: + return LST_NULL; + } +} + +#endif diff --git a/indra/lscript/lscript_byteformat.h b/indra/lscript/lscript_byteformat.h new file mode 100644 index 0000000000..a79f2effae --- /dev/null +++ b/indra/lscript/lscript_byteformat.h @@ -0,0 +1,544 @@ +/** + * @file lscript_byteformat.h + * @brief Shared code between compiler and assembler and LSL + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LSCRIPT_BYTEFORMAT_H +#define LL_LSCRIPT_BYTEFORMAT_H + +// Data shared between compiler/assembler and lscript execution code + +#include "stdtypes.h" + +const S32 LSL2_VERSION_NUMBER = 0x0200; +const S32 LSL2_VERSION1_END_NUMBER = 0x0101; +const S32 LSL2_VERSION2_START_NUMBER = 0x0200; + +const S32 LSL2_MAJOR_VERSION_ONE = 1; +const S32 LSL2_MAJOR_VERSION_TWO = 2; +const S32 LSL2_CURRENT_MAJOR_VERSION = LSL2_MAJOR_VERSION_TWO; + +const S32 TOP_OF_MEMORY = 16384; + +typedef enum e_lscript_registers +{ + LREG_INVALID, + LREG_IP, // instruction pointer + LREG_VN, // version number + LREG_BP, // base pointer - what local variables are referenced from + LREG_SP, // stack pointer - where the top of the stack is + LREG_HR, // heap register - where in memory does the heap start + LREG_HP, // heap pointer - where is the top of the heap? + LREG_CS, // current state - what state are we currently in? + LREG_NS, // next state - what state are we currently in? + LREG_CE, // current events - what events are waiting to be handled? + LREG_IE, // in event - which event handler are we currently in? + LREG_ER, // event register - what events do we have active handlers for? + LREG_FR, // fault register - which errors are currently active? + LREG_SLR, // sleep register - are we sleeping? + LREG_GVR, // global variable register - where do global variables start + LREG_GFR, // global function register - where do global functions start + LREG_SR, // state register - where do states start + LREG_TM, // top of memory - where is the top of memory + LREG_PR, // parameter register - data passed to script from launcher + LREG_ESR, // energy supply register - how much energy do we have on board? + LREG_NCE, // 64 bit current envents - what events are waiting to be handled? + LREG_NIE, // 64 bit in event - which event handler are we currently in? + LREG_NER, // 64 bit event register - what events do we have active handlers for? + LREG_EOF +} LSCRIPTRegisters; + +const S32 gLSCRIPTRegisterAddresses[LREG_EOF] = +{ + 0, // LREG_INVALID + 4, // LREG_IP + 8, // LREG_VN + 12, // LREG_BP + 16, // LREG_SP + 20, // LREG_HR + 24, // LREG_HP + 28, // LREG_CS + 32, // LREG_NS + 36, // LREG_CE + 40, // LREG_IE + 44, // LREG_ER + 48, // LREG_FR + 52, // LREG_SLR + 56, // LREG_GVR + 60, // LREG_GFR + 72, // LREG_SR + 0, // LREG_TM + 64, // LREG_PR + 68, // LREG_ESR + 76, // LREG_NCE + 84, // LREG_NIE + 92, // LREG_NER +}; + +const char * const gLSCRIPTRegisterNames[LREG_EOF] = +{ + "INVALID", // LREG_INVALID + "IP", // LREG_IP + "VN", // LREG_VN + "BP", // LREG_BP + "SP", // LREG_SP + "HR", // LREG_HR + "HP", // LREG_HP + "CS", // LREG_CS + "NS", // LREG_NS + "CE", // LREG_CE + "IE", // LREG_IE + "ER", // LREG_ER + "FR", // LREG_FR + "SLR", // LREG_SLR + "GVR", // LREG_GVR + "GFR", // LREG_GFR + "SR", // LREG_SR + "TM", // LREG_TM + "PR", // LREG_PR + "ESR", // LREG_ESR + "NCE", // LREG_NCE + "NIE", // LREG_NIE + "NER", // LREG_NER +}; + +typedef enum e_lscript_op_codes +{ + LOPC_INVALID, + LOPC_NOOP, + LOPC_POP, + LOPC_POPS, + LOPC_POPL, + LOPC_POPV, + LOPC_POPQ, + LOPC_POPARG, + LOPC_POPIP, + LOPC_POPBP, + LOPC_POPSP, + LOPC_POPSLR, + LOPC_DUP, + LOPC_DUPS, + LOPC_DUPL, + LOPC_DUPV, + LOPC_DUPQ, + LOPC_STORE, + LOPC_STORES, + LOPC_STOREL, + LOPC_STOREV, + LOPC_STOREQ, + LOPC_STOREG, + LOPC_STOREGS, + LOPC_STOREGL, + LOPC_STOREGV, + LOPC_STOREGQ, + LOPC_LOADP, + LOPC_LOADSP, + LOPC_LOADLP, + LOPC_LOADVP, + LOPC_LOADQP, + LOPC_LOADGP, + LOPC_LOADGLP, + LOPC_LOADGSP, + LOPC_LOADGVP, + LOPC_LOADGQP, + LOPC_PUSH, + LOPC_PUSHS, + LOPC_PUSHL, + LOPC_PUSHV, + LOPC_PUSHQ, + LOPC_PUSHG, + LOPC_PUSHGS, + LOPC_PUSHGL, + LOPC_PUSHGV, + LOPC_PUSHGQ, + LOPC_PUSHIP, + LOPC_PUSHBP, + LOPC_PUSHSP, + LOPC_PUSHARGB, + LOPC_PUSHARGI, + LOPC_PUSHARGF, + LOPC_PUSHARGS, + LOPC_PUSHARGV, + LOPC_PUSHARGQ, + LOPC_PUSHE, + LOPC_PUSHEV, + LOPC_PUSHEQ, + LOPC_PUSHARGE, + LOPC_ADD, + LOPC_SUB, + LOPC_MUL, + LOPC_DIV, + LOPC_MOD, + LOPC_EQ, + LOPC_NEQ, + LOPC_LEQ, + LOPC_GEQ, + LOPC_LESS, + LOPC_GREATER, + LOPC_BITAND, + LOPC_BITOR, + LOPC_BITXOR, + LOPC_BOOLAND, + LOPC_BOOLOR, + LOPC_NEG, + LOPC_BITNOT, + LOPC_BOOLNOT, + LOPC_JUMP, + LOPC_JUMPIF, + LOPC_JUMPNIF, + LOPC_STATE, + LOPC_CALL, + LOPC_RETURN, + LOPC_CAST, + LOPC_STACKTOS, + LOPC_STACKTOL, + LOPC_PRINT, + LOPC_CALLLIB, + LOPC_CALLLIB_TWO_BYTE, + LOPC_SHL, + LOPC_SHR, + LOPC_EOF +} LSCRIPTOpCodesEnum; + +const U8 LSCRIPTOpCodes[LOPC_EOF] = +{ + 0x00, // LOPC_INVALID + 0x00, // LOPC_NOOP + 0x01, // LOPC_POP + 0x02, // LOPC_POPS + 0x03, // LOPC_POPL + 0x04, // LOPC_POPV + 0x05, // LOPC_POPQ + 0x06, // LOPC_POPARG + 0x07, // LOPC_POPIP + 0x08, // LOPC_POPBP + 0x09, // LOPC_POPSP + 0x0a, // LOPC_POPSLR + 0x20, // LOPC_DUP + 0x21, // LOPC_DUPS + 0x22, // LOPC_DUPL + 0x23, // LOPC_DUPV + 0x24, // LOPC_DUPQ + 0x30, // LOPC_STORE + 0x31, // LOPC_STORES + 0x32, // LOPC_STOREL + 0x33, // LOPC_STOREV + 0x34, // LOPC_STOREQ + 0x35, // LOPC_STOREG + 0x36, // LOPC_STOREGS + 0x37, // LOPC_STOREGL + 0x38, // LOPC_STOREGV + 0x39, // LOPC_STOREGQ + 0x3a, // LOPC_LOADP + 0x3b, // LOPC_LOADSP + 0x3c, // LOPC_LOADLP + 0x3d, // LOPC_LOADVP + 0x3e, // LOPC_LOADQP + 0x3f, // LOPC_LOADGP + 0x40, // LOPC_LOADGSP + 0x41, // LOPC_LOADGLP + 0x42, // LOPC_LOADGVP + 0x43, // LOPC_LOADGQP + 0x50, // LOPC_PUSH + 0x51, // LOPC_PUSHS + 0x52, // LOPC_PUSHL + 0x53, // LOPC_PUSHV + 0x54, // LOPC_PUSHQ + 0x55, // LOPC_PUSHG + 0x56, // LOPC_PUSHGS + 0x57, // LOPC_PUSHGL + 0x58, // LOPC_PUSHGV + 0x59, // LOPC_PUSHGQ + 0x5a, // LOPC_PUSHIP + 0x5b, // LOPC_PUSHBP + 0x5c, // LOPC_PUSHSP + 0x5d, // LOPC_PUSHARGB + 0x5e, // LOPC_PUSHARGI + 0x5f, // LOPC_PUSHARGF + 0x60, // LOPC_PUSHARGS + 0x61, // LOPC_PUSHARGV + 0x62, // LOPC_PUSHARGQ + 0x63, // LOPC_PUSHE + 0x64, // LOPC_PUSHEV + 0x65, // LOPC_PUSHEQ + 0x66, // LOPC_PUSHARGE + 0x70, // LOPC_ADD + 0x71, // LOPC_SUB + 0x72, // LOPC_MUL + 0x73, // LOPC_DIV + 0x74, // LOPC_MOD + 0x75, // LOPC_EQ + 0x76, // LOPC_NEQ + 0x77, // LOPC_LEQ + 0x78, // LOPC_GEQ + 0x79, // LOPC_LESS + 0x7a, // LOPC_GREATER + 0x7b, // LOPC_BITAND + 0x7c, // LOPC_BITOR + 0x7d, // LOPC_BITXOR + 0x7e, // LOPC_BOOLAND + 0x7f, // LOPC_BOOLOR + 0x80, // LOPC_NEG + 0x81, // LOPC_BITNOT + 0x82, // LOPC_BOOLNOT + 0x90, // LOPC_JUMP + 0x91, // LOPC_JUMPIF + 0x92, // LOPC_JUMPNIF + 0x93, // LOPC_STATE + 0x94, // LOPC_CALL + 0x95, // LOPC_RETURN + 0xa0, // LOPC_CAST + 0xb0, // LOPC_STACKTOS + 0xb1, // LOPC_STACKTOL + 0xc0, // LOPC_PRINT + 0xd0, // LOPC_CALLLIB + 0xd1, // LOPC_CALLLIB_TWO_BYTE + 0xe0, // LOPC_SHL + 0xe1 // LOPC_SHR +}; + +typedef enum e_lscript_state_event_type +{ + LSTT_NULL, + LSTT_STATE_ENTRY, + LSTT_STATE_EXIT, + LSTT_TOUCH_START, + LSTT_TOUCH, + LSTT_TOUCH_END, + LSTT_COLLISION_START, + LSTT_COLLISION, + LSTT_COLLISION_END, + LSTT_LAND_COLLISION_START, + LSTT_LAND_COLLISION, + LSTT_LAND_COLLISION_END, + LSTT_TIMER, + LSTT_CHAT, + LSTT_REZ, + LSTT_SENSOR, + LSTT_NO_SENSOR, + LSTT_CONTROL, + LSTT_MONEY, + LSTT_EMAIL, + LSTT_AT_TARGET, + LSTT_NOT_AT_TARGET, + LSTT_AT_ROT_TARGET, + LSTT_NOT_AT_ROT_TARGET, + LSTT_RTPERMISSIONS, + LSTT_INVENTORY, + LSTT_ATTACH, + LSTT_DATASERVER, + LSTT_LINK_MESSAGE, + LSTT_MOVING_START, + LSTT_MOVING_END, + LSTT_OBJECT_REZ, + LSTT_REMOTE_DATA, + LSTT_HTTP_RESPONSE, + LSTT_EOF, + + LSTT_STATE_BEGIN = LSTT_STATE_ENTRY, + LSTT_STATE_END = LSTT_EOF +} LSCRIPTStateEventType; + +const U64 LSCRIPTStateBitField[LSTT_EOF] = +{ + 0x0000000000000000, // LSTT_NULL + 0x0000000000000001, // LSTT_STATE_ENTRY + 0x0000000000000002, // LSTT_STATE_EXIT + 0x0000000000000004, // LSTT_TOUCH_START + 0x0000000000000008, // LSTT_TOUCH + 0x0000000000000010, // LSTT_TOUCH_END + 0x0000000000000020, // LSTT_COLLISION_START + 0x0000000000000040, // LSTT_COLLISION + 0x0000000000000080, // LSTT_COLLISION_END + 0x0000000000000100, // LSTT_LAND_COLLISION_START + 0x0000000000000200, // LSTT_LAND_COLLISION + 0x0000000000000400, // LSTT_LAND_COLLISION_END + 0x0000000000000800, // LSTT_TIMER + 0x0000000000001000, // LSTT_CHAT + 0x0000000000002000, // LSTT_REZ + 0x0000000000004000, // LSTT_SENSOR + 0x0000000000008000, // LSTT_NO_SENSOR + 0x0000000000010000, // LSTT_CONTROL + 0x0000000000020000, // LSTT_MONEY + 0x0000000000040000, // LSTT_EMAIL + 0x0000000000080000, // LSTT_AT_TARGET + 0x0000000000100000, // LSTT_NOT_AT_TARGET + 0x0000000000200000, // LSTT_AT_ROT_TARGET + 0x0000000000400000, // LSTT_NOT_AT_ROT_TARGET + 0x0000000000800000, // LSTT_RTPERMISSIONS + 0x0000000001000000, // LSTT_INVENTORY + 0x0000000002000000, // LSTT_ATTACH + 0x0000000004000000, // LSTT_DATASERVER + 0x0000000008000000, // LSTT_LINK_MESSAGE + 0x0000000010000000, // LSTT_MOVING_START + 0x0000000020000000, // LSTT_MOVING_END + 0x0000000040000000, // LSTT_OBJECT_REZ + 0x0000000080000000, // LSTT_REMOTE_DATA + 0x0000000100000000LL // LSTT_HTTP_RESPOSE +}; + +inline S32 get_event_handler_jump_position(U64 bit_field, LSCRIPTStateEventType type) +{ + S32 count = 0, position = LSTT_STATE_ENTRY; + while (position < type) + { + if (bit_field & 0x1) + { + count++; + } + bit_field >>= 1; + position++; + } + return count; +} + +inline S32 get_number_of_event_handlers(U64 bit_field) +{ + S32 count = 0, position = 0; + while (position < LSTT_EOF) + { + if (bit_field & 0x1) + { + count++; + } + bit_field >>= 1; + position++; + } + return count; +} + +typedef enum e_lscript_types +{ + LST_NULL, + LST_INTEGER, + LST_FLOATINGPOINT, + LST_STRING, + LST_KEY, + LST_VECTOR, + LST_QUATERNION, + LST_LIST, + LST_UNDEFINED, + LST_EOF +} LSCRIPTType; + +const U8 LSCRIPTTypeByte[LST_EOF] = +{ + LST_NULL, + LST_INTEGER, + LST_FLOATINGPOINT, + LST_STRING, + LST_KEY, + LST_VECTOR, + LST_QUATERNION, + LST_LIST, + LST_NULL, +}; + +const U8 LSCRIPTTypeHi4Bits[LST_EOF] = +{ + LST_NULL, + LST_INTEGER << 4, + LST_FLOATINGPOINT << 4, + LST_STRING << 4, + LST_KEY << 4, + LST_VECTOR << 4, + LST_QUATERNION << 4, + LST_LIST << 4, +}; + +const char * const LSCRIPTTypeNames[LST_EOF] = +{ + "VOID", + "integer", + "float", + "string", + "key", + "vector", + "quaternion", + "list", + "invalid" +}; + +const S32 LSCRIPTDataSize[LST_EOF] = +{ + 0, // VOID + 4, // integer + 4, // float + 4, // string + 4, // key + 12, // vector + 16, // quaternion + 4, // list + 0 // invalid +}; + + +typedef enum e_lscript_runtime_faults +{ + LSRF_INVALID, + LSRF_MATH, + LSRF_STACK_HEAP_COLLISION, + LSRF_BOUND_CHECK_ERROR, + LSRF_HEAP_ERROR, + LSRF_VERSION_MISMATCH, + LSRF_MISSING_INVENTORY, + LSRF_SANDBOX, + LSRF_CHAT_OVERRUN, + LSRF_TOO_MANY_LISTENS, + LSRF_NESTING_LISTS, + LSRF_EOF +} LSCRIPTRunTimeFaults; + +extern char *LSCRIPTRunTimeFaultStrings[LSRF_EOF]; + +const S32 LSCRIPTRunTimeFaultBits[LSRF_EOF] = +{ + 0, // LSRF_INVALID + 1, // LSRF_MATH + 2, // LSRF_STACK_HEAP_COLLISION + 3, // LSREF_BOUND_CHECK_ERROR + 4, // LSREF_HEAP_ERROR + 5, // LSREF_VERSION_MISMATCH + 6, // LSREF_MISSING_INVENTORY + 7, // LSRF_SANDBOX + 8, // LSRF_CHAT_OVERRUN + 9, // LSRF_TOO_MANY_LISTENS + 10, // LSRF_NESTING_LISTS +}; + +typedef enum e_lscript_runtime_permissions +{ + SCRIPT_PERMISSION_DEBIT, + SCRIPT_PERMISSION_TAKE_CONTROLS, + SCRIPT_PERMISSION_REMAP_CONTROLS, + SCRIPT_PERMISSION_TRIGGER_ANIMATION, + SCRIPT_PERMISSION_ATTACH, + SCRIPT_PERMISSION_RELEASE_OWNERSHIP, + SCRIPT_PERMISSION_CHANGE_LINKS, + SCRIPT_PERMISSION_CHANGE_JOINTS, + SCRIPT_PERMISSION_CHANGE_PERMISSIONS, + SCRIPT_PERMISSION_TRACK_CAMERA, + SCRIPT_PERMISSION_CONTROL_CAMERA, + SCRIPT_PERMISSION_EOF +} LSCRIPTRunTimePermissions; + +const U32 LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_EOF] = +{ + (0x1 << 1), // SCRIPT_PERMISSION_DEBIT, + (0x1 << 2), // SCRIPT_PERMISSION_TAKE_CONTROLS, + (0x1 << 3), // SCRIPT_PERMISSION_REMAP_CONTROLS, + (0x1 << 4), // SCRIPT_PERMISSION_TRIGGER_ANIMATION, + (0x1 << 5), // SCRIPT_PERMISSION_ATTACH, + (0x1 << 6), // SCRIPT_PERMISSION_RELEASE_OWNERSHIP, + (0x1 << 7), // SCRIPT_PERMISSION_CHANGE_LINKS, + (0x1 << 8), // SCRIPT_PERMISSION_CHANGE_JOINTS, + (0x1 << 9), // SCRIPT_PERMISSION_CHANGE_PERMISSIONS + (0x1 << 10),// SCRIPT_PERMISSION_TRACK_CAMERA + (0x1 << 11),// SCRIPT_PERMISSION_CONTROL_CAMERA +}; + +#endif + diff --git a/indra/lscript/lscript_compile/indra.l b/indra/lscript/lscript_compile/indra.l new file mode 100644 index 0000000000..2ee219a8ee --- /dev/null +++ b/indra/lscript/lscript_compile/indra.l @@ -0,0 +1,834 @@ +D [-]?[0-9] +N [0-9] +L [a-zA-Z_] +H [a-fA-F0-9] +E [Ee][+-]?{D}+ +FS (f|F) +%e 8000 +%n 4000 +%p 5000 + +%{ +#include +#include "stdtypes.h" +#include "llmath.h" +#include "lscript_tree.h" +#include "lscript_typecheck.h" +#include "lscript_resource.h" +#include "llfile.h" +#if LL_WINDOWS +#include "ytab.h" +#else +#include "indra.y.h" +#endif +#include "lltimer.h" +#include "indra_constants.h" +#include "llagentconstants.h" +#include "lllslconstants.h" +#include "lluuid.h" +#include "llassetstorage.h" +#include "llpartdata.h" +#include "llvehicleparams.h" +#include "llpermissionsflags.h" +#include "llfollowcamparams.h" +#include "llparcelflags.h" +#include "llregionflags.h" +#include "lscript_http.h" + +void count(); +void comment(); +void parse_string(); + +#define YYLMAX 16384 +#define YY_NEVER_INTERACTIVE 1 /* stops flex from calling isatty() */ + +#if defined(__cplusplus) +extern "C" { int yylex( void ); } +extern "C" { int yyparse( void ); } +extern "C" { int yyerror(const char *fmt, ...); } +#endif + +%} + +%% +"//" { gInternalLine++; gInternalColumn = 0; comment(); } + +"integer" { count(); return(INTEGER); } +"float" { count(); return(FLOAT_TYPE); } +"string" { count(); return(STRING); } +"key" { count(); return(LLKEY); } +"vector" { count(); return(VECTOR); } +"quaternion" { count(); return(QUATERNION); } +"rotation" { count(); return(QUATERNION); } +"list" { count(); return(LIST); } + +"default" { count(); yylval.sval = new char[strlen(yytext) + 1]; strcpy(yylval.sval, yytext); return(STATE_DEFAULT); } +"state" { count(); return(STATE); } +"event" { count(); return(EVENT); } +"jump" { count(); return(JUMP); } +"return" { count(); return(RETURN); } +"if" { count(); return(IF); } +"else" { count(); return(ELSE); } +"for" { count(); return(FOR); } +"do" { count(); return(DO); } +"while" { count(); return(WHILE); } + +"state_entry" { count(); return(STATE_ENTRY); } +"state_exit" { count(); return(STATE_EXIT); } +"touch_start" { count(); return(TOUCH_START); } +"touch" { count(); return(TOUCH); } +"touch_end" { count(); return(TOUCH_END); } +"collision_start" { count(); return(COLLISION_START); } +"collision" { count(); return(COLLISION); } +"collision_end" { count(); return(COLLISION_END); } +"land_collision_start" { count(); return(LAND_COLLISION_START); } +"land_collision" { count(); return(LAND_COLLISION); } +"land_collision_end" { count(); return(LAND_COLLISION_END); } +"timer" { count(); return(TIMER); } +"listen" { count(); return(CHAT); } +"sensor" { count(); return(SENSOR); } +"no_sensor" { count(); return(NO_SENSOR); } +"control" { count(); return(CONTROL); } +"print" { count(); return(PRINT); } +"at_target" { count(); return(AT_TARGET); } +"not_at_target" { count(); return(NOT_AT_TARGET); } +"at_rot_target" { count(); return(AT_ROT_TARGET); } +"not_at_rot_target" { count(); return(NOT_AT_ROT_TARGET); } +"money" { count(); return(MONEY); } +"email" { count(); return(EMAIL); } +"run_time_permissions" { count(); return(RUN_TIME_PERMISSIONS); } +"changed" { count(); return(INVENTORY); } +"attach" { count(); return(ATTACH); } +"dataserver" { count(); return(DATASERVER); } +"moving_start" { count(); return(MOVING_START); } +"moving_end" { count(); return(MOVING_END); } +"link_message" { count(); return(LINK_MESSAGE); } +"on_rez" { count(); return(REZ); } +"object_rez" { count(); return(OBJECT_REZ); } +"remote_data" { count(); return(REMOTE_DATA); } +"http_response" { count(); return(HTTP_RESPONSE); } +"." { count(); return(PERIOD); } + + +0[xX]{H}+ { count(); yylval.ival = strtoul(yytext, NULL, 0); return(INTEGER_CONSTANT); } +{D}+ { count(); yylval.ival = strtoul(yytext, NULL, 10); return(INTEGER_CONSTANT); } +"TRUE" { count(); yylval.ival = 1; return(INTEGER_TRUE); } +"FALSE" { count(); yylval.ival = 0; return(INTEGER_FALSE); } +"STATUS_PHYSICS" { count(); yylval.ival = 0x1; return(INTEGER_CONSTANT); } +"STATUS_ROTATE_X" { count(); yylval.ival = 0x2; return(INTEGER_CONSTANT); } +"STATUS_ROTATE_Y" { count(); yylval.ival = 0x4; return(INTEGER_CONSTANT); } +"STATUS_ROTATE_Z" { count(); yylval.ival = 0x8; return(INTEGER_CONSTANT); } +"STATUS_PHANTOM" { count(); yylval.ival = 0x10; return(INTEGER_CONSTANT); } +"STATUS_SANDBOX" { count(); yylval.ival = 0x20; return(INTEGER_CONSTANT); } +"STATUS_BLOCK_GRAB" { count(); yylval.ival = 0x40; return(INTEGER_CONSTANT); } +"STATUS_DIE_AT_EDGE" { count(); yylval.ival = 0x80; return(INTEGER_CONSTANT); } +"STATUS_RETURN_AT_EDGE" { count(); yylval.ival = 0x100; return(INTEGER_CONSTANT); } +"STATUS_CAST_SHADOWS" { count(); yylval.ival = 0x200; return(INTEGER_CONSTANT); } + +"AGENT_FLYING" { count(); yylval.ival = AGENT_FLYING; return(INTEGER_CONSTANT); } +"AGENT_ATTACHMENTS" { count(); yylval.ival = AGENT_ATTACHMENTS; return(INTEGER_CONSTANT); } +"AGENT_SCRIPTED" { count(); yylval.ival = AGENT_SCRIPTED; return(INTEGER_CONSTANT); } +"AGENT_MOUSELOOK" { count(); yylval.ival = AGENT_MOUSELOOK; return(INTEGER_CONSTANT); } +"AGENT_SITTING" { count(); yylval.ival = AGENT_SITTING; return(INTEGER_CONSTANT); } +"AGENT_ON_OBJECT" { count(); yylval.ival = AGENT_ON_OBJECT; return(INTEGER_CONSTANT); } +"AGENT_AWAY" { count(); yylval.ival = AGENT_AWAY; return(INTEGER_CONSTANT); } +"AGENT_WALKING" { count(); yylval.ival = AGENT_WALKING; return(INTEGER_CONSTANT); } +"AGENT_IN_AIR" { count(); yylval.ival = AGENT_IN_AIR; return(INTEGER_CONSTANT); } +"AGENT_TYPING" { count(); yylval.ival = AGENT_TYPING; return(INTEGER_CONSTANT); } +"AGENT_CROUCHING" { count(); yylval.ival = AGENT_CROUCHING; return(INTEGER_CONSTANT); } +"AGENT_BUSY" { count(); yylval.ival = AGENT_BUSY; return(INTEGER_CONSTANT); } +"AGENT_ALWAYS_RUN" { count(); yylval.ival = AGENT_ALWAYS_RUN; return(INTEGER_CONSTANT); } + +"CAMERA_PITCH" { count(); yylval.ival = FOLLOWCAM_PITCH; return(INTEGER_CONSTANT); } +"CAMERA_FOCUS_OFFSET" { count(); yylval.ival = FOLLOWCAM_FOCUS_OFFSET; return (INTEGER_CONSTANT); } +"CAMERA_POSITION_LAG" { count(); yylval.ival = FOLLOWCAM_POSITION_LAG; return (INTEGER_CONSTANT); } +"CAMERA_FOCUS_LAG" { count(); yylval.ival = FOLLOWCAM_FOCUS_LAG; return (INTEGER_CONSTANT); } +"CAMERA_DISTANCE" { count(); yylval.ival = FOLLOWCAM_DISTANCE; return (INTEGER_CONSTANT); } +"CAMERA_BEHINDNESS_ANGLE" { count(); yylval.ival = FOLLOWCAM_BEHINDNESS_ANGLE; return (INTEGER_CONSTANT); } +"CAMERA_BEHINDNESS_LAG" { count(); yylval.ival = FOLLOWCAM_BEHINDNESS_LAG; return (INTEGER_CONSTANT); } +"CAMERA_POSITION_THRESHOLD" { count(); yylval.ival = FOLLOWCAM_POSITION_THRESHOLD; return (INTEGER_CONSTANT); } +"CAMERA_FOCUS_THRESHOLD" { count(); yylval.ival = FOLLOWCAM_FOCUS_THRESHOLD; return (INTEGER_CONSTANT); } +"CAMERA_ACTIVE" { count(); yylval.ival = FOLLOWCAM_ACTIVE; return (INTEGER_CONSTANT); } +"CAMERA_POSITION" { count(); yylval.ival = FOLLOWCAM_POSITION; return (INTEGER_CONSTANT); } +"CAMERA_FOCUS" { count(); yylval.ival = FOLLOWCAM_FOCUS; return (INTEGER_CONSTANT); } +"CAMERA_POSITION_LOCKED" { count(); yylval.ival = FOLLOWCAM_POSITION_LOCKED; return (INTEGER_CONSTANT); } +"CAMERA_FOCUS_LOCKED" { count(); yylval.ival = FOLLOWCAM_FOCUS_LOCKED; return (INTEGER_CONSTANT); } + +"ANIM_ON" { count(); yylval.ival = 0x1; return(INTEGER_CONSTANT); } +"LOOP" { count(); yylval.ival = 0x2; return(INTEGER_CONSTANT); } +"REVERSE" { count(); yylval.ival = 0x4; return(INTEGER_CONSTANT); } +"PING_PONG" { count(); yylval.ival = 0x8; return(INTEGER_CONSTANT); } +"SMOOTH" { count(); yylval.ival = 0x10; return(INTEGER_CONSTANT); } +"ROTATE" { count(); yylval.ival = 0x20; return(INTEGER_CONSTANT); } +"SCALE" { count(); yylval.ival = 0x40; return(INTEGER_CONSTANT); } + +"ALL_SIDES" { count(); yylval.ival = LSL_ALL_SIDES; return(INTEGER_CONSTANT); } +"LINK_ROOT" { count(); yylval.ival = LSL_LINK_ROOT; return(INTEGER_CONSTANT); } +"LINK_SET" { count(); yylval.ival = LSL_LINK_SET; return(INTEGER_CONSTANT); } +"LINK_ALL_OTHERS" { count(); yylval.ival = LSL_LINK_ALL_OTHERS; return(INTEGER_CONSTANT); } +"LINK_ALL_CHILDREN" { count(); yylval.ival = LSL_LINK_ALL_CHILDREN; return(INTEGER_CONSTANT); } +"LINK_THIS" { count(); yylval.ival = LSL_LINK_THIS; return(INTEGER_CONSTANT); } + +"AGENT" { count(); yylval.ival = 0x1; return(INTEGER_CONSTANT); } +"ACTIVE" { count(); yylval.ival = 0x2; return(INTEGER_CONSTANT); } +"PASSIVE" { count(); yylval.ival = 0x4; return(INTEGER_CONSTANT); } +"SCRIPTED" { count(); yylval.ival = 0x8; return(INTEGER_CONSTANT); } + +"CONTROL_FWD" { count(); yylval.ival = AGENT_CONTROL_AT_POS; return(INTEGER_CONSTANT); } +"CONTROL_BACK" { count(); yylval.ival = AGENT_CONTROL_AT_NEG; return(INTEGER_CONSTANT); } +"CONTROL_LEFT" { count(); yylval.ival = AGENT_CONTROL_LEFT_POS; return(INTEGER_CONSTANT); } +"CONTROL_RIGHT" { count(); yylval.ival = AGENT_CONTROL_LEFT_NEG; return(INTEGER_CONSTANT); } +"CONTROL_ROT_LEFT" { count(); yylval.ival = AGENT_CONTROL_YAW_POS; return(INTEGER_CONSTANT); } +"CONTROL_ROT_RIGHT" { count(); yylval.ival = AGENT_CONTROL_YAW_NEG; return(INTEGER_CONSTANT); } +"CONTROL_UP" { count(); yylval.ival = AGENT_CONTROL_UP_POS; return(INTEGER_CONSTANT); } +"CONTROL_DOWN" { count(); yylval.ival = AGENT_CONTROL_UP_NEG; return(INTEGER_CONSTANT); } +"CONTROL_LBUTTON" { count(); yylval.ival = AGENT_CONTROL_LBUTTON_DOWN; return(INTEGER_CONSTANT); } +"CONTROL_ML_LBUTTON" { count(); yylval.ival = AGENT_CONTROL_ML_LBUTTON_DOWN; return(INTEGER_CONSTANT); } + +"PERMISSION_DEBIT" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_DEBIT]; return(INTEGER_CONSTANT); } +"PERMISSION_TAKE_CONTROLS" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_TAKE_CONTROLS]; return(INTEGER_CONSTANT); } +"PERMISSION_REMAP_CONTROLS" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_REMAP_CONTROLS]; return(INTEGER_CONSTANT); } +"PERMISSION_TRIGGER_ANIMATION" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_TRIGGER_ANIMATION]; return(INTEGER_CONSTANT); } +"PERMISSION_ATTACH" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_ATTACH]; return(INTEGER_CONSTANT); } +"PERMISSION_RELEASE_OWNERSHIP" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_RELEASE_OWNERSHIP]; return(INTEGER_CONSTANT); } +"PERMISSION_CHANGE_LINKS" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_CHANGE_LINKS]; return(INTEGER_CONSTANT); } +"PERMISSION_CHANGE_JOINTS" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_CHANGE_JOINTS]; return(INTEGER_CONSTANT); } +"PERMISSION_CHANGE_PERMISSIONS" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_CHANGE_PERMISSIONS]; return(INTEGER_CONSTANT); } +"PERMISSION_TRACK_CAMERA" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_TRACK_CAMERA]; return(INTEGER_CONSTANT); } +"PERMISSION_CONTROL_CAMERA" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_CONTROL_CAMERA]; return(INTEGER_CONSTANT); } + +"INVENTORY_TEXTURE" { count(); yylval.ival = LLAssetType::AT_TEXTURE; return(INTEGER_CONSTANT); } +"INVENTORY_SOUND" { count(); yylval.ival = LLAssetType::AT_SOUND; return(INTEGER_CONSTANT); } +"INVENTORY_OBJECT" { count(); yylval.ival = LLAssetType::AT_OBJECT; return(INTEGER_CONSTANT); } +"INVENTORY_SCRIPT" { count(); yylval.ival = LLAssetType::AT_LSL_TEXT; return(INTEGER_CONSTANT); } +"INVENTORY_LANDMARK" { count(); yylval.ival = LLAssetType::AT_LANDMARK; return(INTEGER_CONSTANT); } +"INVENTORY_CLOTHING" { count(); yylval.ival = LLAssetType::AT_CLOTHING; return(INTEGER_CONSTANT); } +"INVENTORY_NOTECARD" { count(); yylval.ival = LLAssetType::AT_NOTECARD; return(INTEGER_CONSTANT); } +"INVENTORY_BODYPART" { count(); yylval.ival = LLAssetType::AT_BODYPART; return(INTEGER_CONSTANT); } +"INVENTORY_ANIMATION" { count(); yylval.ival = LLAssetType::AT_ANIMATION; return(INTEGER_CONSTANT); } +"INVENTORY_GESTURE" { count(); yylval.ival = LLAssetType::AT_GESTURE; return(INTEGER_CONSTANT); } +"INVENTORY_ALL" { count(); yylval.ival = LLAssetType::AT_NONE; return(INTEGER_CONSTANT); } +"INVENTORY_NONE" { count(); yylval.ival = LLAssetType::AT_NONE; return(INTEGER_CONSTANT); } + +"CHANGED_INVENTORY" { count(); yylval.ival = 0x1; return(INTEGER_CONSTANT); } +"CHANGED_COLOR" { count(); yylval.ival = 0x2; return(INTEGER_CONSTANT); } +"CHANGED_SHAPE" { count(); yylval.ival = 0x4; return(INTEGER_CONSTANT); } +"CHANGED_SCALE" { count(); yylval.ival = 0x8; return(INTEGER_CONSTANT); } +"CHANGED_TEXTURE" { count(); yylval.ival = 0x10; return(INTEGER_CONSTANT); } +"CHANGED_LINK" { count(); yylval.ival = 0x20; return(INTEGER_CONSTANT); } +"CHANGED_ALLOWED_DROP" { count(); yylval.ival = 0x40; return(INTEGER_CONSTANT); } +"CHANGED_OWNER" { count(); yylval.ival = 0x80; return(INTEGER_CONSTANT); } +"CHANGED_REGION" { count(); yylval.ival = 0x100; return(INTEGER_CONSTANT); } +"CHANGED_TELEPORT" { count(); yylval.ival = 0x200; return(INTEGER_CONSTANT); } + +"TYPE_INTEGER" { count(); yylval.ival = LST_INTEGER; return(INTEGER_CONSTANT); } +"TYPE_FLOAT" { count(); yylval.ival = LST_FLOATINGPOINT; return(INTEGER_CONSTANT); } +"TYPE_STRING" { count(); yylval.ival = LST_STRING; return(INTEGER_CONSTANT); } +"TYPE_KEY" { count(); yylval.ival = LST_KEY; return(INTEGER_CONSTANT); } +"TYPE_VECTOR" { count(); yylval.ival = LST_VECTOR; return(INTEGER_CONSTANT); } +"TYPE_ROTATION" { count(); yylval.ival = LST_QUATERNION; return(INTEGER_CONSTANT); } +"TYPE_INVALID" { count(); yylval.ival = LST_NULL; return(INTEGER_CONSTANT); } + +"NULL_KEY" { yylval.sval = new char[UUID_STR_LENGTH]; strcpy(yylval.sval, "00000000-0000-0000-0000-000000000000"); return(STRING_CONSTANT); } +"EOF" { yylval.sval = new char[UUID_STR_LENGTH]; strcpy(yylval.sval, "\n\n\n"); return(STRING_CONSTANT); } + +"PI" { count(); yylval.fval = F_PI; return(FP_CONSTANT); } +"TWO_PI" { count(); yylval.fval = F_TWO_PI; return(FP_CONSTANT); } +"PI_BY_TWO" { count(); yylval.fval = F_PI_BY_TWO; return(FP_CONSTANT); } +"DEG_TO_RAD" { count(); yylval.fval = DEG_TO_RAD; return(FP_CONSTANT); } +"RAD_TO_DEG" { count(); yylval.fval = RAD_TO_DEG; return(FP_CONSTANT); } +"SQRT2" { count(); yylval.fval = F_SQRT2; return(FP_CONSTANT); } + +"DEBUG_CHANNEL" { count(); yylval.ival = CHAT_CHANNEL_DEBUG; return(INTEGER_CONSTANT); } +"PUBLIC_CHANNEL" { count(); yylval.ival = 0; return(INTEGER_CONSTANT); } + +"ZERO_VECTOR" { count(); return(ZERO_VECTOR); } +"ZERO_ROTATION" { count(); return(ZERO_ROTATION); } + +"ATTACH_CHEST" { count(); yylval.ival = 1; return(INTEGER_CONSTANT); } +"ATTACH_HEAD" { count(); yylval.ival = 2; return(INTEGER_CONSTANT); } +"ATTACH_LSHOULDER" { count(); yylval.ival = 3; return(INTEGER_CONSTANT); } +"ATTACH_RSHOULDER" { count(); yylval.ival = 4; return(INTEGER_CONSTANT); } +"ATTACH_LHAND" { count(); yylval.ival = 5; return(INTEGER_CONSTANT); } +"ATTACH_RHAND" { count(); yylval.ival = 6; return(INTEGER_CONSTANT); } +"ATTACH_LFOOT" { count(); yylval.ival = 7; return(INTEGER_CONSTANT); } +"ATTACH_RFOOT" { count(); yylval.ival = 8; return(INTEGER_CONSTANT); } +"ATTACH_BACK" { count(); yylval.ival = 9; return(INTEGER_CONSTANT); } +"ATTACH_PELVIS" { count(); yylval.ival = 10; return(INTEGER_CONSTANT); } +"ATTACH_MOUTH" { count(); yylval.ival = 11; return(INTEGER_CONSTANT); } +"ATTACH_CHIN" { count(); yylval.ival = 12; return(INTEGER_CONSTANT); } +"ATTACH_LEAR" { count(); yylval.ival = 13; return(INTEGER_CONSTANT); } +"ATTACH_REAR" { count(); yylval.ival = 14; return(INTEGER_CONSTANT); } +"ATTACH_LEYE" { count(); yylval.ival = 15; return(INTEGER_CONSTANT); } +"ATTACH_REYE" { count(); yylval.ival = 16; return(INTEGER_CONSTANT); } +"ATTACH_NOSE" { count(); yylval.ival = 17; return(INTEGER_CONSTANT); } +"ATTACH_RUARM" { count(); yylval.ival = 18; return(INTEGER_CONSTANT); } +"ATTACH_RLARM" { count(); yylval.ival = 19; return(INTEGER_CONSTANT); } +"ATTACH_LUARM" { count(); yylval.ival = 20; return(INTEGER_CONSTANT); } +"ATTACH_LLARM" { count(); yylval.ival = 21; return(INTEGER_CONSTANT); } +"ATTACH_RHIP" { count(); yylval.ival = 22; return(INTEGER_CONSTANT); } +"ATTACH_RULEG" { count(); yylval.ival = 23; return(INTEGER_CONSTANT); } +"ATTACH_RLLEG" { count(); yylval.ival = 24; return(INTEGER_CONSTANT); } +"ATTACH_LHIP" { count(); yylval.ival = 25; return(INTEGER_CONSTANT); } +"ATTACH_LULEG" { count(); yylval.ival = 26; return(INTEGER_CONSTANT); } +"ATTACH_LLLEG" { count(); yylval.ival = 27; return(INTEGER_CONSTANT); } +"ATTACH_BELLY" { count(); yylval.ival = 28; return(INTEGER_CONSTANT); } +"ATTACH_RPEC" { count(); yylval.ival = 29; return(INTEGER_CONSTANT); } +"ATTACH_LPEC" { count(); yylval.ival = 30; return(INTEGER_CONSTANT); } +"ATTACH_HUD_CENTER_2" { count(); yylval.ival = 31; return(INTEGER_CONSTANT); } +"ATTACH_HUD_TOP_RIGHT" { count(); yylval.ival = 32; return(INTEGER_CONSTANT); } +"ATTACH_HUD_TOP_CENTER" { count(); yylval.ival = 33; return(INTEGER_CONSTANT); } +"ATTACH_HUD_TOP_LEFT" { count(); yylval.ival = 34; return(INTEGER_CONSTANT); } +"ATTACH_HUD_CENTER_1" { count(); yylval.ival = 35; return(INTEGER_CONSTANT); } +"ATTACH_HUD_BOTTOM_LEFT" { count(); yylval.ival = 36; return(INTEGER_CONSTANT); } +"ATTACH_HUD_BOTTOM" { count(); yylval.ival = 37; return(INTEGER_CONSTANT); } +"ATTACH_HUD_BOTTOM_RIGHT" { count(); yylval.ival = 38; return(INTEGER_CONSTANT); } + +"LAND_LEVEL" { count(); yylval.ival = E_LANDBRUSH_LEVEL; return(INTEGER_CONSTANT); } +"LAND_RAISE" { count(); yylval.ival = E_LANDBRUSH_RAISE; return(INTEGER_CONSTANT); } +"LAND_LOWER" { count(); yylval.ival = E_LANDBRUSH_LOWER; return(INTEGER_CONSTANT); } +"LAND_SMOOTH" { count(); yylval.ival = E_LANDBRUSH_SMOOTH; return(INTEGER_CONSTANT); } +"LAND_NOISE" { count(); yylval.ival = E_LANDBRUSH_NOISE; return(INTEGER_CONSTANT); } +"LAND_REVERT" { count(); yylval.ival = E_LANDBRUSH_REVERT; return(INTEGER_CONSTANT); } + +"LAND_SMALL_BRUSH" { count(); yylval.ival = 1; return(INTEGER_CONSTANT); } +"LAND_MEDIUM_BRUSH" { count(); yylval.ival = 2; return(INTEGER_CONSTANT); } +"LAND_LARGE_BRUSH" { count(); yylval.ival = 3; return(INTEGER_CONSTANT); } + +"DATA_ONLINE" { count(); yylval.ival = 1; return(INTEGER_CONSTANT); } +"DATA_NAME" { count(); yylval.ival = 2; return(INTEGER_CONSTANT); } +"DATA_BORN" { count(); yylval.ival = 3; return(INTEGER_CONSTANT); } +"DATA_RATING" { count(); yylval.ival = 4; return(INTEGER_CONSTANT); } +"DATA_SIM_POS" { count(); yylval.ival = 5; return(INTEGER_CONSTANT); } +"DATA_SIM_STATUS" { count(); yylval.ival = 6; return(INTEGER_CONSTANT); } +"DATA_SIM_RATING" { count(); yylval.ival = 7; return(INTEGER_CONSTANT); } +"DATA_PAYINFO" { count(); yylval.ival = 8; return(INTEGER_CONSTANT); } + +"PAYMENT_INFO_ON_FILE" { count(); yylval.ival = 1; return(INTEGER_CONSTANT); } +"PAYMENT_INFO_USED" { count(); yylval.ival = 2; return(INTEGER_CONSTANT); } + +"REMOTE_DATA_CHANNEL" { count(); yylval.ival = LSL_REMOTE_DATA_CHANNEL; return(INTEGER_CONSTANT); } +"REMOTE_DATA_REQUEST" { count(); yylval.ival = LSL_REMOTE_DATA_REQUEST; return(INTEGER_CONSTANT); } +"REMOTE_DATA_REPLY" { count(); yylval.ival = LSL_REMOTE_DATA_REPLY; return(INTEGER_CONSTANT); } + + +"PSYS_PART_FLAGS" { count(); yylval.ival = LLPS_PART_FLAGS; return(INTEGER_CONSTANT); } +"PSYS_PART_START_COLOR" { count(); yylval.ival = LLPS_PART_START_COLOR; return (INTEGER_CONSTANT); } +"PSYS_PART_START_ALPHA" { count(); yylval.ival = LLPS_PART_START_ALPHA; return (INTEGER_CONSTANT); } +"PSYS_PART_START_SCALE" { count(); yylval.ival = LLPS_PART_START_SCALE; return (INTEGER_CONSTANT); } +"PSYS_PART_END_COLOR" { count(); yylval.ival = LLPS_PART_END_COLOR; return (INTEGER_CONSTANT); } +"PSYS_PART_END_ALPHA" { count(); yylval.ival = LLPS_PART_END_ALPHA; return (INTEGER_CONSTANT); } +"PSYS_PART_END_SCALE" { count(); yylval.ival = LLPS_PART_END_SCALE; return (INTEGER_CONSTANT); } +"PSYS_PART_MAX_AGE" { count(); yylval.ival = LLPS_PART_MAX_AGE; return (INTEGER_CONSTANT); } + + +"PSYS_PART_WIND_MASK" { count(); yylval.ival = LLPartData::LL_PART_WIND_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_INTERP_COLOR_MASK" { count(); yylval.ival = LLPartData::LL_PART_INTERP_COLOR_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_INTERP_SCALE_MASK" { count(); yylval.ival = LLPartData::LL_PART_INTERP_SCALE_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_BOUNCE_MASK" { count(); yylval.ival = LLPartData::LL_PART_BOUNCE_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_FOLLOW_SRC_MASK" { count(); yylval.ival = LLPartData::LL_PART_FOLLOW_SRC_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_FOLLOW_VELOCITY_MASK" { count(); yylval.ival = LLPartData::LL_PART_FOLLOW_VELOCITY_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_TARGET_POS_MASK" { count(); yylval.ival = LLPartData::LL_PART_TARGET_POS_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_EMISSIVE_MASK" { count(); yylval.ival = LLPartData::LL_PART_EMISSIVE_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_TARGET_LINEAR_MASK" { count(); yylval.ival = LLPartData::LL_PART_TARGET_LINEAR_MASK; return(INTEGER_CONSTANT); } + + +"PSYS_SRC_MAX_AGE" { count(); yylval.ival = LLPS_SRC_MAX_AGE; return(INTEGER_CONSTANT); } +"PSYS_SRC_PATTERN" { count(); yylval.ival = LLPS_SRC_PATTERN; return(INTEGER_CONSTANT); } +"PSYS_SRC_INNERANGLE" { count(); yylval.ival = LLPS_SRC_INNERANGLE; return(INTEGER_CONSTANT); } +"PSYS_SRC_OUTERANGLE" { count(); yylval.ival = LLPS_SRC_OUTERANGLE; return(INTEGER_CONSTANT); } +"PSYS_SRC_ANGLE_BEGIN" { count(); yylval.ival = LLPS_SRC_ANGLE_BEGIN; return(INTEGER_CONSTANT); } +"PSYS_SRC_ANGLE_END" { count(); yylval.ival = LLPS_SRC_ANGLE_END; return(INTEGER_CONSTANT); } +"PSYS_SRC_BURST_RATE" { count(); yylval.ival = LLPS_SRC_BURST_RATE; return(INTEGER_CONSTANT); } +"PSYS_SRC_BURST_PART_COUNT" { count(); yylval.ival = LLPS_SRC_BURST_PART_COUNT; return(INTEGER_CONSTANT); } +"PSYS_SRC_BURST_RADIUS" { count(); yylval.ival = LLPS_SRC_BURST_RADIUS; return(INTEGER_CONSTANT); } +"PSYS_SRC_BURST_SPEED_MIN" { count(); yylval.ival = LLPS_SRC_BURST_SPEED_MIN; return(INTEGER_CONSTANT); } +"PSYS_SRC_BURST_SPEED_MAX" { count(); yylval.ival = LLPS_SRC_BURST_SPEED_MAX; return(INTEGER_CONSTANT); } +"PSYS_SRC_ACCEL" { count(); yylval.ival = LLPS_SRC_ACCEL; return(INTEGER_CONSTANT); } +"PSYS_SRC_TEXTURE" { count(); yylval.ival = LLPS_SRC_TEXTURE; return(INTEGER_CONSTANT); } +"PSYS_SRC_TARGET_KEY" { count(); yylval.ival = LLPS_SRC_TARGET_UUID; return(INTEGER_CONSTANT); } +"PSYS_SRC_OMEGA" { count(); yylval.ival = LLPS_SRC_OMEGA; return(INTEGER_CONSTANT); } + +"PSYS_SRC_OBJ_REL_MASK" { count(); yylval.ival = LLPartSysData::LL_PART_SRC_OBJ_REL_MASK; return(INTEGER_CONSTANT); } + +"PSYS_SRC_PATTERN_DROP" { count(); yylval.ival = LLPartSysData::LL_PART_SRC_PATTERN_DROP; return(INTEGER_CONSTANT); } +"PSYS_SRC_PATTERN_EXPLODE" { count(); yylval.ival = LLPartSysData::LL_PART_SRC_PATTERN_EXPLODE; return(INTEGER_CONSTANT); } +"PSYS_SRC_PATTERN_ANGLE" { count(); yylval.ival = LLPartSysData::LL_PART_SRC_PATTERN_ANGLE; return(INTEGER_CONSTANT); } +"PSYS_SRC_PATTERN_ANGLE_CONE" { count(); yylval.ival = LLPartSysData::LL_PART_SRC_PATTERN_ANGLE_CONE; return(INTEGER_CONSTANT); } +"PSYS_SRC_PATTERN_ANGLE_CONE_EMPTY" { count(); yylval.ival = LLPartSysData::LL_PART_SRC_PATTERN_ANGLE_CONE_EMPTY; return(INTEGER_CONSTANT); } + + +"VEHICLE_TYPE_NONE" { count(); yylval.ival = VEHICLE_TYPE_NONE; return(INTEGER_CONSTANT); } +"VEHICLE_TYPE_SLED" { count(); yylval.ival = VEHICLE_TYPE_SLED; return(INTEGER_CONSTANT); } +"VEHICLE_TYPE_CAR" { count(); yylval.ival = VEHICLE_TYPE_CAR; return(INTEGER_CONSTANT); } +"VEHICLE_TYPE_BOAT" { count(); yylval.ival = VEHICLE_TYPE_BOAT; return(INTEGER_CONSTANT); } +"VEHICLE_TYPE_AIRPLANE" { count(); yylval.ival = VEHICLE_TYPE_AIRPLANE; return(INTEGER_CONSTANT); } +"VEHICLE_TYPE_BALLOON" { count(); yylval.ival = VEHICLE_TYPE_BALLOON; return(INTEGER_CONSTANT); } + +"VEHICLE_REFERENCE_FRAME" { count(); yylval.ival = VEHICLE_REFERENCE_FRAME; return(INTEGER_CONSTANT); } +"VEHICLE_LINEAR_FRICTION_TIMESCALE" { count(); yylval.ival = VEHICLE_LINEAR_FRICTION_TIMESCALE; return(INTEGER_CONSTANT); } +"VEHICLE_ANGULAR_FRICTION_TIMESCALE" { count(); yylval.ival = VEHICLE_ANGULAR_FRICTION_TIMESCALE; return(INTEGER_CONSTANT); } +"VEHICLE_LINEAR_MOTOR_DIRECTION" { count(); yylval.ival = VEHICLE_LINEAR_MOTOR_DIRECTION; return(INTEGER_CONSTANT); } +"VEHICLE_ANGULAR_MOTOR_DIRECTION" { count(); yylval.ival = VEHICLE_ANGULAR_MOTOR_DIRECTION; return(INTEGER_CONSTANT); } +"VEHICLE_LINEAR_MOTOR_OFFSET" { count(); yylval.ival = VEHICLE_LINEAR_MOTOR_OFFSET; return(INTEGER_CONSTANT); } + + + +"VEHICLE_HOVER_HEIGHT" { count(); yylval.ival = VEHICLE_HOVER_HEIGHT; return(INTEGER_CONSTANT); } +"VEHICLE_HOVER_EFFICIENCY" { count(); yylval.ival = VEHICLE_HOVER_EFFICIENCY; return(INTEGER_CONSTANT); } +"VEHICLE_HOVER_TIMESCALE" { count(); yylval.ival = VEHICLE_HOVER_TIMESCALE; return(INTEGER_CONSTANT); } +"VEHICLE_BUOYANCY" { count(); yylval.ival = VEHICLE_BUOYANCY; return(INTEGER_CONSTANT); } + +"VEHICLE_LINEAR_DEFLECTION_EFFICIENCY" { count(); yylval.ival = VEHICLE_LINEAR_DEFLECTION_EFFICIENCY; return(INTEGER_CONSTANT); } +"VEHICLE_LINEAR_DEFLECTION_TIMESCALE" { count(); yylval.ival = VEHICLE_LINEAR_DEFLECTION_TIMESCALE; return(INTEGER_CONSTANT); } +"VEHICLE_LINEAR_MOTOR_TIMESCALE" { count(); yylval.ival = VEHICLE_LINEAR_MOTOR_TIMESCALE; return(INTEGER_CONSTANT); } +"VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE" { count(); yylval.ival = VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE; return(INTEGER_CONSTANT); } + +"VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY" { count(); yylval.ival = VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY; return(INTEGER_CONSTANT); } +"VEHICLE_ANGULAR_DEFLECTION_TIMESCALE" { count(); yylval.ival = VEHICLE_ANGULAR_DEFLECTION_TIMESCALE; return(INTEGER_CONSTANT); } +"VEHICLE_ANGULAR_MOTOR_TIMESCALE" { count(); yylval.ival = VEHICLE_ANGULAR_MOTOR_TIMESCALE; return(INTEGER_CONSTANT); } +"VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE" { count(); yylval.ival = VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE; return(INTEGER_CONSTANT); } + +"VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY" { count(); yylval.ival = VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY; return(INTEGER_CONSTANT); } +"VEHICLE_VERTICAL_ATTRACTION_TIMESCALE" { count(); yylval.ival = VEHICLE_VERTICAL_ATTRACTION_TIMESCALE; return(INTEGER_CONSTANT); } + +"VEHICLE_BANKING_EFFICIENCY" { count(); yylval.ival = VEHICLE_BANKING_EFFICIENCY; return(INTEGER_CONSTANT); } +"VEHICLE_BANKING_MIX" { count(); yylval.ival = VEHICLE_BANKING_MIX; return(INTEGER_CONSTANT); } +"VEHICLE_BANKING_TIMESCALE" { count(); yylval.ival = VEHICLE_BANKING_TIMESCALE; return(INTEGER_CONSTANT); } + +"VEHICLE_FLAG_NO_FLY_UP" { count(); yylval.ival = VEHICLE_FLAG_NO_DEFLECTION_UP; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_NO_DEFLECTION_UP" { count(); yylval.ival = VEHICLE_FLAG_NO_DEFLECTION_UP; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_LIMIT_ROLL_ONLY" { count(); yylval.ival = VEHICLE_FLAG_LIMIT_ROLL_ONLY; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_HOVER_WATER_ONLY" { count(); yylval.ival = VEHICLE_FLAG_HOVER_WATER_ONLY; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_HOVER_TERRAIN_ONLY" { count(); yylval.ival = VEHICLE_FLAG_HOVER_TERRAIN_ONLY; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_HOVER_GLOBAL_HEIGHT" { count(); yylval.ival = VEHICLE_FLAG_HOVER_GLOBAL_HEIGHT; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_HOVER_UP_ONLY" { count(); yylval.ival = VEHICLE_FLAG_HOVER_UP_ONLY; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_LIMIT_MOTOR_UP" { count(); yylval.ival = VEHICLE_FLAG_LIMIT_MOTOR_UP; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_MOUSELOOK_STEER" { count(); yylval.ival = VEHICLE_FLAG_MOUSELOOK_STEER; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_MOUSELOOK_BANK" { count(); yylval.ival = VEHICLE_FLAG_MOUSELOOK_BANK; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_CAMERA_DECOUPLED" { count(); yylval.ival = VEHICLE_FLAG_CAMERA_DECOUPLED; return(INTEGER_CONSTANT); } + + + +"PRIM_TYPE" { count(); yylval.ival = LSL_PRIM_TYPE; return(INTEGER_CONSTANT); } +"PRIM_MATERIAL" { count(); yylval.ival = LSL_PRIM_MATERIAL; return(INTEGER_CONSTANT); } +"PRIM_PHYSICS" { count(); yylval.ival = LSL_PRIM_PHYSICS; return(INTEGER_CONSTANT); } +"PRIM_FLEXIBLE" { count(); yylval.ival = LSL_PRIM_FLEXIBLE; return(INTEGER_CONSTANT); } +"PRIM_POINT_LIGHT" { count(); yylval.ival = LSL_PRIM_POINT_LIGHT; return(INTEGER_CONSTANT); } +"PRIM_TEMP_ON_REZ" { count(); yylval.ival = LSL_PRIM_TEMP_ON_REZ; return(INTEGER_CONSTANT); } +"PRIM_PHANTOM" { count(); yylval.ival = LSL_PRIM_PHANTOM; return(INTEGER_CONSTANT); } +"PRIM_CAST_SHADOWS" { count(); yylval.ival = LSL_PRIM_CAST_SHADOWS; return(INTEGER_CONSTANT); } +"PRIM_POSITION" { count(); yylval.ival = LSL_PRIM_POSITION; return(INTEGER_CONSTANT); } +"PRIM_SIZE" { count(); yylval.ival = LSL_PRIM_SIZE; return(INTEGER_CONSTANT); } +"PRIM_ROTATION" { count(); yylval.ival = LSL_PRIM_ROTATION; return(INTEGER_CONSTANT); } +"PRIM_TEXTURE" { count(); yylval.ival = LSL_PRIM_TEXTURE; return(INTEGER_CONSTANT); } +"PRIM_COLOR" { count(); yylval.ival = LSL_PRIM_COLOR; return(INTEGER_CONSTANT); } +"PRIM_BUMP_SHINY" { count(); yylval.ival = LSL_PRIM_BUMP_SHINY; return(INTEGER_CONSTANT); } +"PRIM_FULLBRIGHT" { count(); yylval.ival = LSL_PRIM_FULLBRIGHT; return(INTEGER_CONSTANT); } +"PRIM_TEXGEN" { count(); yylval.ival = LSL_PRIM_TEXGEN; return(INTEGER_CONSTANT); } + +"PRIM_TYPE_BOX" { count(); yylval.ival = LSL_PRIM_TYPE_BOX; return(INTEGER_CONSTANT); } +"PRIM_TYPE_CYLINDER" { count(); yylval.ival = LSL_PRIM_TYPE_CYLINDER; return(INTEGER_CONSTANT); } +"PRIM_TYPE_PRISM" { count(); yylval.ival = LSL_PRIM_TYPE_PRISM; return(INTEGER_CONSTANT); } +"PRIM_TYPE_SPHERE" { count(); yylval.ival = LSL_PRIM_TYPE_SPHERE; return(INTEGER_CONSTANT); } +"PRIM_TYPE_TORUS" { count(); yylval.ival = LSL_PRIM_TYPE_TORUS; return(INTEGER_CONSTANT); } +"PRIM_TYPE_TUBE" { count(); yylval.ival = LSL_PRIM_TYPE_TUBE; return(INTEGER_CONSTANT); } +"PRIM_TYPE_RING" { count(); yylval.ival = LSL_PRIM_TYPE_RING; return(INTEGER_CONSTANT); } + +"PRIM_HOLE_DEFAULT" { count(); yylval.ival = LSL_PRIM_HOLE_DEFAULT; return(INTEGER_CONSTANT); } +"PRIM_HOLE_CIRCLE" { count(); yylval.ival = LSL_PRIM_HOLE_CIRCLE; return(INTEGER_CONSTANT); } +"PRIM_HOLE_SQUARE" { count(); yylval.ival = LSL_PRIM_HOLE_SQUARE; return(INTEGER_CONSTANT); } +"PRIM_HOLE_TRIANGLE" { count(); yylval.ival = LSL_PRIM_HOLE_TRIANGLE; return(INTEGER_CONSTANT); } + +"PRIM_MATERIAL_STONE" { count(); yylval.ival = LSL_PRIM_MATERIAL_STONE; return(INTEGER_CONSTANT); } +"PRIM_MATERIAL_METAL" { count(); yylval.ival = LSL_PRIM_MATERIAL_METAL; return(INTEGER_CONSTANT); } +"PRIM_MATERIAL_GLASS" { count(); yylval.ival = LSL_PRIM_MATERIAL_GLASS; return(INTEGER_CONSTANT); } +"PRIM_MATERIAL_WOOD" { count(); yylval.ival = LSL_PRIM_MATERIAL_WOOD; return(INTEGER_CONSTANT); } +"PRIM_MATERIAL_FLESH" { count(); yylval.ival = LSL_PRIM_MATERIAL_FLESH; return(INTEGER_CONSTANT); } +"PRIM_MATERIAL_PLASTIC" { count(); yylval.ival = LSL_PRIM_MATERIAL_PLASTIC; return(INTEGER_CONSTANT); } +"PRIM_MATERIAL_RUBBER" { count(); yylval.ival = LSL_PRIM_MATERIAL_RUBBER; return(INTEGER_CONSTANT); } +"PRIM_MATERIAL_LIGHT" { count(); yylval.ival = LSL_PRIM_MATERIAL_LIGHT; return(INTEGER_CONSTANT); } + +"PRIM_SHINY_NONE" { count(); yylval.ival = LSL_PRIM_SHINY_NONE; return(INTEGER_CONSTANT); } +"PRIM_SHINY_LOW" { count(); yylval.ival = LSL_PRIM_SHINY_LOW; return(INTEGER_CONSTANT); } +"PRIM_SHINY_MEDIUM" { count(); yylval.ival = LSL_PRIM_SHINY_MEDIUM; return(INTEGER_CONSTANT); } +"PRIM_SHINY_HIGH" { count(); yylval.ival = LSL_PRIM_SHINY_HIGH; return(INTEGER_CONSTANT); } + +"PRIM_BUMP_NONE" { count(); yylval.ival = LSL_PRIM_BUMP_NONE; return(INTEGER_CONSTANT); } +"PRIM_BUMP_BRIGHT" { count(); yylval.ival = LSL_PRIM_BUMP_BRIGHT; return(INTEGER_CONSTANT); } +"PRIM_BUMP_DARK" { count(); yylval.ival = LSL_PRIM_BUMP_DARK; return(INTEGER_CONSTANT); } +"PRIM_BUMP_WOOD" { count(); yylval.ival = LSL_PRIM_BUMP_WOOD; return(INTEGER_CONSTANT); } +"PRIM_BUMP_BARK" { count(); yylval.ival = LSL_PRIM_BUMP_BARK; return(INTEGER_CONSTANT); } +"PRIM_BUMP_BRICKS" { count(); yylval.ival = LSL_PRIM_BUMP_BRICKS; return(INTEGER_CONSTANT); } +"PRIM_BUMP_CHECKER" { count(); yylval.ival = LSL_PRIM_BUMP_CHECKER; return(INTEGER_CONSTANT); } +"PRIM_BUMP_CONCRETE" { count(); yylval.ival = LSL_PRIM_BUMP_CONCRETE; return(INTEGER_CONSTANT); } +"PRIM_BUMP_TILE" { count(); yylval.ival = LSL_PRIM_BUMP_TILE; return(INTEGER_CONSTANT); } +"PRIM_BUMP_STONE" { count(); yylval.ival = LSL_PRIM_BUMP_STONE; return(INTEGER_CONSTANT); } +"PRIM_BUMP_DISKS" { count(); yylval.ival = LSL_PRIM_BUMP_DISKS; return(INTEGER_CONSTANT); } +"PRIM_BUMP_GRAVEL" { count(); yylval.ival = LSL_PRIM_BUMP_GRAVEL; return(INTEGER_CONSTANT); } +"PRIM_BUMP_BLOBS" { count(); yylval.ival = LSL_PRIM_BUMP_BLOBS; return(INTEGER_CONSTANT); } +"PRIM_BUMP_SIDING" { count(); yylval.ival = LSL_PRIM_BUMP_SIDING; return(INTEGER_CONSTANT); } +"PRIM_BUMP_LARGETILE" { count(); yylval.ival = LSL_PRIM_BUMP_LARGETILE; return(INTEGER_CONSTANT); } +"PRIM_BUMP_STUCCO" { count(); yylval.ival = LSL_PRIM_BUMP_STUCCO; return(INTEGER_CONSTANT); } +"PRIM_BUMP_SUCTION" { count(); yylval.ival = LSL_PRIM_BUMP_SUCTION; return(INTEGER_CONSTANT); } +"PRIM_BUMP_WEAVE" { count(); yylval.ival = LSL_PRIM_BUMP_WEAVE; return(INTEGER_CONSTANT); } + +"PRIM_TEXGEN_DEFAULT" { count(); yylval.ival = LSL_PRIM_TEXGEN_DEFAULT; return(INTEGER_CONSTANT); } +"PRIM_TEXGEN_PLANAR" { count(); yylval.ival = LSL_PRIM_TEXGEN_PLANAR; return(INTEGER_CONSTANT); } + +"MASK_BASE" { count(); yylval.ival = 0; return(INTEGER_CONSTANT); } +"MASK_OWNER" { count(); yylval.ival = 1; return(INTEGER_CONSTANT); } +"MASK_GROUP" { count(); yylval.ival = 2; return(INTEGER_CONSTANT); } +"MASK_EVERYONE" { count(); yylval.ival = 3; return(INTEGER_CONSTANT); } +"MASK_NEXT" { count(); yylval.ival = 4; return(INTEGER_CONSTANT); } + +"PERM_TRANSFER" { count(); yylval.ival = PERM_TRANSFER; return(INTEGER_CONSTANT); } +"PERM_MODIFY" { count(); yylval.ival = PERM_MODIFY; return(INTEGER_CONSTANT); } +"PERM_COPY" { count(); yylval.ival = PERM_COPY; return(INTEGER_CONSTANT); } +"PERM_MOVE" { count(); yylval.ival = PERM_MOVE; return(INTEGER_CONSTANT); } +"PERM_ALL" { count(); yylval.ival = PERM_ALL; return(INTEGER_CONSTANT); } + +"PARCEL_MEDIA_COMMAND_STOP" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_STOP; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_PAUSE" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_PAUSE; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_PLAY" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_PLAY; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_LOOP" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_LOOP; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_TEXTURE" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_TEXTURE; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_URL" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_URL; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_TIME" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_TIME; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_AGENT" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_AGENT; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_UNLOAD" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_UNLOAD; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_AUTO_ALIGN" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_AUTO_ALIGN; return(INTEGER_CONSTANT); } + +"LIST_STAT_MAX" { count(); yylval.ival = LIST_STAT_MAX; return(INTEGER_CONSTANT); } +"LIST_STAT_MIN" { count(); yylval.ival = LIST_STAT_MIN; return(INTEGER_CONSTANT); } +"LIST_STAT_MEAN" { count(); yylval.ival = LIST_STAT_MEAN; return(INTEGER_CONSTANT); } +"LIST_STAT_MEDIAN" { count(); yylval.ival = LIST_STAT_MEDIAN; return(INTEGER_CONSTANT); } +"LIST_STAT_STD_DEV" { count(); yylval.ival = LIST_STAT_STD_DEV; return(INTEGER_CONSTANT); } +"LIST_STAT_SUM" { count(); yylval.ival = LIST_STAT_SUM; return(INTEGER_CONSTANT); } +"LIST_STAT_SUM_SQUARES" { count(); yylval.ival = LIST_STAT_SUM_SQUARES; return(INTEGER_CONSTANT); } +"LIST_STAT_NUM_COUNT" { count(); yylval.ival = LIST_STAT_NUM_COUNT; return(INTEGER_CONSTANT); } +"LIST_STAT_GEOMETRIC_MEAN" { count(); yylval.ival = LIST_STAT_GEO_MEAN; return(INTEGER_CONSTANT); } +"LIST_STAT_RANGE" { count(); yylval.ival = LIST_STAT_RANGE; return(INTEGER_CONSTANT); } + +"PAY_HIDE" { count(); yylval.ival = PAY_PRICE_HIDE; return(INTEGER_CONSTANT); } +"PAY_DEFAULT" { count(); yylval.ival = PAY_PRICE_DEFAULT; return(INTEGER_CONSTANT); } + +"PARCEL_FLAG_ALLOW_FLY" { count(); yylval.ival = PF_ALLOW_FLY; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_GROUP_SCRIPTS" { count(); yylval.ival = PF_ALLOW_GROUP_SCRIPTS; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_SCRIPTS" { count(); yylval.ival = PF_ALLOW_OTHER_SCRIPTS; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_LANDMARK" { count(); yylval.ival = PF_ALLOW_LANDMARK; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_TERRAFORM" { count(); yylval.ival = PF_ALLOW_TERRAFORM; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_DAMAGE" { count(); yylval.ival = PF_ALLOW_DAMAGE; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_CREATE_OBJECTS" { count(); yylval.ival = PF_CREATE_OBJECTS; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_CREATE_GROUP_OBJECTS" { count(); yylval.ival = PF_CREATE_GROUP_OBJECTS; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_USE_ACCESS_GROUP" { count(); yylval.ival = PF_USE_ACCESS_GROUP; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_USE_ACCESS_LIST" { count(); yylval.ival = PF_USE_ACCESS_LIST; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_USE_BAN_LIST" { count(); yylval.ival = PF_USE_BAN_LIST; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_USE_LAND_PASS_LIST" { count(); yylval.ival = PF_USE_PASS_LIST; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_LOCAL_SOUND_ONLY" { count(); yylval.ival = PF_SOUND_LOCAL; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_RESTRICT_PUSHOBJECT" { count(); yylval.ival = PF_RESTRICT_PUSHOBJECT; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_GROUP_OBJECT_ENTRY" { count(); yylval.ival = PF_ALLOW_GROUP_OBJECT_ENTRY; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_ALL_OBJECT_ENTRY" { count(); yylval.ival = PF_ALLOW_ALL_OBJECT_ENTRY; return(INTEGER_CONSTANT); } + +"REGION_FLAG_ALLOW_DAMAGE" { count(); yylval.ival = REGION_FLAGS_ALLOW_DAMAGE; return(INTEGER_CONSTANT); } +"REGION_FLAG_FIXED_SUN" { count(); yylval.ival = REGION_FLAGS_SUN_FIXED; return(INTEGER_CONSTANT); } +"REGION_FLAG_BLOCK_TERRAFORM" { count(); yylval.ival = REGION_FLAGS_BLOCK_TERRAFORM; return(INTEGER_CONSTANT); } +"REGION_FLAG_SANDBOX" { count(); yylval.ival = REGION_FLAGS_SANDBOX; return(INTEGER_CONSTANT); } +"REGION_FLAG_DISABLE_COLLISIONS" { count(); yylval.ival = REGION_FLAGS_SKIP_COLLISIONS; return(INTEGER_CONSTANT); } +"REGION_FLAG_DISABLE_PHYSICS" { count(); yylval.ival = REGION_FLAGS_SKIP_PHYSICS; return(INTEGER_CONSTANT); } +"REGION_FLAG_BLOCK_FLY" { count(); yylval.ival = REGION_FLAGS_BLOCK_FLY; return(INTEGER_CONSTANT); } +"REGION_FLAG_ALLOW_DIRECT_TELEPORT" { count(); yylval.ival = REGION_FLAGS_ALLOW_DIRECT_TELEPORT; return(INTEGER_CONSTANT); } +"REGION_FLAG_RESTRICT_PUSHOBJECT" { count(); yylval.ival = REGION_FLAGS_RESTRICT_PUSHOBJECT; return(INTEGER_CONSTANT); } + +"HTTP_METHOD" { count(); yylval.ival = HTTP_METHOD; return(INTEGER_CONSTANT); } +"HTTP_MIMETYPE" { count(); yylval.ival = HTTP_MIMETYPE; return(INTEGER_CONSTANT); } +"HTTP_BODY_MAXLENGTH" { count(); yylval.ival = HTTP_BODY_MAXLENGTH; return(INTEGER_CONSTANT); } +"HTTP_BODY_TRUNCATED" { count(); yylval.ival = HTTP_BODY_TRUNCATED; return(INTEGER_CONSTANT); } +"HTTP_VERIFY_CERT" { count(); yylval.ival = HTTP_VERIFY_CERT; return(INTEGER_CONSTANT); } + +"PARCEL_COUNT_TOTAL" { count(); yylval.ival = OC_TOTAL; return(INTEGER_CONSTANT); } +"PARCEL_COUNT_OWNER" { count(); yylval.ival = OC_OWNER; return(INTEGER_CONSTANT); } +"PARCEL_COUNT_GROUP" { count(); yylval.ival = OC_GROUP; return(INTEGER_CONSTANT); } +"PARCEL_COUNT_OTHER" { count(); yylval.ival = OC_OTHER; return(INTEGER_CONSTANT); } +"PARCEL_COUNT_SELECTED" { count(); yylval.ival = OC_SELECTED; return(INTEGER_CONSTANT); } +"PARCEL_COUNT_TEMP" { count(); yylval.ival = OC_TEMP; return(INTEGER_CONSTANT); } + +"PARCEL_DETAILS_NAME" { count(); yylval.ival = PARCEL_DETAILS_NAME; return(INTEGER_CONSTANT); } +"PARCEL_DETAILS_DESC" { count(); yylval.ival = PARCEL_DETAILS_DESC; return(INTEGER_CONSTANT); } +"PARCEL_DETAILS_OWNER" { count(); yylval.ival = PARCEL_DETAILS_OWNER; return(INTEGER_CONSTANT); } +"PARCEL_DETAILS_GROUP" { count(); yylval.ival = PARCEL_DETAILS_GROUP; return(INTEGER_CONSTANT); } +"PARCEL_DETAILS_AREA" { count(); yylval.ival = PARCEL_DETAILS_AREA; return(INTEGER_CONSTANT); } + +{L}({L}|{N})* { count(); yylval.sval = new char[strlen(yytext) + 1]; strcpy(yylval.sval, yytext); return(IDENTIFIER); } + +{D}+{E} { count(); yylval.fval = (F32)atof(yytext); return(FP_CONSTANT); } +{D}*"."{D}+({E})?{FS}? { count(); yylval.fval = (F32)atof(yytext); return(FP_CONSTANT); } +{D}+"."{D}*({E})?{FS}? { count(); yylval.fval = (F32)atof(yytext); return(FP_CONSTANT); } + +L?\"(\\.|[^\\"])*\" { parse_string(); count(); return(STRING_CONSTANT); } + +"++" { count(); return(INC_OP); } +"--" { count(); return(DEC_OP); } +"+=" { count(); return(ADD_ASSIGN); } +"-=" { count(); return(SUB_ASSIGN); } +"*=" { count(); return(MUL_ASSIGN); } +"/=" { count(); return(DIV_ASSIGN); } +"%=" { count(); return(MOD_ASSIGN); } +";" { count(); return(';'); } +"{" { count(); return('{'); } +"}" { count(); return('}'); } +"," { count(); return(','); } +"=" { count(); return('='); } +"(" { count(); return('('); } +")" { count(); return(')'); } +"-" { count(); return('-'); } +"+" { count(); return('+'); } +"*" { count(); return('*'); } +"/" { count(); return('/'); } +"%" { count(); return('%'); } +"@" { count(); return('@'); } +":" { count(); return(':'); } +">" { count(); return('>'); } +"<" { count(); return('<'); } +"]" { count(); return(']'); } +"[" { count(); return('['); } +"==" { count(); return(EQ); } +"!=" { count(); return(NEQ); } +">=" { count(); return(GEQ); } +"<=" { count(); return(LEQ); } +"&" { count(); return('&'); } +"|" { count(); return('|'); } +"^" { count(); return('^'); } +"~" { count(); return('~'); } +"!" { count(); return('!'); } +"&&" { count(); return(BOOLEAN_AND); } +"||" { count(); return(BOOLEAN_OR); } +"<<" { count(); return(SHIFT_LEFT); } +">>" { count(); return(SHIFT_RIGHT); } + +[ \t\v\n\f] { count(); } +. { /* ignore bad characters */ } + +%% + +LLScriptAllocationManager *gAllocationManager; +LLScriptScript *gScriptp; + +// Prototype for the yacc parser entry point +int yyparse(void); + +int yyerror(const char *fmt, ...) +{ + gErrorToText.writeError(yyout, gLine, gColumn, LSERROR_SYNTAX_ERROR); + return 0; +} + +#define LL_MKS_YACC 1 +#if LL_WINDOWS && LL_MKS_YACC +int yyinput(void) +{ + return input(); +} +#endif + +//#define EMERGENCY_DEBUG_PRINTOUTS +//#define EMIT_CIL_ASSEMBLER + +BOOL lscript_compile(const char* src_filename, const char* dst_filename, + const char* err_filename, BOOL is_god_like) +{ + BOOL b_parse_ok = FALSE; + BOOL b_dummy = FALSE; + U64 b_dummy_count = FALSE; + LSCRIPTType type = LST_NULL; + + gInternalColumn = 0; + gInternalLine = 0; + gScriptp = NULL; + + gErrorToText.init(); + init_supported_expressions(); + init_temp_jumps(); + gAllocationManager = new LLScriptAllocationManager(); + + yyin = LLFile::fopen(src_filename, "r"); + if (yyin) + { + yyout = LLFile::fopen(err_filename, "w"); + + // Reset the lexer's internal buffering. +#if LL_DARWIN || LL_LINUX || !LL_MKS_YACC + yyrestart(yyin); +#else + yy_reset(); +#endif + b_parse_ok = !yyparse(); + + if (b_parse_ok) + { +#ifdef EMERGENCY_DEBUG_PRINTOUTS + char compiled[256]; + sprintf(compiled, "%s.o", src_filename); + FILE* compfile; + compfile = LLFile::fopen(compiled, "w"); +#endif + + if(dst_filename) + { + gScriptp->setBytecodeDest(dst_filename); + } + + gScriptp->mGodLike = is_god_like; + + gScopeStringTable = new LLStringTable(16384); +#ifdef EMERGENCY_DEBUG_PRINTOUTS + gScriptp->recurse(compfile, 0, 4, LSCP_PRETTY_PRINT, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); +#endif + gScriptp->recurse(yyout, 0, 0, LSCP_PRUNE, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); + gScriptp->recurse(yyout, 0, 0, LSCP_SCOPE_PASS1, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); + gScriptp->recurse(yyout, 0, 0, LSCP_SCOPE_PASS2, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); + gScriptp->recurse(yyout, 0, 0, LSCP_TYPE, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); + if (!gErrorToText.getErrors()) + { + gScriptp->recurse(yyout, 0, 0, LSCP_RESOURCE, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); +#ifdef EMERGENCY_DEBUG_PRINTOUTS + gScriptp->recurse(yyout, 0, 0, LSCP_EMIT_ASSEMBLY, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); +#endif +#ifdef EMIT_CIL_ASSEMBLER + const char* cil_output_file_name = dst_filename? dst_filename : "lscript.cil"; + FILE* cilout = LLFile::fopen(cil_output_file_name, "w"); + if(NULL == cilout) + { + fprintf(yyout, "Error opening cil output file %s\n", cil_output_file_name); + } + else + { + gScriptp->recurse(cilout, 0, 0, LSCP_EMIT_CIL_ASSEMBLY, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); + if(fclose(cilout) == EOF) + { + fprintf(yyout, "Error closing cil output file %s\n", cil_output_file_name); + } + } +#endif + gScriptp->recurse(yyout, 0, 0, LSCP_EMIT_BYTE_CODE, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); + } + delete gScopeStringTable; + gScopeStringTable = NULL; +#ifdef EMERGENCY_DEBUG_PRINTOUTS + fclose(compfile); +#endif + } + fclose(yyout); + } + + fclose(yyin); + delete gAllocationManager; + delete gScopeStringTable; + + return b_parse_ok && !gErrorToText.getErrors(); +} + + +BOOL lscript_compile(char *filename, BOOL is_god_like = FALSE) +{ + char src_filename[MAX_STRING]; + sprintf(src_filename, "%s.lsl", filename); + char err_filename[MAX_STRING]; + sprintf(err_filename, "%s.out", filename); + return lscript_compile(src_filename, NULL, err_filename, is_god_like); +} + + +S32 yywrap() +{ + return(1); +} + +void comment() +{ + char c; + +#if LL_DARWIN + while ((c = yyinput()) != '\n' && c != 0 && c != EOF) + ; +#else + while ((c = yyinput()) != '\n' && c != 0) + ; +#endif + + +} + +void count() +{ + S32 i; + + gColumn = gInternalColumn; + gLine = gInternalLine; + + for (i = 0; yytext[i] != '\0'; i++) + if (yytext[i] == '\n') + { + gInternalLine++; + gInternalColumn = 0; + } + else if (yytext[i] == '\t') + gInternalColumn += 4 - (gInternalColumn % 8); + else + gInternalColumn++; +} + +void parse_string() +{ + S32 length = (S32)strlen(yytext); + length = length - 2; + char *temp = yytext + 1; + + S32 i; + S32 escapes = 0; + S32 tabs = 0; + for (i = 0; i < length; i++) + { + if (temp[i] == '\\') + { + escapes++; + i++; + if (temp[i] == 't') + tabs++; + } + } + + S32 newlength = length - escapes + tabs*3; + yylval.sval = new char[newlength + 1]; + + char *dest = yylval.sval; + + for (i = 0; i < length; i++) + { + if (temp[i] == '\\') + { + i++; + // linefeed + if (temp[i] == 'n') + { + *dest++ = 10; + } + else if (temp[i] == 't') + { + *dest++ = ' '; + *dest++ = ' '; + *dest++ = ' '; + *dest++ = ' '; + } + else + { + *dest++ = temp[i]; + } + } + else + { + *dest++ = temp[i]; + } + } + yylval.sval[newlength] = 0; +} diff --git a/indra/lscript/lscript_compile/indra.y b/indra/lscript/lscript_compile/indra.y new file mode 100644 index 0000000000..7744649a92 --- /dev/null +++ b/indra/lscript/lscript_compile/indra.y @@ -0,0 +1,1680 @@ +%{ + #include "stdtypes.h" + #include "lscript_tree.h" + + #ifdef __cplusplus + extern "C" { + #endif + + int yylex(void); + int yyparse( void ); + int yyerror(const char *fmt, ...); + + #if LL_LINUX + // broken yacc codegen... --ryan. + #define getenv getenv_workaround + #endif + + #ifdef __cplusplus + } + #endif +%} + +%union +{ + S32 ival; + F32 fval; + char *sval; + class LLScriptType *type; + class LLScriptConstant *constant; + class LLScriptIdentifier *identifier; + class LLScriptSimpleAssignable *assignable; + class LLScriptGlobalVariable *global; + class LLScriptEvent *event; + class LLScriptEventHandler *handler; + class LLScriptExpression *expression; + class LLScriptStatement *statement; + class LLScriptGlobalFunctions *global_funcs; + class LLScriptFunctionDec *global_decl; + class LLScriptState *state; + class LLScritpGlobalStorage *global_store; + class LLScriptScript *script; +}; + +%token INTEGER +%token FLOAT_TYPE +%token STRING +%token LLKEY +%token VECTOR +%token QUATERNION +%token LIST + +%token STATE_DEFAULT +%token STATE +%token EVENT +%token JUMP +%token RETURN + +%token STATE_ENTRY +%token STATE_EXIT +%token TOUCH_START +%token TOUCH +%token TOUCH_END +%token COLLISION_START +%token COLLISION +%token COLLISION_END +%token LAND_COLLISION_START +%token LAND_COLLISION +%token LAND_COLLISION_END +%token TIMER +%token CHAT +%token SENSOR +%token NO_SENSOR +%token CONTROL +%token AT_TARGET +%token NOT_AT_TARGET +%token AT_ROT_TARGET +%token NOT_AT_ROT_TARGET +%token MONEY +%token EMAIL +%token RUN_TIME_PERMISSIONS +%token INVENTORY +%token ATTACH +%token DATASERVER +%token MOVING_START +%token MOVING_END +%token REZ +%token OBJECT_REZ +%token LINK_MESSAGE +%token REMOTE_DATA +%token HTTP_RESPONSE + +%token IDENTIFIER +%token STATE_DEFAULT + +%token INTEGER_CONSTANT +%token INTEGER_TRUE +%token INTEGER_FALSE + +%token FP_CONSTANT + +%token STRING_CONSTANT + +%token INC_OP +%token DEC_OP +%token ADD_ASSIGN +%token SUB_ASSIGN +%token MUL_ASSIGN +%token DIV_ASSIGN +%token MOD_ASSIGN + +%token EQ +%token NEQ +%token GEQ +%token LEQ + +%token BOOLEAN_AND +%token BOOLEAN_OR + +%token SHIFT_LEFT +%token SHIFT_RIGHT + +%token IF +%token ELSE +%token FOR +%token DO +%token WHILE + +%token PRINT + +%token PERIOD + +%token ZERO_VECTOR +%token ZERO_ROTATION + +%nonassoc LOWER_THAN_ELSE +%nonassoc ELSE + + +%type